From 50c07128cd2c7062a22df508d95f158ae68f04b0 Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Sun, 22 Mar 2015 17:13:28 -0400 Subject: [PATCH 1/5] untappd support --- .../Owin.Security.Providers.csproj | 9 + Owin.Security.Providers/Untappd/Constants.cs | 7 + .../IUntappdAuthenticationProvider.cs | 24 ++ .../Provider/UntappdAuthenticatedContext.cs | 89 +++++++ .../Provider/UntappdAuthenticationProvider.cs | 50 ++++ .../Provider/UntappdReturnEndpointContext.cs | 26 ++ .../UntappdAuthenticationExtensions.cs | 41 ++++ .../Untappd/UntappdAuthenticationHandler.cs | 232 ++++++++++++++++++ .../UntappdAuthenticationMiddleware.cs | 85 +++++++ .../Untappd/UntappdAuthenticationOptions.cs | 143 +++++++++++ 10 files changed, 706 insertions(+) create mode 100644 Owin.Security.Providers/Untappd/Constants.cs create mode 100644 Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs create mode 100644 Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs create mode 100644 Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs create mode 100644 Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs create mode 100644 Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs create mode 100644 Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj index a638670..75e4410 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -251,6 +251,15 @@ + + + + + + + + + diff --git a/Owin.Security.Providers/Untappd/Constants.cs b/Owin.Security.Providers/Untappd/Constants.cs new file mode 100644 index 0000000..fef3041 --- /dev/null +++ b/Owin.Security.Providers/Untappd/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.Untappd +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "Untappd"; + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs b/Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs new file mode 100644 index 0000000..f3aab47 --- /dev/null +++ b/Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Untappd +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IUntappdAuthenticationProvider + { + /// + /// Invoked whenever Untappd succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(UntappdAuthenticatedContext context); + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + Task ReturnEndpoint(UntappdReturnEndpointContext context); + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs new file mode 100644 index 0000000..b7fb0c8 --- /dev/null +++ b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Security.Claims; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Untappd +{ + /// + /// Contains information about the login session as well as the user . + /// + public class UntappdAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Untappd Access token + public UntappdAuthenticatedContext(IOwinContext context, JObject user, string accessToken) + : base(context) + { + User = user; + AccessToken = accessToken; + + Id = TryGetValue(user, "_id"); + Name = TryGetValue(user, "first_name") +" "+ TryGetValue(user, "last_name"); + Link = TryGetValue(user, "url"); + UserName = TryGetValue(user, "user_name"); + Email = TryGetValue(user, "email_address"); + } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the Untappd user obtained from the User Info endpoint. By default this is https://api.Untappd.com/user but it can be + /// overridden in the options + /// + public JObject User { get; private set; } + + /// + /// Gets the Untappd access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the Untappd user ID + /// + public string Id { get; private set; } + + /// + /// Gets the user's name + /// + public string Name { get; private set; } + + public string Link { get; private set; } + + /// + /// Gets the Untappd username + /// + public string UserName { get; private set; } + + /// + /// Gets the Untappd email + /// + public string Email { get; private set; } + + /// + /// Gets the representing the user + /// + public ClaimsIdentity Identity { get; set; } + + /// + /// Gets or sets a property bag for common authentication properties + /// + public AuthenticationProperties Properties { get; set; } + + private static string TryGetValue(JObject user, string propertyName) + { + JToken value; + return user.TryGetValue(propertyName, out value) ? value.ToString() : null; + } + } +} diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs new file mode 100644 index 0000000..a290334 --- /dev/null +++ b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Untappd +{ + /// + /// Default implementation. + /// + public class UntappdAuthenticationProvider : IUntappdAuthenticationProvider + { + /// + /// Initializes a + /// + public UntappdAuthenticationProvider() + { + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); + } + + /// + /// Gets or sets the function that is invoked when the Authenticated method is invoked. + /// + public Func OnAuthenticated { get; set; } + + /// + /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. + /// + public Func OnReturnEndpoint { get; set; } + + /// + /// Invoked whenever Untappd succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(UntappdAuthenticatedContext context) + { + return OnAuthenticated(context); + } + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + public virtual Task ReturnEndpoint(UntappdReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs b/Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs new file mode 100644 index 0000000..688d513 --- /dev/null +++ b/Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; + +namespace Owin.Security.Providers.Untappd +{ + /// + /// Provides context information to middleware providers. + /// + public class UntappdReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public UntappdReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs new file mode 100644 index 0000000..f16a748 --- /dev/null +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs @@ -0,0 +1,41 @@ +using System; + +namespace Owin.Security.Providers.Untappd +{ + public static class UntappdAuthenticationExtensions + { + /// + /// Login with Untappd. http://yourUrl/signin-Untappd will be used as the redirect URI + /// + /// + /// + /// + public static IAppBuilder UseUntappdAuthentication(this IAppBuilder app, + UntappdAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException("app"); + if (options == null) + throw new ArgumentNullException("options"); + + app.Use(typeof(UntappdAuthenticationMiddleware), app, options); + + return app; + } + /// + /// Login with Untappd. http://yourUrl/signin-Untappd will be used as the redirect URI + /// + /// + /// + /// + /// + public static IAppBuilder UseUntappdAuthentication(this IAppBuilder app, string clientId, string clientSecret) + { + return app.UseUntappdAuthentication(new UntappdAuthenticationOptions + { + ClientId = clientId, + ClientSecret = clientSecret + }); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs new file mode 100644 index 0000000..0173d4f --- /dev/null +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.Untappd +{ + public class UntappdAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger logger; + private readonly HttpClient httpClient; + + public UntappdAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + IReadableStringCollection query = Request.Query; + IList values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = string.Copy(values.First()); + } + values = query.GetValues("state"); + if (values != null && values.Count == 1) + { + state = values[0]; + } + + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, logger)) + { + return new AuthenticationTicket(null, properties); + } + + string requestPrefix = Request.Scheme + "://" + Request.Host; + string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List>(); + body.Add(new KeyValuePair("client_id", Options.ClientId)); + body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + body.Add(new KeyValuePair("redirect_uri", redirectUri)); + body.Add(new KeyValuePair("code", code)); + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Content = new FormUrlEncodedContent(body); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + string text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + string accessToken = (string)response.access_token; + + // Get the Untappd user + HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + JObject user = JObject.Parse(text); + + var context = new UntappdAuthenticatedContext(Context, user, accessToken); + context.Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType); + if (!string.IsNullOrEmpty(context.Id)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.UserName)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Email)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim("urn:Untappd:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:Untappd:url", context.Link, XmlSchemaString, Options.AuthenticationType)); + } + context.Properties = properties; + + await Options.Provider.Authenticated(context); + + return new AuthenticationTicket(context.Identity, context.Properties); + } + catch (Exception ex) + { + logger.WriteError(ex.Message); + } + return new AuthenticationTicket(null, properties); + } + + protected override Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) + { + return Task.FromResult(null); + } + + AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge != null) + { + string baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + string currentUri = + baseUri + + Request.Path + + Request.QueryString; + + string redirectUri = + baseUri + + Options.CallbackPath; + + AuthenticationProperties properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + string authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&response_type=" + "code"; + + Response.Redirect(authorizationEndpoint); + } + + return Task.FromResult(null); + } + + public override async Task InvokeAsync() + { + return await InvokeReplyPathAsync(); + } + + private async Task InvokeReplyPathAsync() + { + if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + { + // TODO: error responses + + AuthenticationTicket ticket = await AuthenticateAsync(); + if (ticket == null) + { + logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new UntappdReturnEndpointContext(Context, ticket); + context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType; + context.RedirectUri = ticket.Properties.RedirectUri; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + ClaimsIdentity grantIdentity = context.Identity; + if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) + { + grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType); + } + Context.Authentication.SignIn(context.Properties, grantIdentity); + } + + if (!context.IsRequestCompleted && context.RedirectUri != null) + { + string redirectUri = context.RedirectUri; + if (context.Identity == null) + { + // add a redirect hint that sign-in failed in some way + redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied"); + } + Response.Redirect(redirectUri); + context.RequestCompleted(); + } + + return context.IsRequestCompleted; + } + return false; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs new file mode 100644 index 0000000..0fc0000 --- /dev/null +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler; +using Microsoft.Owin.Security.DataProtection; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.Properties; + +namespace Owin.Security.Providers.Untappd +{ + public class UntappdAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public UntappdAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + UntappdAuthenticationOptions options) + : base(next, options) + { + if (String.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "ClientId")); + if (String.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + Resources.Exception_OptionMustBeProvided, "ClientSecret")); + + logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new UntappdAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + IDataProtector dataProtector = app.CreateDataProtector( + typeof (UntappdAuthenticationMiddleware).FullName, + Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + } + + if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + + httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10, + }; + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Untappd middleware"); + httpClient.DefaultRequestHeaders.ExpectContinue = false; + } + + /// + /// Provides the object for processing + /// authentication-related requests. + /// + /// + /// An configured with the + /// supplied to the constructor. + /// + protected override AuthenticationHandler CreateHandler() + { + return new UntappdAuthenticationHandler(httpClient, logger); + } + + private HttpMessageHandler ResolveHttpMessageHandler(UntappdAuthenticationOptions options) + { + HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + + // If they provided a validator, apply it or fail. + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) + { + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); + } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + + return handler; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs new file mode 100644 index 0000000..81886fe --- /dev/null +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.Untappd +{ + public class UntappdAuthenticationOptions : AuthenticationOptions + { + public class UntappdAuthenticationEndpoints + { + /// + /// Endpoint which is used to redirect users to request Untappd access + /// + /// + /// Defaults to https://untappd.com/oauth/authenticate/ + /// + public string AuthorizationEndpoint { get; set; } + + /// + /// Endpoint which is used to exchange code for access token + /// + /// + /// Defaults to https://untappd.com/oauth/authorize + /// + public string TokenEndpoint { get; set; } + + /// + /// Endpoint which is used to obtain user information after authentication + /// + /// + /// Defaults tohttps://untappd.com/v4/user/info + /// + public string UserInfoEndpoint { get; set; } + } + + private const string AuthorizationEndPoint = "https://untappd.com/oauth/authenticate"; + private const string TokenEndpoint = "https://untappd.com/oauth/authorize"; + private const string UserInfoEndpoint = "https://untappd.com/v4/user/info"; + + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to Untappd. + /// + /// + /// The pinned certificate validator. + /// + /// + /// If this property is null then the default certificate checks are performed, + /// validating the subject name and if the signing chain is a trusted party. + /// + public ICertificateValidator BackchannelCertificateValidator { get; set; } + + /// + /// The HttpMessageHandler used to communicate with Untappd. + /// This cannot be set at the same time as BackchannelCertificateValidator unless the value + /// can be downcast to a WebRequestHandler. + /// + public HttpMessageHandler BackchannelHttpHandler { get; set; } + + /// + /// Gets or sets timeout value in milliseconds for back channel communications with Untappd. + /// + /// + /// The back channel timeout in milliseconds. + /// + public TimeSpan BackchannelTimeout { get; set; } + + /// + /// The request path within the application's base path where the user-agent will be returned. + /// The middleware will process this request when it arrives. + /// Default value is "/signin-Untappd". + /// + public PathString CallbackPath { get; set; } + + /// + /// Get or sets the text that the user can display on a sign in user interface. + /// + public string Caption + { + get { return Description.Caption; } + set { Description.Caption = value; } + } + + /// + /// Gets or sets the Untappd supplied Client ID + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the Untappd supplied Client Secret + /// + public string ClientSecret { get; set; } + + /// + /// Gets the sets of OAuth endpoints used to authenticate against Untappd. Overriding these endpoints allows you to use Untappd Enterprise for + /// authentication. + /// + public UntappdAuthenticationEndpoints Endpoints { get; set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IUntappdAuthenticationProvider Provider { get; set; } + + /// + /// A list of permissions to request. + /// + public IList Scope { get; private set; } + + /// + /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user + /// . + /// + public string SignInAsAuthenticationType { get; set; } + + /// + /// Gets or sets the type used to secure data handled by the middleware. + /// + public ISecureDataFormat StateDataFormat { get; set; } + + /// + /// Initializes a new + /// + public UntappdAuthenticationOptions() + : base("Untappd") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-Untappd"); + AuthenticationMode = AuthenticationMode.Passive; + //untappd has no scopes AFAIK + Scope = new List{}; + BackchannelTimeout = TimeSpan.FromSeconds(60); + Endpoints = new UntappdAuthenticationEndpoints + { + AuthorizationEndpoint = AuthorizationEndPoint, + TokenEndpoint = TokenEndpoint, + UserInfoEndpoint = UserInfoEndpoint + }; + } + } +} \ No newline at end of file From 3e438209c9314c5ee0511a2bc5d39dd35ad1386a Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Wed, 8 Apr 2015 22:59:06 -0400 Subject: [PATCH 2/5] stopping point --- .../Owin.Security.Providers.csproj | 1 + .../Untappd/ApiResponse.cs | 26 +++++++++ .../Provider/UntappdAuthenticatedContext.cs | 10 ++-- .../Untappd/UntappdAuthenticationHandler.cs | 54 +++++++----------- .../UntappdAuthenticationMiddleware.cs | 8 --- .../Untappd/UntappdAuthenticationOptions.cs | 2 +- ...-OwinOAuthProvidersDemo-20131113093838.mdf | Bin 3211264 -> 3211264 bytes ...nOAuthProvidersDemo-20131113093838_log.ldf | Bin 1048576 -> 1048576 bytes 8 files changed, 55 insertions(+), 46 deletions(-) create mode 100644 Owin.Security.Providers/Untappd/ApiResponse.cs diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj index 75e4410..0c8c6e3 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -251,6 +251,7 @@ + diff --git a/Owin.Security.Providers/Untappd/ApiResponse.cs b/Owin.Security.Providers/Untappd/ApiResponse.cs new file mode 100644 index 0000000..4edaadf --- /dev/null +++ b/Owin.Security.Providers/Untappd/ApiResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Untappd +{ + + internal class ResponseRoot + { + public Meta meta { get; set; } + public Response response { get; set; } + } + + public class Meta + { + public int http_code { get; set; } + } + + public class Response + { + public string access_token { get; set; } + } + +} diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs index b7fb0c8..8d3f5a2 100644 --- a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs +++ b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs @@ -27,11 +27,11 @@ namespace Owin.Security.Providers.Untappd User = user; AccessToken = accessToken; - Id = TryGetValue(user, "_id"); - Name = TryGetValue(user, "first_name") +" "+ TryGetValue(user, "last_name"); - Link = TryGetValue(user, "url"); - UserName = TryGetValue(user, "user_name"); - Email = TryGetValue(user, "email_address"); + Id = user["response"]["user"]["id"].ToString(); + Name = user["response"]["user"]["first_name"].ToString() +" "+ user["response"]["user"]["last_name"].ToString(); + Link = user["response"]["user"]["url"].ToString(); + UserName = user["response"]["user"]["user_name"].ToString(); + Email = user["response"]["user"]["settings"]["email_address"].ToString(); } /// diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs index 0173d4f..f9a2d7b 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs @@ -35,7 +35,6 @@ namespace Owin.Security.Providers.Untappd try { string code = null; - string state = null; IReadableStringCollection query = Request.Query; IList values = query.GetValues("code"); @@ -43,45 +42,30 @@ namespace Owin.Security.Providers.Untappd { code = string.Copy(values.First()); } - values = query.GetValues("state"); - if (values != null && values.Count == 1) - { - state = values[0]; - } - - properties = Options.StateDataFormat.Unprotect(state); - if (properties == null) - { - return null; - } - - // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) - { - return new AuthenticationTicket(null, properties); - } - string requestPrefix = Request.Scheme + "://" + Request.Host; string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; - // Build up the body for the token request - var body = new List>(); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("code", code)); + //// Build up the body for the token request + //var body = new List>(); + //body.Add(new KeyValuePair("client_id", Options.ClientId)); + //body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + //body.Add(new KeyValuePair("redirect_url", redirectUri)); + //body.Add(new KeyValuePair("response_type", "code")); + //body.Add(new KeyValuePair("code", code)); // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Content = new FormUrlEncodedContent(body); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, + + + String.Format(@"{0}/?client_id={1}&client_secret={2}&response_type=code&redirect_url={3}&code={4}", Options.Endpoints.TokenEndpoint,Options.ClientId, Options.ClientSecret, redirectUri, code)); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); string text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; + var response = JsonConvert.DeserializeObject(text); + string accessToken = response.response.access_token; // Get the Untappd user HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); @@ -116,10 +100,16 @@ namespace Owin.Security.Providers.Untappd { context.Identity.AddClaim(new Claim("urn:Untappd:url", context.Link, XmlSchemaString, Options.AuthenticationType)); } + + + IDictionary data = new Dictionary + { + { "userData", "Data" } + }; + properties = new AuthenticationProperties(data); context.Properties = properties; - await Options.Provider.Authenticated(context); - + return new AuthenticationTicket(context.Identity, context.Properties); } catch (Exception ex) @@ -167,7 +157,7 @@ namespace Owin.Security.Providers.Untappd string authorizationEndpoint = Options.Endpoints.AuthorizationEndpoint + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&redirect_url=" + Uri.EscapeDataString(redirectUri) + "&response_type=" + "code"; Response.Redirect(authorizationEndpoint); diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs index 0fc0000..c8a5df2 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs @@ -32,14 +32,6 @@ namespace Owin.Security.Providers.Untappd if (Options.Provider == null) Options.Provider = new UntappdAuthenticationProvider(); - if (Options.StateDataFormat == null) - { - IDataProtector dataProtector = app.CreateDataProtector( - typeof (UntappdAuthenticationMiddleware).FullName, - Options.AuthenticationType, "v1"); - Options.StateDataFormat = new PropertiesDataFormat(dataProtector); - } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs index 81886fe..165ee7a 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs @@ -37,7 +37,7 @@ namespace Owin.Security.Providers.Untappd private const string AuthorizationEndPoint = "https://untappd.com/oauth/authenticate"; private const string TokenEndpoint = "https://untappd.com/oauth/authorize"; - private const string UserInfoEndpoint = "https://untappd.com/v4/user/info"; + private const string UserInfoEndpoint = "https://api.untappd.com/v4/user/info"; /// /// Gets or sets the a pinned certificate validator to use to validate the endpoints used diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf index 9c13e273623639ea9bcecfb4355cbdbc13599d5b..0961ca784a28222be15d363238a999f850c018a8 100644 GIT binary patch delta 946 zcmaKqPe>F|7{F)7+1b9jTZ;o%LJ(V1NH^%bU zN@+`}^U2H}9#UR3aHk8a(ysqFjMMMs*p!t~Y%5cfzY%Gn^lj4AD(xNEZ=G&WEY&CT zlhQG%UmB4{rK{4AbXmG6jp0CwC-8Zi&&{l=vol3CYme5b+U~Bsrm9OaiP}`wtr9N6 zO?d3Ao6R+DA1Ge6h3`+7@0ZWZ@*eaTTqYLVnH!O@Ai<8xAv`mpgfVrO1xtk!2`c>XiF9j3VW^*DD$XWY zR1ToL;S1y9uv^7KY2mliA%HK(sl(|cGb-_9DSl|OFj~c0700er_O+g5KPy_XgY}o= zsUDLNUcyJz5_QBL!cXiaG$LU4nDzP1R@QVra*qz-{BxDZbFN{&{C`b8`_@~&IX=kI z-^mS}Nb)#d=;YDqQyQ*!@mn>Q!D z=*p0mQFKvS=(;N9E~~XjC`A{7h*F4~B3%@v2)eL(CXbq7J~*8JKR+|W6bc)xK&)sc zW98VUQ4X0Q<0Nx9bjSbI`QmE1^nUPV)_nW7dw}}mWT1VxroZe}%VV2yJ8QCQO!{|S zJf=KL4(-sRistMR)ocES9#vfIk~^q+L6{N!!LK8{IL>>vpOeSgig5aSzRpl1XX&fC3$DglW$x@ zRNTxbV>vA?tHVO~r~@c`^7T(6BF$fvUK3o@RFL_yh{%Pns5$!|=(Mk9SKM}Ae zGe;`>L*IJWjVE*i^{v!5QI9By<;D;Cb73RUctj&>*jG*)%WnH(-Z{s)73lOK7zjax zE`$(57@-@X2cZ`sg3yN$Md(KuKrj(v2yui#gdv2Z2*(hH5fTVVgcL#=A%k!nVFcmC IGK{|Z2Sz%~%m4rY diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf index 7625dd83ad2328f35e124d7fc6eb21e78aee9e36..75f6af2689fbacac42fabff31776a0f31844fda0 100644 GIT binary patch delta 22891 zcmeI4d3;nww#Vzio_*^{qvo$tErn1(vLk%_Mskb_JcT2$Voan zq=k`6$NnvPUUsc~8vbB#eqGl+2JbwG8T#ui&J$AM+xaCS`tSC;<6z^1^CNrQFxZz* zjG4ESX5UUS*u&g#J1HnQJa=GlTqlE=K`eG@FEh4IFZNs>CfP2vR}x1qTU@p=XyH|N$jGYxvV zpp8*Ulk<1bU-P5FXQOb<)RQ3GEyL;r#ofjSXC`^G1voUwe)e@U>k-Yqdz}*^Lz9uE z)5+Nb{;iPKM}@zEAq@?F7eZ>1tw_m`W>}IUjJ&5C8}UYD-ZPDjXxB4jJV~dE{@5|i za6#O}RgZnS^8z(;Ws?xHt_BH}!}KPGmQ{De9Q!bBhhdr~YmqFA!9yTqy8^mN64(bJ zeaSE~hCD-Z81s`^)WgYhWHdWBp4>&8Vt4D8kf#?u zFr2u3KA4h0!lh@1vk+231B|xQo7vIyox!19LwRIzOj1@SoJA1IK4RJTNY*`=wHTx+ zsYg=S$4K^J8m93__5rP|g?4*1nI0S*(+~oeN9fYu#l!Qy?2*OuX+BKk@|2znD=lI9 zc}o`r@tM#!*l@G2pbnJ-uAsEMiJyVW-QtzIMQ*WHZ{@B(TQ?f&=A(smn{ITc2Zptr z=Dy?F1zWL&m0h{a0CzgW#!rJSRU(N;Oz_OH5}&ocL{qzsSd8C92)5HUq{ z67?zirXi+iGEC9>Vc9jZL)dTzZD9*hPj@k*>4Uo$Gy2+f;O zV9uv63=avDRW#|;;X`w$45T@EA+*Z~Z$l96JtCi$jI2#-j}&KNxoAZjjoccM!JbJ6 zHR-jHyIqh@E&A|CaNeRNIkr4IdtosQ+6zZxS5Sn?VG-;Z93scRj1#V6 z>B@1f!=gkJ$)KmlJp0vW5G*LK6pmQdUXKvexYEBYo8_~g&MBoE*U+O{=Cky9^G;OkQ4&!A~ z3M}Hd1TxM~X1E&a=DRj(~@wt;8p>3uLXN_s+saqo?>`qjF;y+B`>F8Vg7Q>PEjeI%oIPSV1}l18Ladcu%~bK|!( zxDn@$%dS5?c1^v?n8=un=$yI>qe|FnpI&E3?YR+K!f(`a*UGLrJ#0;AWsS%h86i2Y zg~263Bq-e+V$5X=LClfK|BDMmANuR+sPIovc=8W%iA=cN8`i{uaKy2IbjFoRPAY?~ zRM2LS0}a`F)xRc0c&95A3t&Ka6yHHE#3)wPNUII$pLr^UuK@S3^C2{1aQzP6Xh3&} zpC~x^gT!tV(2i_y6l)*!V2Q zU{1Pjx>ztR#P~qH#P4>Xdlp;+$&ML(bio|< zUpu(SO!Bs}jo;1MUzzia@h9WN!I|4Jz zq~i*^+?irQJtN*ESHdV{3%YS%a7aM|I)%TPFIc8hEU^}vaLqh!4t#m~N0r<)gT zsD&4+1!OiR1yvVq3fk=rK#12fL3u&?IeK1p`m`U0i#Wq6`Jz}ofg|N8a%5O|Hc1hG zLAun)zg*DFi1-5aigsQdXU@y4hXjK$5L|==&rt~8%i1KtGTg=xk|V)(`7O7z=-=0B^_*yQ!*~a;@`&SfC%k;AQxnNf^EArRb)rZ+bx?i-j zTg2H7V(jK$DQI9cP|011vb!={WJAgMZ%npIn}N}eQ^8$@+AXtGaG?+?xH2OESG1|% z=3gy{g;J{6u13E@9=+fP+npTSUO^}L9KYy;?M_E*g=drOIKVhhHA z?M}q@Jq6qStW9Do!{s44Vr!QNY`;YLiG7bL*jl!TtxOooxEis&=!~u9!keuP+qEcO zC(mOoxvK@X5?7JGj;%h(9y0kZF&y3nq|n^yfNeg?Udn5$V;dN44cm37ZN49>thZ1I z6PRzYd;coD!@xDvR=iG*b2`kDdq#Y7P0*s>SbNfumxkl zwivOUr(k=4wMlGcxI83BZ0+)Z?FN)TxLd{6vPEoV!cfNbh;2cz<7#WU@Mden_A3-$ zo8`rJy}(xDD)QH{)d$%_wx6?OTkL@C*C_i~mJhap(blm22DS4uRcxUUDz-Z?BdE7% zs}9?Z=yz7uML(`A`b=EBtt<=Ewjt?+D;U3?(FLTopo-hE>(tE(*-1Gc*m zTdH7N%GxBhGF%>#Ber&V!1h~|e><~GS#2#_#8xH@W!#9^=DHlQwOn|!wP9O;;{Q0G zXRWpy1-24bk-v_uKFA(2r>hta??@DyyBx5EtNrYgB%W`hueO2F*09}z+JC+frmVJ5 z2o+nI5!73>Rfp|&=y&!ef7tHk*e<*x>hud-VpERYj@Sy%CfUsgvH-CyM7_atHEh8c zu-%Q=&R4KK%-STjGTcQFk|VZudBAon%Fo%XVr$tVwu>=gDC2jC?JpsY*jg^U+1jw( zhT@sy-nX#*PGBo>75VGf>Vxbd>4z$8ugi8jV7nbv2hV2g2eqd}(1zRYD ziml8D>Mh!;!?p*?U@LJI`Rmx~gX|&8Z;IjY4o0E5#{pZoD8oKU?*XYgwt>;su-${& zEBe<`u!TaX*vgEc-lDBKY~f`(!n4V& z0NY&x+wgs;*X#08(8CJ}U<=glMQRr))RwU}Nv(`_2SnsZtz8|c-H+;>i_a<4TDC|n z_l_45_6lnACap4J=xc?kRbC0RoOsi{BVm!Q~ zQE2Yfxz#R$!HWa-Njgr5)-HktNLsy;a1f;rz1&{G7V4m4TZ{<-wxX>%Y+>UFDg8Qu zFF!taWbDh!e1c)y_B6*ftCqJ=@NAzWw!*W?tN_~*Tx}1b-Ut1UsH-g)1GoE-+Yc3P z%UPS`R)!l6Avtnumj`ZP!@)kuPcQRdE_l<0WsBT$@5pVb;MT~>OP*>*JCbX;@n&oz z_Xq~iBd5ue?=PUvUGYt*yAn zP`m3-_bBK>AyjnxV@80kXsZrgSkEA*e>CSWuYJ(n&o>IIUOB_jJ=nN=nBs! z+0O^^2%>u&^*R`bRdm4^(A|&dex#s#lC?>6Ww<;fM|AD-fG$h}_DOCv;TKO3UCS2H zl?g)`%W$J`I^1!iV7c&SYs2;gif^5^+`_g@U@LJI`Rmx~gX|#{WnwtILs4k%cfc0v z2RS#H7Nuhw7;O#P3e^5#%DoDCdq~>%VmNRV z*tbHn!~xs0D0{9?q>gQ1v^8wcp|-2neG0Zv2o>8lm=V-lv{i@gdGz~E5P#>>2ipT2 z+fN3cFmV3vI!g<8@y*iHAS}wfV+OWNZ;_vTWZDD&}U@LJI`Rmx~ zgX|$IoCD77C$aTZ|b2wxX>%Y_Fi- z9+mtZPakX#a%_thiaN!~JD;#%2OY5$o=tL)59B4p_A2TPFF&Ya3&w!$LBw{6f-S5T z5?dMWP6){nTf019dky8Y&+_-P5nIa^vE|+o+bf7|T$CfWmJ4sTHf*n>_>NX9Eo`p{ zY$dKDe;r$WkUiv|?_9KDd(Z*fzoTq&t0p?OfzXaN>ZH3?WVnbMAu-lR1pknf0Jmw$ z*K5!Ocg?c6NTU`(3eQj>mFHhDRp42)`S4uVNKEIqfaS7|3=-4LECEyS-MsZV@QwG3 z>b^bX*DA{hclxkLd-l$9T_Z{Om+zQ^|GMb^_|jZ&LE#=VF)r#oW@214hZwM047iUT z0#2}s;buR{G}u#iEU*H0v!6tp|6+LG3I|KLQx1H0yut76_nF|Oe1czEDHGMN;&e^VS=X^QP2YSQG(|0i%aArhttpjCV44N^3Rk>4$D4Fa%l(Md016W za<8Qt!j=>x5?W%}1AMgp;VfK$X`HQimJ!Sl3T&c7kC?naG&UNVnbB3wb8f{f+5ItU z(%L)+oBG^_kgM*+IL}jDa6X0(@-)nnQ(RQRJJrELA94?goPEhQ)k~|G>d;@_1MYG+ z^pe?ORea#&tzS|g-+(=!=>OHY6HaP43*}HJbrdG$ZS&z+yPX59;@JcIewlyJ&$Yut zoZ;l3MX9y|Wy*2LkzwK4B+SRaXkMlI^yb>OG;Z^coMiax0#JT<&MB2-=msPoLXwv$ zB*V@~k}Lz~!8wv#Wgy@E$a#i86M;Y$ueqx5Z1v)C%pkG|Ze|%Tc-|W0$g}0aR)EXO zt##lJZVHD8W;I!&Q60~zmNbis=v3>%*xAvi~>g{7ZBkm?llRCb=c$vBzbAF7-JEJkv#5wpwGM!XEWK@~`mh7lO;CC5lYXcU zh`dHM+{O=sIBuXyHIzf8dKo6=X6Hp)byPQFmf*MH8~*-;@d&3ncK>C*0E~o{K{<{% zQY}22g!w?K8*5ZIXP!X0$NG~h)zA&7K7v%QP^gAoyQEqM&VzHLy2?P1Y7l@x%(t&A zYqix2sg^}>Gs|Xz>XG#wskS`W3gAn1ON`*ulBL>O-HcN$X%-dHsn!ATkOsk5ZEN)r z2dZ14a)%{J_O&`7@*35xQU1Gy_bY2PltZOD9+Psj^P;Ufs#DSL%cJ?bi9WmNqnzrk zO|S7%mBThjIgUC~Ej*iq`9P{$YE;8tcmhoGpFXZq4c&n1qe%5ig=%<_E2)-&^WYq* zt}+m$x(x<0?8WaDs;yo~^)@Vmn^~s{s>>TVQf+y#6~LG3wirQp+A@vmR8F;|SyV)) zS_i;G_P-^DiaEri&^+ouHN2#QLAaHcXs0?L@*355p?p-E2NbHI94gf%n3S8H7j4y1 z-A?)~Kjdcza*R`5I`=xK`ZMhhZ3#K%NVV{6GAl`STaD_wQF-{4GL>rR22>wIs#hb` zCG318c5$=sGVntXoFmm$27*+#N3UVWE-O@9y^w0IgFBFRg6hv2I#O+Uuob|U>U%JP zO_iT%RJY?)OPWPRbgFd#JY-6@zuVU8V-8f`i^_v46YNw6L|&u%K9v9FY)6G^D2Gb* zR7}dv&WpC{sJSKdMscyjQD&;71q*{133G;z#^*tKZ4=_)#O!P`Q zrcw>vfa)@&`b&jsnA&o!mVuW+aE?@083mPjG!Bfi~RqF;7yKgjf_phglzY581o;h7IO22bepd>MyP!4d%c| zYan+<>7bA%3dm3f736)Gkei(sZPkI?1^vGJ@h(3gALk(VED>c|2QQM9#3U*l!RBa0c@TL0tXl zM`c;IdLY2PFnKq#>>`%sb%~A?TOPE;eF@G)_2h;dCBZJP!Ixx7uc(Ahu|DV?GIP^S z8^y;RD9%FVZ4FJ0;=svkBzHym`{J4^Bttn=l3&B5pd_NLI+7nkzpL-x>4)TUPBQ8K zpJJl{Zk40lk!0c7B+Lgc$(h=c{5$3eKstT*c9mx61~iu=&FdAKVI`6@%fNYXjx<*p z2on8!3}i#cpA@34UP!bof}2@Cgc}XFBhi)zTLFBj?uHRWCw!$*{Sc>G(kv>XQ>_Ew zA?r4Zp<)j4C^X9*sP4|JU=aS9VA!b+h`dJi!ze!`shL7GltZO@J0|63=S5p}RQEu? zTc0WRL-h$x^|Z(zIMo}HnXz)5aHLvzHVN~ARCm*;?#VnsLI3jnHkE4V22`Iwsy8T9 z!){AbEd%GlIZ|C^AV_sD45az9KSK#{qha+zs;6TS+Nx9j1(N-PRkE7qOH|_VcR9A4S*Ic{Bm+BaRP&q0bsTQ71_KyZ7 z)%_&Z{EfQ-%o8~5Abp=BGtparhYm-pRxOr@jP*)iTLOqaqg*cXP zz6o(4)K)KqTGqhLEFb4k8!`O9=~a1SH@9Tg@P3oM2EJeq;+AaDEZU?k*N=0sCD5WO zI@me`9uhX>C)>`tLI>OW+XRU52~^H5aw&hCAl?}djQTfhh}k%rp#CJPFWQ!(tkzHs zmFt?A6Z8;mK3vBbOH+dm$$u!x|EuJ0U+~{+{#JBrbTAqQqv>EY8;lkOqb1?sDhVe8i>0;)>}?M?+8%JWJ>Y74z}@zM Pr|ki6+XKF35BL=TcnUR6 From 5318c1c788a8515d8b570e899371ed8b36756be3 Mon Sep 17 00:00:00 2001 From: rodkings Date: Sun, 19 Apr 2015 19:28:24 -0600 Subject: [PATCH 3/5] Saving State by Cookie --- .../Provider/UntappdAuthenticatedContext.cs | 10 +++- .../Untappd/UntappdAuthenticationHandler.cs | 52 +++++++++++++++---- .../UntappdAuthenticationMiddleware.cs | 10 +++- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs index 8d3f5a2..a40a8da 100644 --- a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs +++ b/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs @@ -28,10 +28,11 @@ namespace Owin.Security.Providers.Untappd AccessToken = accessToken; Id = user["response"]["user"]["id"].ToString(); - Name = user["response"]["user"]["first_name"].ToString() +" "+ user["response"]["user"]["last_name"].ToString(); + Name = user["response"]["user"]["first_name"].ToString() + " " + user["response"]["user"]["last_name"].ToString(); Link = user["response"]["user"]["url"].ToString(); UserName = user["response"]["user"]["user_name"].ToString(); Email = user["response"]["user"]["settings"]["email_address"].ToString(); + AvatarUrl = user["response"]["user"]["user_avatar"].ToString(); } /// @@ -70,6 +71,11 @@ namespace Owin.Security.Providers.Untappd /// public string Email { get; private set; } + /// + /// Gets the Untappd avatar url 100x100 + /// + public string AvatarUrl { get; private set; } + /// /// Gets the representing the user /// @@ -86,4 +92,4 @@ namespace Owin.Security.Providers.Untappd return user.TryGetValue(propertyName, out value) ? value.ToString() : null; } } -} +} \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs index f9a2d7b..4a1eb15 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs @@ -17,6 +17,7 @@ namespace Owin.Security.Providers.Untappd { public class UntappdAuthenticationHandler : AuthenticationHandler { + private const string StateCookie = "_StateCookie"; private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; private readonly ILogger logger; @@ -35,6 +36,7 @@ namespace Owin.Security.Providers.Untappd try { string code = null; + string state = null; IReadableStringCollection query = Request.Query; IList values = query.GetValues("code"); @@ -42,6 +44,21 @@ namespace Owin.Security.Providers.Untappd { code = string.Copy(values.First()); } + + // restore State from Cookie + state = Request.Cookies[StateCookie]; + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, logger)) + { + return new AuthenticationTicket(null, properties); + } + string requestPrefix = Request.Scheme + "://" + Request.Host; string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; @@ -54,10 +71,8 @@ namespace Owin.Security.Providers.Untappd //body.Add(new KeyValuePair("code", code)); // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Get, - - - String.Format(@"{0}/?client_id={1}&client_secret={2}&response_type=code&redirect_url={3}&code={4}", Options.Endpoints.TokenEndpoint,Options.ClientId, Options.ClientSecret, redirectUri, code)); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, + String.Format(@"{0}/?client_id={1}&client_secret={2}&response_type=code&redirect_url={3}&code={4}", Options.Endpoints.TokenEndpoint, Options.ClientId, Options.ClientSecret, redirectUri, code)); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); @@ -80,6 +95,10 @@ namespace Owin.Security.Providers.Untappd Options.AuthenticationType, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); + + // Add access_token to Claims to be used later on authenticated Untappd API requests + context.Identity.AddClaim(new Claim("UntappdAccessToken", accessToken)); + if (!string.IsNullOrEmpty(context.Id)) { context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType)); @@ -100,16 +119,20 @@ namespace Owin.Security.Providers.Untappd { context.Identity.AddClaim(new Claim("urn:Untappd:url", context.Link, XmlSchemaString, Options.AuthenticationType)); } - + if (!string.IsNullOrEmpty(context.AvatarUrl)) + { + context.Identity.AddClaim(new Claim("urn:Untappd:avatar", context.AvatarUrl, XmlSchemaString, Options.AuthenticationType)); + } + + //IDictionary data = new Dictionary + // { + // { "userData", "Data" } + // }; + //properties = new AuthenticationProperties(data); - IDictionary data = new Dictionary - { - { "userData", "Data" } - }; - properties = new AuthenticationProperties(data); context.Properties = properties; await Options.Provider.Authenticated(context); - + return new AuthenticationTicket(context.Identity, context.Properties); } catch (Exception ex) @@ -160,6 +183,13 @@ namespace Owin.Security.Providers.Untappd "&redirect_url=" + Uri.EscapeDataString(redirectUri) + "&response_type=" + "code"; + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); Response.Redirect(authorizationEndpoint); } diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs index c8a5df2..eb9d6c8 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs @@ -32,13 +32,21 @@ namespace Owin.Security.Providers.Untappd if (Options.Provider == null) Options.Provider = new UntappdAuthenticationProvider(); + if (Options.StateDataFormat == null) + { + IDataProtector dataProtector = app.CreateDataProtector( + typeof(UntappdAuthenticationMiddleware).FullName, + Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + } + if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, - MaxResponseContentBufferSize = 1024*1024*10, + MaxResponseContentBufferSize = 1024 * 1024 * 10, }; httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Untappd middleware"); httpClient.DefaultRequestHeaders.ExpectContinue = false; From 8b5e916a7c3c44a637714e3b0b526bacabfcb27e Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Sun, 19 Apr 2015 22:39:27 -0400 Subject: [PATCH 4/5] include demo --- .../App_Start/Startup.Auth.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index bf04d4a..49d2f3f 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -26,6 +26,7 @@ using Owin.Security.Providers.Yahoo; using Owin.Security.Providers.OpenID; using Owin.Security.Providers.SoundCloud; using Owin.Security.Providers.Steam; +using Owin.Security.Providers.Untappd; using Owin.Security.Providers.WordPress; namespace OwinOAuthProvidersDemo @@ -44,6 +45,7 @@ namespace OwinOAuthProvidersDemo // Use a cookie to temporarily store information about a user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); + //app.UseUntappdAuthentication("id", "secret"); // Uncomment the following lines to enable logging in with third party login providers //app.UseMicrosoftAccountAuthentication( // clientId: "", @@ -64,7 +66,7 @@ namespace OwinOAuthProvidersDemo //app.UseYahooAuthentication("", ""); //app.UseTripItAuthentication("", ""); - + //app.UseGitHubAuthentication("", ""); //app.UseBufferAuthentication("", ""); @@ -109,7 +111,7 @@ namespace OwinOAuthProvidersDemo // ClientSecret = "", // Provider = new TwitchAuthenticationProvider() // { - + // OnAuthenticated = async z => // { //// Getting the twitch users picture @@ -118,7 +120,7 @@ namespace OwinOAuthProvidersDemo //// You should be able to access these claims with HttpContext.GetOwinContext().Authentication.GetExternalLoginInfoAsync().Claims in your Account Controller // // Commonly used in the ExternalLoginCallback() in AccountController.cs // /* - + // if (user != null) // { // var claim = (await AuthenticationManager.GetExternalLoginInfoAsync()).ExternalIdentity.Claims.First( @@ -131,7 +133,7 @@ namespace OwinOAuthProvidersDemo // } //}; //app.UseTwitchAuthentication(opt); - + //app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo"); @@ -191,24 +193,24 @@ namespace OwinOAuthProvidersDemo // clientSecret: ""); - //app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions - //{ - // ClientId = "", - // ClientSecret = "" - //}); - //app.UseBattleNetAuthentication( - // clientId: "", - // clientSecret: ""); + //app.UseBattleNetAuthentication(new BattleNetAuthenticationOptions + //{ + // ClientId = "", + // ClientSecret = "" + //}); + //app.UseBattleNetAuthentication( + // clientId: "", + // clientSecret: ""); //app.UseAsanaAuthentication("", ""); //app.UseEveOnlineAuthentication("", ""); - //app.UseSoundCloudAuthentication("", ""); + //app.UseSoundCloudAuthentication("", ""); - //app.UseFoursquareAuthentication( - // clientId: "", - // clientSecret: ""); + //app.UseFoursquareAuthentication( + // clientId: "", + // clientSecret: ""); } } } \ No newline at end of file From e0ed492864cedf2e69ffafa369f0910aa75be8b6 Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Sat, 2 May 2015 11:18:21 -0400 Subject: [PATCH 5/5] use lowercase url --- Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs index 165ee7a..43cc055 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs +++ b/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs @@ -127,7 +127,7 @@ namespace Owin.Security.Providers.Untappd : base("Untappd") { Caption = Constants.DefaultAuthenticationType; - CallbackPath = new PathString("/signin-Untappd"); + CallbackPath = new PathString("/signin-untappd"); AuthenticationMode = AuthenticationMode.Passive; //untappd has no scopes AFAIK Scope = new List{};