diff --git a/.gitignore b/.gitignore index d9908de..3ca0adc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,3 @@ -################# -## Eclipse -################# - -*.pydevproject -.project -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.classpath -.settings/ -.loadpath - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - - ################# ## Visual Studio ################# @@ -188,34 +157,4 @@ $RECYCLE.BIN/ # Mac crap .DS_Store - -############# -## Python -############# - -*.py[co] - -# Packages -*.egg -*.egg-info -dist/ -build/ -eggs/ -parts/ -var/ -sdist/ -develop-eggs/ -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg +/Output diff --git a/NuGet/Owin.Security.Providers.nuspec b/NuGet/Owin.Security.Providers.nuspec deleted file mode 100644 index 96914b5..0000000 --- a/NuGet/Owin.Security.Providers.nuspec +++ /dev/null @@ -1,35 +0,0 @@ - - - - Owin.Security.Providers - 1.27 - Jerrie Pelser and contributors - Jerrie Pelser - http://opensource.org/licenses/MIT - https://github.com/owin-middleware/OwinOAuthProviders - false - - Adds additional OAuth providers for OWIN to use with ASP.NET - - - Additional OAuth providers for Katana (OWIN). - - Includes providers for ArcGISOnline, Asana, Backlog, Battle.net, Bitbucket, Buffer, DeviantArt, Dropbox, EVEOnline, Fitbit, Flickr, Foursquare, GitHub, Gitter, Google+, HealthGraph, Imgur, Instagram, LinkedIn, Onshape, PayPal, Reddit, Salesforce, Slack, SoundCloud, Spotify, StackExchange, TripIt, Twitch.tv, Untappd, Vimeo, Visual Studio Online, VKontakte, Wordpress, Yahoo and Yammer. - - Also adds generic OpenID 2.0 providers as well implementations for Steam and Wargaming. - - - Version 1.27 - - Adds Xing provider - - Copyright 2013 - 2016 - owin katana oauth LinkedIn Yahoo Google+ GitHub Reddit Instagram StackExchange SalesForce TripIt Buffer ArcGIS Dropbox Wordpress Battle.NET Yammer OpenID Steam Twitch - - - - - - - - - \ No newline at end of file diff --git a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs b/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs deleted file mode 100644 index a5c71ce..0000000 --- a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.Collections.Generic; -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.ArcGISOnline -{ - public class ArcGISOnlineAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public ArcGISOnlineAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = new FormUrlEncodedContent(body); - 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 ArcGISOnline user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?f=json&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(); - var user = JsonConvert.DeserializeObject(text); - - var context = new ArcGISOnlineAuthenticatedContext(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:ArcGISOnline:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:ArcGISOnline:url", context.Link, XmlSchemaString, Options.AuthenticationType)); - } - string baseUri = - Request.Scheme + - Uri.SchemeDelimiter + - Request.Host + - Request.PathBase; - - 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; - } - - GenerateCorrelationId(properties); - string state = Options.StateDataFormat.Protect(properties); - // comma separated - string scope = string.Join(",", Options.Scope); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&response_type=" + Uri.EscapeDataString(scope) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - 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 ArcGISOnlineReturnEndpointContext(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/ArcGISOnline/Provider/ArcGISOnlineUser.cs b/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineUser.cs deleted file mode 100644 index 3dd3a03..0000000 --- a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineUser.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Owin.Security.Providers.ArcGISOnline.Provider -{ - public class ArcGISOnlineUser - { - public User user { get; set; } - } - - public class User - { - public string username { get; set; } - public string fullName { get; set; } - public string email { get; set; } - } -} diff --git a/Owin.Security.Providers/Backlog/BacklogAuthenticationHandler.cs b/Owin.Security.Providers/Backlog/BacklogAuthenticationHandler.cs deleted file mode 100644 index 40cad0f..0000000 --- a/Owin.Security.Providers/Backlog/BacklogAuthenticationHandler.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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; -using Owin.Security.Providers.Backlog; -using System.Net.Http.Headers; - -namespace Owin.Security.Providers.Backlog -{ - public class BacklogAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public BacklogAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Get token - var tokenRequest = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint); - tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - tokenRequest.Content = new FormUrlEncodedContent(body); - - HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest, Request.CallCancelled); - - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string expires = (string) response.expires_in; - string refreshToken = null; - if (response.refresh_token != null) - refreshToken = (string) response.refresh_token; - string tokenType = (string)response.token_type; - - // Get the Backlog user - var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInfoEndpoint); - userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken)); - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - - userResponse.EnsureSuccessStatusCode(); - text = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new BacklogAuthenticatedContext(Context, user, accessToken, expires, refreshToken); - - 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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.MailAddress)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.MailAddress, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.AuthorizationEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - 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 BacklogReturnEndpointContext(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/BattleNet/BattleNetAuthenticationHandler.cs b/Owin.Security.Providers/BattleNet/BattleNetAuthenticationHandler.cs deleted file mode 100644 index 8a8454a..0000000 --- a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationHandler.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Threading.Tasks; -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.BattleNet -{ - public class BattleNetAuthenticationHandler : AuthenticationHandler - { - - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private string tokenEndpoint = "https://eu.battle.net/oauth/token"; - private string accountUserIdEndpoint = "https://eu.api.battle.net/account/user/id"; - private string accountUserBattleTagEndpoint = "https://eu.api.battle.net/account/user/battletag"; - private string oauthAuthEndpoint = "https://eu.battle.net/oauth/authorize"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public BattleNetAuthenticationHandler(HttpClient httpClient, ILogger logger) - { - this.httpClient = httpClient; - this.logger = logger; - } - - protected override Task InitializeCoreAsync() - { - switch (Options.Region) - { - case Region.China: - tokenEndpoint = "https://cn.battle.net/oauth/token"; - accountUserIdEndpoint = "https://cn.api.battle.net/account/user/id"; - accountUserBattleTagEndpoint = "https://cn.api.battle.net/account/user/battletag"; - oauthAuthEndpoint = "https://cn.battle.net/oauth/authorize"; - break; - case Region.Korea: - tokenEndpoint = "https://kr.battle.net/oauth/token"; - accountUserIdEndpoint = "https://kr.api.battle.net/account/user/id"; - accountUserBattleTagEndpoint = "https://kr.api.battle.net/account/user/battletag"; - oauthAuthEndpoint = "https://kr.battle.net/oauth/authorize"; - break; - case Region.Taiwan: - tokenEndpoint = "https://tw.battle.net/oauth/token"; - accountUserIdEndpoint = "https://tw.api.battle.net/account/user/id"; - accountUserBattleTagEndpoint = "https://tw.api.battle.net/account/user/battletag"; - oauthAuthEndpoint = "https://tw.battle.net/oauth/authorize"; - break; - case Region.Europe: - tokenEndpoint = "https://eu.battle.net/oauth/token"; - accountUserIdEndpoint = "https://eu.api.battle.net/account/user/id"; - accountUserBattleTagEndpoint = "https://eu.api.battle.net/account/user/battletag"; - oauthAuthEndpoint = "https://eu.battle.net/oauth/authorize"; - break; - default: - tokenEndpoint = "https://us.battle.net/oauth/token"; - accountUserIdEndpoint = "https://us.api.battle.net/account/user/id"; - accountUserBattleTagEndpoint = "https://us.api.battle.net/account/user/battletag"; - oauthAuthEndpoint = "https://us.battle.net/oauth/authorize"; - break; - } - - return Task.FromResult(true); - } - - protected override async Task AuthenticateCoreAsync() - { - AuthenticationProperties properties = null; - - try - { - string code = null; - string state = null; - - var query = Request.Query; - var values = query.GetValues("code"); - if (values != null && values.Count == 1) - { - code = values[0]; - } - 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); - } - - // Check for error - if (Request.Query.Get("error") != null) - return new AuthenticationTicket(null, properties); - - var requestPrefix = Request.Scheme + "://" + Request.Host; - var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; - - // Build up the body for the token request - var body = new List> - { - new KeyValuePair("grant_type", "authorization_code"), - new KeyValuePair("code", code), - new KeyValuePair("redirect_uri", redirectUri), - new KeyValuePair("client_id", Options.ClientId), - new KeyValuePair("client_secret", Options.ClientSecret) - }; - - // Request the token - var tokenResponse = await httpClient.PostAsync(tokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - var text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - var response = JsonConvert.DeserializeObject(text); - var accessToken = (string)response.access_token; - var expires = (string)response.expires_in; - - // Get WoW User Id - var graphResponse = await httpClient.GetAsync(accountUserIdEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - var userId = JObject.Parse(text); - - // Get WoW BattleTag - graphResponse = await httpClient.GetAsync(accountUserBattleTagEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - var battleTag = JObject.Parse(text); - - - var context = new BattleNetAuthenticatedContext(Context, userId, battleTag, accessToken, expires) - { - 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.BattleTag)) - { - context.Identity.AddClaim(new Claim("urn:battlenet:battletag", context.BattleTag, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - context.Identity.AddClaim(new Claim("urn:battlenet:accesstoken", context.AccessToken, 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); - } - - var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - - if (challenge != null) - { - var baseUri = - Request.Scheme + - Uri.SchemeDelimiter + - Request.Host + - Request.PathBase; - - var currentUri = - baseUri + - Request.Path + - Request.QueryString; - - var redirectUri = - baseUri + - Options.CallbackPath; - - var properties = challenge.Properties; - if (string.IsNullOrEmpty(properties.RedirectUri)) - { - properties.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - // comma separated - var scope = string.Join(" ", Options.Scope); - - var state = Options.StateDataFormat.Protect(properties); - - var authorizationEndpoint = - oauthAuthEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - 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 - - var ticket = await AuthenticateAsync(); - if (ticket == null) - { - logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new BattleNetReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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; - } - } -} diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs b/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs deleted file mode 100644 index 2837980..0000000 --- a/Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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.Buffer -{ - public class BufferAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://api.bufferapp.com/1/oauth2/token.json"; - private const string UserInfoEndpoint = "https://api.bufferapp.com/1/user.json"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public BufferAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string expires = (string) response.expires_in; - - // Get the Buffer user - HttpResponseMessage graphResponse = await httpClient.GetAsync( - UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new BufferAuthenticatedContext(Context, user, accessToken, expires); - 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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://bufferapp.com/oauth2/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - 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 BufferReturnEndpointContext(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/Cosign/CosignAuthenticationHandler.cs b/Owin.Security.Providers/Cosign/CosignAuthenticationHandler.cs deleted file mode 100644 index cf6dca3..0000000 --- a/Owin.Security.Providers/Cosign/CosignAuthenticationHandler.cs +++ /dev/null @@ -1,345 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Eventing.Reader; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Claims; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Owin; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Cosign.Provider; - -namespace Owin.Security.Providers.Cosign -{ - public class CosignAuthenticationHandler : AuthenticationHandler - { - /* - Cosign sends authenticated users to iis web site root (not to application). - We need redirect user back to Identity Server application. - This can be done with different approaches http handler, url rewrite... - Here is UrlRewrite configuration - - - - - - - - - - - - - - - - - - - - */ - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private readonly ILogger logger; - - public CosignAuthenticationHandler(ILogger logger) - { - - this.logger = logger; - } - - protected override Task AuthenticateCoreAsync() - { - AuthenticationProperties properties = null; - - try - { - string serviceCookieValue = null; - string state = null; - - /*BUG: IReadableStringCollection has a bug. Some charactes can be missed in the collection and replaces with blank space. - Example: having "x" character in QueryString will result in having " " in the collection. - I will use QueryString from Request object instead of IReadableStringCollection*/ - - //IReadableStringCollection query = Request.Query; - //IList values = query.GetValues("cosign-" + Options.ClientServer); - //if (values != null && values.Count == 1) - //{ - // serviceCookieValue = values[0]; - //} - //values = query.GetValues("state"); - //if (values != null && values.Count == 1) - //{ - // state = values[0]; - //} - - string queryString = Request.QueryString.Value; - string[] values = queryString.Split(new string[] {"&"}, StringSplitOptions.RemoveEmptyEntries); - serviceCookieValue = - values.First(a => a.Contains(Options.ClientServer)) - .Replace("cosign-" + Options.ClientServer + "=", ""); - state = - values.First(a => a.Contains("state")) - .Replace("state=", ""); - - properties = Options.StateDataFormat.Unprotect(state); - if (properties == null) - { - return null; - } - - - //// OAuth2 10.12 CSRF - //if (!ValidateCorrelationId(properties, logger)) - //{ - // return new AuthenticationTicket(null, properties); - //} - - - // Get host related information. - IPHostEntry hostEntry = Dns.GetHostEntry(Options.CosignServer); - - // Loop through the AddressList to obtain the supported AddressFamily. This is to avoid - // an exception that occurs when the host IP Address is not compatible with the address family - // (typical in the IPv6 case). - foreach (IPAddress address in hostEntry.AddressList) - { - IPEndPoint ipLocalEndPoint = new IPEndPoint(address, Options.CosignServicePort); - - using (TcpClient tcpClient = new TcpClient()) - { - - tcpClient.Connect(address, Options.CosignServicePort); - if (tcpClient.Connected) - { - logger.WriteInformation("Cosign authenticaion handler. Connected to server ip: " + address); - - //read message from connected server and validate response - NetworkStream networkStream = tcpClient.GetStream(); - byte[] buffer = new byte[256]; - var bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length); - var receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); - //expected message: 220 2 Collaborative Web Single Sign-On [COSIGNv3 FACTORS=5 REKEY] - if (receivedData.Substring(0, 3) != "220") - continue; - - //initiate secure negotiation and validate resonse - //buffer = Encoding.UTF8.GetBytes("STARTTLS 2\r\n"); - buffer = Encoding.UTF8.GetBytes("STARTTLS 2" + Environment.NewLine); - networkStream.Write(buffer, 0, buffer.Length); - networkStream.Flush(); - buffer = new byte[256]; - bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length); - receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); - //expected message: 220 Ready to start TLS - if (receivedData.Substring(0, 3) != "220") - continue; - - - SslStream sslStream = new SslStream(tcpClient.GetStream(), false, ValidateServerCertificate, - null); - - X509CertificateCollection certs = GetCertificateCertificateCollection(Options.ClientServer, - StoreName.My, - StoreLocation.LocalMachine); - try - { - Task authResult = sslStream.AuthenticateAsClientAsync(Options.CosignServer, certs, SslProtocols.Tls, false); - authResult.GetAwaiter().GetResult(); - } - catch (AuthenticationException e) - { - logger.WriteError(e.Message); - if (e.InnerException != null) - { - logger.WriteError(string.Format("Inner exception: {0}", e.InnerException.Message)); - } - logger.WriteError("Authentication failed - closing the connection."); - tcpClient.Close(); - continue; - } - catch (Exception ex) - { - logger.WriteError(ex.Message); - tcpClient.Close(); - continue; - } - - if (!sslStream.IsEncrypted || !sslStream.IsSigned || !sslStream.IsMutuallyAuthenticated) - continue; - // The server name must match the name on the server certificate. - if (!sslStream.IsAuthenticated) - continue; - - - buffer = new byte[256]; - bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length); - receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); - //expected message: 220 2 Collaborative Web Single Sign-On [COSIGNv3 FACTORS=5 REKEY] - if (receivedData.Substring(0, 3) != "220") - continue; - - byte[] data = - Encoding.UTF8.GetBytes("CHECK " + "cosign-" + Options.ClientServer + "=" + - serviceCookieValue + Environment.NewLine); - - sslStream.Write(data, 0, data.Length); - sslStream.Flush(); - buffer = new byte[256]; - - bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length); - receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); - - - switch (receivedData.Substring(0, 1)) - { - case "2": - //Success - logger.WriteInformation("Cosign authenticaion handler. 2-Response from Server: Success."); - var context = new CosignAuthenticatedContext(Context, receivedData) - { - Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType) - }; - - - var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType); - if (!string.IsNullOrEmpty(context.Id)) - { - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, - XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.UserId)) - { - identity.AddClaim(new Claim("UserId", context.UserId, XmlSchemaString, - Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.IpAddress)) - { - identity.AddClaim(new Claim("IpAddress", context.IpAddress, XmlSchemaString, - Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Realm)) - { - identity.AddClaim(new Claim("Realm", context.Realm, XmlSchemaString, - Options.AuthenticationType)); - } - - context.Properties = properties; - - return Task.FromResult(new AuthenticationTicket(identity, properties)); - - - case "4": - //Logged out - logger.WriteInformation("Cosign authenticaion handler. Response from Server: 4-Logged out."); - break; - case "5": - //Try a different server - logger.WriteInformation("Cosign authenticaion handler. Response from Server: 5-Try different server."); - break; - default: - logger.WriteInformation("Cosign authenticaion handler. Response from Server: Undefined."); - break; - - } - } - } - } - } - catch (Exception ex) - { - logger.WriteError(ex.Message); - } - - - return Task.FromResult(new AuthenticationTicket(null, properties)); - } - - protected override Task ApplyResponseChallengeAsync() - { - if (Response.StatusCode == 401) - { - var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - - // Only react to 401 if there is an authentication challenge for the authentication - // type of this handler. - if (challenge != null) - { - var state = challenge.Properties; - - if (string.IsNullOrEmpty(state.RedirectUri)) - { - state.RedirectUri = Request.Uri.ToString(); - } - - var stateString = Options.StateDataFormat.Protect(state); - - string loginUrl = - "https://" + Options.CosignServer + "/?cosign-" + Options.ClientServer + - "&state=" + Uri.EscapeDataString(stateString) + - "&core=" + Options.IdentityServerHostInstance; - logger.WriteInformation("Cosign authenticaion handler. Redirecting to cosign. " + loginUrl); - Response.Redirect(loginUrl); - } - } - - return Task.FromResult(null); - } - - public override async Task InvokeAsync() - { - // This is always invoked on each request. For passive middleware, only do anything if this is - // for our callback path when the user is redirected back from the authentication provider. - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) - { - var ticket = await AuthenticateAsync(); - - if (ticket != null) - { - Context.Authentication.SignIn(ticket.Properties, ticket.Identity); - - Response.Redirect(ticket.Properties.RedirectUri); - - // Prevent further processing by the owin pipeline. - return true; - } - } - // Let the rest of the pipeline run. - return false; - } - - - public static X509CertificateCollection GetCertificateCertificateCollection(string subjectName, - StoreName storeName, - StoreLocation storeLocation) - { - // The following code gets the cert from the keystore - X509Store store = new X509Store(storeName, storeLocation); - store.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = - store.Certificates.Find(X509FindType.FindBySubjectName, - subjectName, - false); - return certCollection; - } - - private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - if (sslPolicyErrors == SslPolicyErrors.None) - return true; - - return false; - } - } -} \ No newline at end of file diff --git a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationHandler.cs b/Owin.Security.Providers/DeviantArt/DeviantAuthenticationHandler.cs deleted file mode 100644 index 4b68278..0000000 --- a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationHandler.cs +++ /dev/null @@ -1,235 +0,0 @@ -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; -using Owin.Security.Providers.DeviantArt; -using Owin.Security.Providers.DeviantArt.Provider; - -namespace Owin.Security.Providers.DeviantArt -{ - public class DeviantArtAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public DeviantArtAuthenticationHandler(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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - - // 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 DeviantArt 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 DeviantArtAuthenticatedContext(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.Name)) - { - context.Identity.AddClaim(new Claim("urn:DeviantArt:name", context.Name, 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); - - // comma separated - string scope = string.Join(" ", Options.Scope.Distinct()); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&response_type=" + "code" + - "&state=" + Uri.EscapeDataString(state); - - 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 DeviantArtReturnEndpointContext(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/Dropbox/DropboxAuthenticationHandler.cs b/Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs deleted file mode 100644 index 83614d2..0000000 --- a/Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -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.DataHandler.Encoder; -using Microsoft.Owin.Security.Infrastructure; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Owin.Security.Providers.Dropbox -{ - public class DropboxAuthenticationHandler : AuthenticationHandler - { - private const string StateCookie = "_DropboxState"; - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://api.dropbox.com/1/oauth2/token"; - private const string UserInfoEndpoint = "https://api.dropbox.com/1/account/info"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public DropboxAuthenticationHandler(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 = values[0]; - } - - 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); - } - - // Check for error - if (Request.Query.Get("error") != null) - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.AppKey)); - body.Add(new KeyValuePair("client_secret", Options.AppSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - 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 Dropbox user - HttpResponseMessage graphResponse = await httpClient.GetAsync( - UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new DropboxAuthenticatedContext(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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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 = - "https://www.dropbox.com/1/oauth2/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.AppKey) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri); - - var cookieOptions = new CookieOptions - { - HttpOnly = true, - Secure = Request.IsSecure - }; - - Response.StatusCode = 302; - Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); - Response.Headers.Set("Location", 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 DropboxReturnEndpointContext(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/Fitbit/FitbitAuthenticationHandler.cs b/Owin.Security.Providers/Fitbit/FitbitAuthenticationHandler.cs deleted file mode 100644 index adec2cb..0000000 --- a/Owin.Security.Providers/Fitbit/FitbitAuthenticationHandler.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Owin; -using Microsoft.Owin.Infrastructure; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.DataHandler.Encoder; -using Microsoft.Owin.Security.Infrastructure; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Owin.Security.Providers.Fitbit.Provider; - -namespace Owin.Security.Providers.Fitbit -{ - public class FitbitAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public FitbitAuthenticationHandler(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 = values[0]; - } - 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("code", code)); - body.Add(new KeyValuePair("grant_type", "authorization_code")); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - - // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", new Base64TextEncoder().Encode(Encoding.ASCII.GetBytes(Options.ClientId + ":" + Options.ClientSecret))); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = new FormUrlEncodedContent(body); - 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; - string refreshToken = (string) response.refresh_token; - - // Get the user info - var userInfoRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserEndpoint); - userInfoRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - userInfoRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage userInfoResponse = await httpClient.SendAsync(userInfoRequest); - userInfoResponse.EnsureSuccessStatusCode(); - text = await userInfoResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new FitbitAuthenticatedContext(Context, user, accessToken, refreshToken); - 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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); - - // comma separated - string scope = string.Join(" ", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&response_type=" + Uri.EscapeDataString("code") + - "&scope=" + Uri.EscapeDataString(scope) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&prompt=" + Uri.EscapeDataString(Options.Prompt) + - "&state=" + Uri.EscapeDataString(state); - - 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 FitbitReturnEndpointContext(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/GitHub/GitHubAuthenticationHandler.cs b/Owin.Security.Providers/GitHub/GitHubAuthenticationHandler.cs deleted file mode 100644 index f52bfbb..0000000 --- a/Owin.Security.Providers/GitHub/GitHubAuthenticationHandler.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; -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.GitHub -{ - public class GitHubAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public GitHubAuthenticationHandler(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 = values[0]; - } - 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("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = new FormUrlEncodedContent(body); - 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 GitHub 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 GitHubAuthenticatedContext(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:github:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:github: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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - 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 GitHubReturnEndpointContext(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/GooglePlus/GooglePlusAuthenticationHandler.cs b/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationHandler.cs deleted file mode 100644 index 2c99b38..0000000 --- a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationHandler.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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; -using Owin.Security.Providers.GooglePlus.Provider; - -namespace Owin.Security.Providers.GooglePlus -{ - public class GooglePlusAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token"; - private const string UserInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"; - private const string GooglePlusUserEndpoint = "https://www.googleapis.com/plus/v1/people/me"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public GooglePlusAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string expires = (string) response.expires_in; - string refreshToken = null; - if (response.refresh_token != null) - refreshToken = (string) response.refresh_token; - - // Get the Google user - HttpResponseMessage graphResponse = await httpClient.GetAsync( - UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - // Get the Google+ Person Info - graphResponse = await httpClient.GetAsync( - GooglePlusUserEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject person = JObject.Parse(text); - - var context = new GooglePlusAuthenticatedContext(Context, user, person, accessToken, expires, refreshToken); - 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:googleplus:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:googleplus: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); - - // comma separated - string scope = string.Join(" ", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://accounts.google.com/o/oauth2/auth" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - // Check if offline access was requested - if (Options.RequestOfflineAccess) - authorizationEndpoint += "&access_type=offline"; - - // Request the moment types - if (Options.MomentTypes.Count > 0) - authorizationEndpoint += String.Format("&request_visible_actions={0}", - String.Join(" ", Options.MomentTypes)); - - 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 GooglePlusReturnEndpointContext(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/HealthGraph/HealthGraphAuthenticationHandler.cs b/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationHandler.cs deleted file mode 100644 index e8b5c5c..0000000 --- a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationHandler.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -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; -using Owin.Security.Providers.HealthGraph.Provider; - -namespace Owin.Security.Providers.HealthGraph -{ - public class HealthGraphAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private HttpClient httpClient; - private ILogger logger; - - public HealthGraphAuthenticationHandler( - HttpClient httpClient, - ILogger logger) - { - this.httpClient = httpClient; - this.logger = logger; - } - - protected async override 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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = new FormUrlEncodedContent(body); - 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 RunKeeper user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - userResponse.EnsureSuccessStatusCode(); - var userText = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(userText); - - // Get the RunKeeper Profile - HttpRequestMessage profileRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.ProfileInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); - HttpResponseMessage profileResponse = await httpClient.SendAsync(profileRequest, Request.CallCancelled); - profileResponse.EnsureSuccessStatusCode(); - var profileText = await profileResponse.Content.ReadAsStringAsync(); - JObject profile = JObject.Parse(profileText); - - var context = new HealthGraphAuthenticatedContext(Context, user, profile, accessToken); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - - if (!string.IsNullOrEmpty(context.UserId)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); - - // comma separated - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&response_type=code" + - "&state=" + Uri.EscapeDataString(state); - - 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 HealthGraphReturnEndpointContext(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/Instagram/InstagramAuthenticationHandler.cs b/Owin.Security.Providers/Instagram/InstagramAuthenticationHandler.cs deleted file mode 100755 index 7493ebf..0000000 --- a/Owin.Security.Providers/Instagram/InstagramAuthenticationHandler.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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; -using Owin.Security.Providers.Instagram.Provider; - -namespace Owin.Security.Providers.Instagram -{ - public class InstagramAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://api.instagram.com/oauth/access_token"; - - private readonly HttpClient httpClient; - private readonly ILogger logger; - - public InstagramAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - JObject user = (JObject) response.user; - - var context = new InstagramAuthenticatedContext(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.Name)) - { - context.Identity.AddClaim(new Claim("urn:instagram:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.ProfilePicture)) - { - context.Identity.AddClaim(new Claim("urn:instagram:profilepicture", context.ProfilePicture, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - context.Identity.AddClaim(new Claim("urn:instagram:accesstoken", context.AccessToken, 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); - - // plus separated (do not URL encode) - string scope = string.Join("+", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://api.instagram.com/oauth/authorize/" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + scope + - "&state=" + Uri.EscapeDataString(state); - - 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 InstagramReturnEndpointContext(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/LinkedIn/LinkedInAuthenticationHandler.cs b/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationHandler.cs deleted file mode 100644 index 4387c1c..0000000 --- a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationHandler.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -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.LinkedIn -{ - public class LinkedInAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://www.linkedin.com/uas/oauth2/accessToken"; - private const string UserInfoEndpoint = "https://api.linkedin.com/v1/people/"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public LinkedInAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string expires = (string) response.expires_in; - - // Get the LinkedIn user - string userInfoEndpoint = UserInfoEndpoint - + "~:("+ string.Join(",", Options.ProfileFields.Distinct().ToArray()) +")" - + "?oauth2_access_token=" + Uri.EscapeDataString(accessToken); - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint); - userRequest.Headers.Add("x-li-format", "json"); - HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new LinkedInAuthenticatedContext(Context, user, accessToken, expires); - 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.Email)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.GivenName)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.FamilyName)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Name)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, Options.AuthenticationType)); - context.Identity.AddClaim(new Claim("urn:linkedin:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:linkedin:url", context.Link, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - context.Identity.AddClaim(new Claim("urn:linkedin:accesstoken", context.AccessToken, 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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - // allow scopes to be specified via the authentication properties for this request, when specified they will already be comma separated - if (properties.Dictionary.ContainsKey("scope")) - { - scope = properties.Dictionary["scope"]; - } - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://www.linkedin.com/uas/oauth2/authorization" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - 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 LinkedInReturnEndpointContext(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/Onshape/OnshapeAuthenticationHandler.cs b/Owin.Security.Providers/Onshape/OnshapeAuthenticationHandler.cs deleted file mode 100644 index 4ae2c70..0000000 --- a/Owin.Security.Providers/Onshape/OnshapeAuthenticationHandler.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -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.DataHandler.Encoder; -using Microsoft.Owin.Security.Infrastructure; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Net.Http.Headers; - -namespace Owin.Security.Providers.Onshape -{ - public class OnshapeAuthenticationHandler : AuthenticationHandler - { - private const string StateCookie = "_OnshapeState"; - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string AuthorizationEndpoint = "/oauth/authorize"; - private const string TokenEndpoint = "/oauth/token"; - private const string UserInfoEndpoint = "/api/users/session"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public OnshapeAuthenticationHandler(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 = values[0]; - } - - 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); - } - - // Check for error - if (Request.Query.Get("error") != null) - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.AppKey)); - body.Add(new KeyValuePair("client_secret", Options.AppSecret)); - - // Get token - var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://" + Options.Hostname + TokenEndpoint); - tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - tokenRequest.Content = new FormUrlEncodedContent(body); - - HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest, Request.CallCancelled); - - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - - string tokenType = (string)response.token_type; - - var userRequest = new HttpRequestMessage(HttpMethod.Get, "https://" + Options.Hostname + UserInfoEndpoint); - userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, accessToken); - HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new OnshapeAuthenticatedContext(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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://" + Options.Hostname + AuthorizationEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.AppKey) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - var cookieOptions = new CookieOptions - { - HttpOnly = true, - Secure = Request.IsSecure - }; - - Response.StatusCode = 302; - Response.Headers.Set("Location", 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 OnshapeReturnEndpointContext(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/OpenID/Constants.cs b/Owin.Security.Providers/OpenID/Constants.cs deleted file mode 100644 index 3486f23..0000000 --- a/Owin.Security.Providers/OpenID/Constants.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace Owin.Security.Providers.OpenID -{ - internal static class Constants - { - internal const string DefaultAuthenticationType = "OpenID"; - } -} diff --git a/Owin.Security.Providers/OpenID/OpenIDAuthenticationMiddleware.cs b/Owin.Security.Providers/OpenID/OpenIDAuthenticationMiddleware.cs deleted file mode 100644 index 67175ee..0000000 --- a/Owin.Security.Providers/OpenID/OpenIDAuthenticationMiddleware.cs +++ /dev/null @@ -1,116 +0,0 @@ -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; -using System; -using System.Globalization; -using System.Net.Http; - -namespace Owin.Security.Providers.OpenID -{ - /// - /// OWIN middleware for authenticating users using an OpenID provider - /// - public class OpenIDAuthenticationMiddleware : OpenIDAuthenticationMiddlewareBase - { - /// - /// Initializes a - /// - /// The next middleware in the OWIN pipeline to invoke - /// The OWIN application - /// Configuration options for the middleware - public OpenIDAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIDAuthenticationOptions options) - : base(next, app, options) - { } - - protected override AuthenticationHandler CreateSpecificHandler() - { - return new OpenIDAuthenticationHandler(_httpClient, _logger); - } - } - - /// - /// OWIN middleware for authenticating users using an OpenID provider - /// - public abstract class OpenIDAuthenticationMiddlewareBase : AuthenticationMiddleware where T : OpenIDAuthenticationOptions - { - protected readonly ILogger _logger; - protected readonly HttpClient _httpClient; - - /// - /// Initializes a - /// - /// The next middleware in the OWIN pipeline to invoke - /// The OWIN application - /// Configuration options for the middleware - public OpenIDAuthenticationMiddlewareBase(OwinMiddleware next, IAppBuilder app, T options) - : base(next, options) - { - if (String.IsNullOrWhiteSpace(Options.ProviderDiscoveryUri) && String.IsNullOrWhiteSpace(Options.ProviderLoginUri) && Options.AuthenticationType != Constants.DefaultAuthenticationType) - { - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ProviderDiscoveryUri")); - } - - _logger = app.CreateLogger(); - - if (Options.Provider == null) - { - Options.Provider = new OpenIDAuthenticationProvider(); - } - - if (Options.StateDataFormat == null) - { - IDataProtector dataProtecter = app.CreateDataProtector( - typeof(OpenIDAuthenticationMiddleware).FullName, - Options.AuthenticationType, "v1"); - Options.StateDataFormat = new PropertiesDataFormat(dataProtecter); - } - - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) - { - Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - } - - _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); - _httpClient.Timeout = Options.BackchannelTimeout; - _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB - } - - /// - /// Provides the object for processing authentication-related requests. - /// - /// An configured with the supplied to the constructor. - protected override AuthenticationHandler CreateHandler() - { - return CreateSpecificHandler(); - } - - /// - /// Provides the object for processing authentication-related requests. - /// - /// An configured with the supplied to the constructor. - protected abstract AuthenticationHandler CreateSpecificHandler(); - - private static HttpMessageHandler ResolveHttpMessageHandler(OpenIDAuthenticationOptions options) - { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); - - // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) - { - // 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; - } - } -} diff --git a/Owin.Security.Providers/OpenID/OpenIDAuthorizationEndpointInfo.cs b/Owin.Security.Providers/OpenID/OpenIDAuthorizationEndpointInfo.cs deleted file mode 100644 index 99be04a..0000000 --- a/Owin.Security.Providers/OpenID/OpenIDAuthorizationEndpointInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Owin.Security.Providers.OpenID -{ - public class OpenIDAuthorizationEndpointInfo - { - - public string Url { get; set; } - - } -} \ No newline at end of file diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj deleted file mode 100644 index eb17962..0000000 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ /dev/null @@ -1,491 +0,0 @@ - - - - - Debug - AnyCPU - {6AD9BA00-1330-426D-8BAE-2D3BC0D976E4} - Library - Properties - Owin.Security.Providers - Owin.Security.Providers - v4.5 - 512 - cbdbae43 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll - - - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - - - - - \ No newline at end of file diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings deleted file mode 100644 index 6c10e3b..0000000 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings +++ /dev/null @@ -1,7 +0,0 @@ - - True - True - True - True - True - True \ No newline at end of file diff --git a/Owin.Security.Providers/Properties/AssemblyInfo.cs b/Owin.Security.Providers/Properties/AssemblyInfo.cs deleted file mode 100644 index c9ef003..0000000 --- a/Owin.Security.Providers/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Owin.Security.Providers")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Owin.Security.Providers")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c745499f-213a-461d-9dfb-c46935ec44e9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.25.0.0")] -[assembly: AssemblyFileVersion("1.25.0.0")] diff --git a/Owin.Security.Providers/Reddit/RedditAuthenticationHandler.cs b/Owin.Security.Providers/Reddit/RedditAuthenticationHandler.cs deleted file mode 100644 index f8525e5..0000000 --- a/Owin.Security.Providers/Reddit/RedditAuthenticationHandler.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -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; -using Owin.Security.Providers.Reddit.Provider; - -namespace Owin.Security.Providers.Reddit -{ - public class RedditAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://ssl.reddit.com/api/v1/access_token"; - private const string UserInfoEndpoint = "https://oauth.reddit.com/api/v1/me"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public RedditAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("state", state)); - body.Add(new KeyValuePair("scope", string.Join(",", Options.Scope))); - var request = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); - request.Headers.Add("User-Agent", Options.UserAgent); - request.Content = new FormUrlEncodedContent(body); - - // Request the token - - HttpResponseMessage tokenResponse = - await httpClient.SendAsync(request); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string expires = (string) response.expires_in; - string refreshToken = (string) response.refresh_token; - - // Get the Reddit user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); - userRequest.Headers.Add("User-Agent", Options.UserAgent); - userRequest.Headers.Add("Authorization", "bearer " + Uri.EscapeDataString(accessToken) + ""); - HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new RedditAuthenticatedContext(Context, user, accessToken, expires, refreshToken); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!string.IsNullOrEmpty(context.Id)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.UserName)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:reddit:url", context.Link, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - context.Identity.AddClaim(new Claim("urn:reddit:accesstoken", context.AccessToken, XmlSchemaString, Options.AuthenticationType)); - } - context.Identity.AddClaim(new Claim("urn:reddit:overeighteen", context.OverEighteen.ToString())); - 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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://ssl.reddit.com/api/v1/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state) + - "&duration=permanent"; - - 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 RedditReturnEndpointContext(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/Slack/SlackAuthenticationHandler.cs b/Owin.Security.Providers/Slack/SlackAuthenticationHandler.cs deleted file mode 100644 index e0e424b..0000000 --- a/Owin.Security.Providers/Slack/SlackAuthenticationHandler.cs +++ /dev/null @@ -1,252 +0,0 @@ -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; -using Owin.Security.Providers.Slack.Provider; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Owin.Security.Providers.Slack -{ - public class SlackAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://slack.com/api/oauth.access"; - private const string UserInfoEndpoint = "https://slack.com/api/auth.test"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public SlackAuthenticationHandler(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 = values[0]; - } - 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); - } - - // Check for error - if (Request.Query.Get("error") != null) - 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("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - - string secret = Options.ClientId + ":" + Options.ClientSecret; - var secretBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret)); - - HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); - tokenRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", secretBase64); - tokenRequest.Content = new FormUrlEncodedContent(body); - - // Request the token - HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string scope = (string)response.scope; - - // Get the Slack user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "?token=" + Uri.EscapeDataString(accessToken)); - - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - userResponse.EnsureSuccessStatusCode(); - text = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new SlackAuthenticatedContext(Context, user, accessToken, scope); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!string.IsNullOrEmpty(context.UserId)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.UserName)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.TeamId)) - { - context.Identity.AddClaim(new Claim("urn:slack:teamid", context.TeamId, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.TeamName)) - { - context.Identity.AddClaim(new Claim("urn:slack:teamname", context.TeamName, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.TeamUrl)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Webpage, context.TeamUrl, 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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://slack.com/oauth/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state) + - "&team=" + Uri.EscapeDataString(Options.TeamId); - - 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 SlackReturnEndpointContext(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; - } - } -} diff --git a/Owin.Security.Providers/Spotify/SpotifyAuthenticationHandler.cs b/Owin.Security.Providers/Spotify/SpotifyAuthenticationHandler.cs deleted file mode 100644 index cd55e04..0000000 --- a/Owin.Security.Providers/Spotify/SpotifyAuthenticationHandler.cs +++ /dev/null @@ -1,243 +0,0 @@ -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; -using Owin.Security.Providers.Spotify.Provider; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Owin.Security.Providers.Spotify -{ - public class SpotifyAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://accounts.spotify.com/api/token"; - private const string UserInfoEndpoint = "https://api.spotify.com/v1/me"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public SpotifyAuthenticationHandler(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 = values[0]; - } - 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); - } - - // Check for error - if (Request.Query.Get("error") != null) - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - - string secret = Options.ClientId + ":" + Options.ClientSecret; - var secretBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret)); - - HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); - tokenRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", secretBase64); - tokenRequest.Content = new FormUrlEncodedContent(body); - - // Request the token - HttpResponseMessage tokenResponse = await httpClient.SendAsync(tokenRequest); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string refreshToken = (string)response.refresh_token; - string expiresIn = (string)response.expires_in; - - // Get the Spotify user - HttpRequestMessage graphRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); - graphRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - HttpResponseMessage graphResponse = await httpClient.SendAsync(graphRequest, Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - var context = new SpotifyAuthenticatedContext(Context, user, accessToken, refreshToken, expiresIn); - 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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.ProfilePicture)) - { - context.Identity.AddClaim(new Claim("urn:spotify:profilepicture", context.ProfilePicture, 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 scope = string.Join(" ", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://accounts.spotify.com/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - 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 SpotifyReturnEndpointContext(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/Steam/SteamAuthenticationHandler.cs b/Owin.Security.Providers/Steam/SteamAuthenticationHandler.cs deleted file mode 100644 index baf9d09..0000000 --- a/Owin.Security.Providers/Steam/SteamAuthenticationHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Owin.Logging; -using Newtonsoft.Json; -using Owin.Security.Providers.OpenID; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Text.RegularExpressions; - -namespace Owin.Security.Providers.Steam -{ - internal sealed class SteamAuthenticationHandler : OpenIDAuthenticationHandlerBase - { - private readonly Regex AccountIDRegex = new Regex(@"^http://steamcommunity\.com/openid/id/(7[0-9]{15,25})$", RegexOptions.Compiled); - - private const string UserInfoUri = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={0}&steamids={1}"; - - public SteamAuthenticationHandler(HttpClient httpClient, ILogger logger) - : base(httpClient, logger) - { } - - protected override void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary attributeExchangeProperties) - { - Match accountIDMatch = AccountIDRegex.Match(claimedID); - if (accountIDMatch.Success) - { - string accountID = accountIDMatch.Groups[1].Value; - - var getUserInfoTask = _httpClient.GetStringAsync(string.Format(UserInfoUri, Options.ApplicationKey, accountID)); - getUserInfoTask.Wait(); - string userInfoRaw = getUserInfoTask.Result; - dynamic userInfo = JsonConvert.DeserializeObject(userInfoRaw); - identity.AddClaim(new Claim(ClaimTypes.Name, (string)userInfo.response.players[0].personaname, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); - } - } - } -} diff --git a/Owin.Security.Providers/Twitch/TwitchAuthenticationHandler.cs b/Owin.Security.Providers/Twitch/TwitchAuthenticationHandler.cs deleted file mode 100644 index 2be4117..0000000 --- a/Owin.Security.Providers/Twitch/TwitchAuthenticationHandler.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -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.Twitch -{ - public class TwitchAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public TwitchAuthenticationHandler(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("grant_type", "authorization_code")); - 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 Twitch user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?oauth_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 TwitchAuthenticatedContext(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:Twitch:name", context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Link)) - { - context.Identity.AddClaim(new Claim("urn:Twitch: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); - - // comma separated - string scope = string.Join(" ", Options.Scope.Distinct()); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&response_type=" + "code" + - "&state=" + Uri.EscapeDataString(state); - - 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 TwitchReturnEndpointContext(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/ApiResponse.cs b/Owin.Security.Providers/Untappd/ApiResponse.cs deleted file mode 100644 index 4edaadf..0000000 --- a/Owin.Security.Providers/Untappd/ApiResponse.cs +++ /dev/null @@ -1,26 +0,0 @@ -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/VKontakte/VKontakteAuthenticationHandler.cs b/Owin.Security.Providers/VKontakte/VKontakteAuthenticationHandler.cs deleted file mode 100644 index c6deefd..0000000 --- a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationHandler.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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.Linq; -using Owin.Security.Providers.VKontakte.Provider; - -namespace Owin.Security.Providers.VKontakte -{ - public class VKontakteAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public VKontakteAuthenticationHandler(HttpClient httpClient, ILogger logger) - { - this.httpClient = httpClient; - this.logger = logger; - } - - public override async Task InvokeAsync() - { - return await InvokeReplyPathAsync(); - } - - 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 = string.Format("{0}{1}{2}{3}", Request.Scheme, Uri.SchemeDelimiter, Request.Host, Request.PathBase); - - string currentUri = string.Format("{0}{1}{2}", baseUri, Request.Path, Request.QueryString); - - string redirectUri = string.Format("{0}{1}", baseUri, Options.CallbackPath); - - AuthenticationProperties properties = challenge.Properties; - if (string.IsNullOrEmpty(properties.RedirectUri)) - { - properties.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = string.Format("{0}?client_id={1}&redirect_uri={2}&scope={3}&state={4}&display={5}", - Options.Endpoints.AuthorizationEndpoint, - Uri.EscapeDataString(Options.ClientId), - Uri.EscapeDataString(redirectUri), - Uri.EscapeDataString(scope), - Uri.EscapeDataString(state), - Uri.EscapeDataString(Options.Display)); - - Response.Redirect(authorizationEndpoint); - } - - return Task.FromResult(null); - } - - protected override async Task AuthenticateCoreAsync() - { - AuthenticationProperties properties = null; - - try - { - string authorizationCode = GetParameterValueFromRequest("code"); - string state = GetParameterValueFromRequest("state"); - - properties = Options.StateDataFormat.Unprotect(state); - if (properties == null) - { - return null; - } - - // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) - { - return new AuthenticationTicket(null, properties); - } - - JObject response = await GetAuthorizationToken(authorizationCode); - string accessToken = (string)response["access_token"]; - - JObject user = await GetUser(response, accessToken); - - VKontakteAuthenticatedContext context = CreateAuthenticatedContext(user, accessToken, 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); - } - - private string GetParameterValueFromRequest(string parameterName) - { - string value = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues(parameterName); - if (values != null && values.Count == 1) - { - value = values[0]; - } - return value; - } - - private VKontakteAuthenticatedContext CreateAuthenticatedContext(JObject user, string accessToken, - AuthenticationProperties properties) - { - var context = new VKontakteAuthenticatedContext(Context, user, accessToken) - { - 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)); - } - - context.Properties = properties; - return context; - } - - private async Task GetUser(JObject response, string accessToken) - { - int userId = (int)response["user_id"]; - - // Get the VK user - var userRequestUri = new Uri(string.Format("{0}?access_token={1}&user_id{2}", Options.Endpoints.UserInfoEndpoint, Uri.EscapeDataString(accessToken), userId)); - HttpResponseMessage userResponse = await httpClient.GetAsync(userRequestUri, Request.CallCancelled); - userResponse.EnsureSuccessStatusCode(); - - var userReposnseAsString = await userResponse.Content.ReadAsStringAsync(); - var user = JObject.Parse(userReposnseAsString)["response"]; - return (JObject)user[0]; - } - - private async Task GetAuthorizationToken(string authorizationCode) - { - string redirectUri = string.Format("{0}://{1}{2}{3}", Request.Scheme, Request.Host, Request.PathBase, Options.CallbackPath); - - // Build up the body for the token request - var body = new Dictionary - { - {"code", authorizationCode}, - {"redirect_uri", redirectUri}, - {"client_id", Options.ClientId}, - {"client_secret", Options.ClientSecret} - }; - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(Options.Endpoints.TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string tokenResponseAsString = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - JObject response = JObject.Parse(tokenResponseAsString); - return response; - } - - private async Task InvokeReplyPathAsync() - { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) - { - AuthenticationTicket ticket = await AuthenticateAsync(); - if (ticket == null) - { - logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new VKontakteReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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/Vimeo/VimeoAuthenticationHandler.cs b/Owin.Security.Providers/Vimeo/VimeoAuthenticationHandler.cs deleted file mode 100644 index 11dd159..0000000 --- a/Owin.Security.Providers/Vimeo/VimeoAuthenticationHandler.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Microsoft.Owin; -using Microsoft.Owin.Infrastructure; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Infrastructure; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Owin.Security.Providers.Vimeo -{ - public class VimeoAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://api.vimeo.com/oauth/access_token"; - private const string AuthorizationEndpoint = "https://api.vimeo.com/oauth/authorize"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public VimeoAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - - // Vimeo includes the user information in the response - var context = new VimeoAuthenticatedContext(Context, response.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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - AuthorizationEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - if (!string.IsNullOrWhiteSpace(Options.Scope)) - { - authorizationEndpoint += "&scope=" + Uri.EscapeDataString(Options.Scope); - } - - 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) - { - AuthenticationTicket ticket = await AuthenticateAsync(); - if (ticket == null) - { - logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new VimeoReturnEndpointContext(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/VisualStudio/Constants.cs b/Owin.Security.Providers/VisualStudio/Constants.cs deleted file mode 100644 index 9d32b17..0000000 --- a/Owin.Security.Providers/VisualStudio/Constants.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Owin.Security.Providers.VisualStudio { - internal static class Constants { - public const string DefaultAuthenticationType = "Visual Studio Online"; - } -} diff --git a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationHandler.cs b/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationHandler.cs deleted file mode 100644 index 94f44d3..0000000 --- a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationHandler.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System; -using System.Collections.Generic; -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.VisualStudio { - public class VisualStudioAuthenticationHandler : AuthenticationHandler { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public VisualStudioAuthenticationHandler(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 = values[0]; - } - 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 = "https://" + Request.Host; // Schema must be HTTPS - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; - - // Build up the body for the token request - var body = new List>(); - body.Add(new KeyValuePair("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); - body.Add(new KeyValuePair("client_assertion", Options.AppSecret)); - body.Add(new KeyValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")); - body.Add(new KeyValuePair("assertion", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - - // Request the token - var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = new FormUrlEncodedContent(body); - 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; - string refreshToken = (string)response.refresh_token; - int expiresIn = (int)response.expires_in; - - // Get the Visual Studio Online user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint); - userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", 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 VisualStudioAuthenticatedContext(Context, user, accessToken, expiresIn, refreshToken); - 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.Name)) { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Email)) { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Alias)) { - context.Identity.AddClaim(new Claim("urn:vso:alias", context.Alias, 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 = - "https" + //Schema must be HTTPS - 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); - - // space separated - string scope = string.Join(" ", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.AppId) + - "&response_type=Assertion" + - "&state=" + Uri.EscapeDataString(state) + - "&scope=" + Uri.EscapeDataString(scope) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri); - - 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 VisualStudioReturnEndpointContext(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; - } - } -} diff --git a/Owin.Security.Providers/Wargaming/WargamingAuthenticationHandler.cs b/Owin.Security.Providers/Wargaming/WargamingAuthenticationHandler.cs deleted file mode 100644 index 8bb5477..0000000 --- a/Owin.Security.Providers/Wargaming/WargamingAuthenticationHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Owin.Logging; -using Newtonsoft.Json; -using Owin.Security.Providers.OpenID; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Text.RegularExpressions; - -namespace Owin.Security.Providers.Wargaming -{ - internal sealed class WargamingAuthenticationHandler : OpenIDAuthenticationHandlerBase - { - private readonly Regex AccountIDRegex = new Regex(@"^https://na.wargaming.net/id/([0-9]{10}).*$", RegexOptions.Compiled); - - private const string UserInfoUri = "https://api.worldoftanks.com/wot/account/info/?application_id={0}&account_id={1}&fields=nickname"; - - public WargamingAuthenticationHandler(HttpClient httpClient, ILogger logger) - : base(httpClient, logger) - { - } - - protected override void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary attributeExchangeProperties) - { - Match accountIDMatch = AccountIDRegex.Match(claimedID); - if (accountIDMatch.Success) - { - string accountID = accountIDMatch.Groups[1].Value; - var getUserInfoTask = _httpClient.GetStringAsync(string.Format(UserInfoUri, Options.AppId, accountID)); - getUserInfoTask.Wait(); - string userInfoRaw = getUserInfoTask.Result; - dynamic userInfo = JsonConvert.DeserializeObject(userInfoRaw); - identity.AddClaim(new Claim(ClaimTypes.Name, (string)userInfo["data"][accountID].nickname, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); - } - } - } -} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs b/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs deleted file mode 100644 index 94dcd44..0000000 --- a/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -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.WordPress -{ - public class WordPressAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://public-api.wordpress.com/oauth2/token"; - private const string UserInfoEndpoint = "https://public-api.wordpress.com/rest/v1/me"; - private const string SiteInfoEndpoint = "https://public-api.wordpress.com/rest/v1/sites/"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public WordPressAuthenticationHandler(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 = values[0]; - } - 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("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); - - // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string blogId = (string)response.blog_id; - string blogUrl = (string)response.blog_url; - - // Get the Wordpress user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); - userRequest.Headers.Add("User-Agent", "OWIN OAuth Provider"); - userRequest.Headers.Add("Authorization", "BEARER " + accessToken); - HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); - graphResponse.EnsureSuccessStatusCode(); - text = await graphResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); - - // Get the site details - HttpRequestMessage siteRequest = new HttpRequestMessage(HttpMethod.Get, SiteInfoEndpoint + blogId); - siteRequest.Headers.Add("User-Agent", "OWIN OAuth Provider"); - siteRequest.Headers.Add("Authorization", "BEARER " + accessToken); - HttpResponseMessage siteResponse = await httpClient.SendAsync(siteRequest, Request.CallCancelled); - siteResponse.EnsureSuccessStatusCode(); - text = await siteResponse.Content.ReadAsStringAsync(); - JObject site = JObject.Parse(text); - - var context = new WordPressAuthenticatedContext(Context, user, site, accessToken, blogId, blogUrl); - 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.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Email)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://public-api.wordpress.com/oauth2/authorize" + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - 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 WordPressReturnEndpointContext(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/Yammer/YammerAuthenticationHandler.cs b/Owin.Security.Providers/Yammer/YammerAuthenticationHandler.cs deleted file mode 100644 index 84e23e4..0000000 --- a/Owin.Security.Providers/Yammer/YammerAuthenticationHandler.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -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; -using Owin.Security.Providers.Yammer.Provider; - -namespace Owin.Security.Providers.Yammer -{ - public class YammerAuthenticationHandler : AuthenticationHandler - { - private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private const string TokenEndpoint = "https://www.yammer.com/oauth2/access_token.json"; - private const string UserAuthenticationEndpoint = "https://www.yammer.com/dialog/oauth"; - - private readonly ILogger logger; - private readonly HttpClient httpClient; - - public YammerAuthenticationHandler(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 = values[0]; - } - values = query.GetValues("state"); - if (values != null && values.Count == 1) - { - state = values[0]; - } - - properties = Options.StateDataFormat.Unprotect(state); - if (properties == null) - { - return null; - } - if (code == null) - { - throw new Exception(query["error"] + " - " + query["error_description"]); - } - - // 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; - - string endPoint = - TokenEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&client_secret=" + Uri.EscapeDataString(Options.ClientSecret) + - "&code=" + Uri.EscapeDataString(code); - - // Request the token - HttpResponseMessage tokenResponse = await httpClient.GetAsync(endPoint); - tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); - - // Deserializes the token response - dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token.token; - - // Get the Yammer user - dynamic user = response.user; - var context = new YammerAuthenticatedContext(Context, user, accessToken); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - EnsureAcceptedNetwork(Options.AcceptedNetworks, context.Network); - - if (!string.IsNullOrEmpty(context.Id)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Name)) - { - context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.PrimaryEmail)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.PrimaryEmail, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.Url)) - { - context.Identity.AddClaim(new Claim(ClaimTypes.Uri, context.Url, XmlSchemaString, Options.AuthenticationType)); - } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - context.Identity.AddClaim(new Claim("urn:Yammer:accesstoken", context.AccessToken, 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - UserAuthenticationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - 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 YammerReturnEndpointContext(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; - } - - private void EnsureAcceptedNetwork(string[] validNetworks, string userNetwork) - { - if (validNetworks != null && validNetworks.Length > 0) - { - bool isValid = false; - foreach (string network in validNetworks) - { - if (userNetwork == network) - { - isValid = true; - break; - } - } - if (!isValid) throw new Exception("User is not in list of accepted networks"); - } - } - } -} \ No newline at end of file diff --git a/Owin.Security.Providers/app.config b/Owin.Security.Providers/app.config deleted file mode 100644 index c202238..0000000 --- a/Owin.Security.Providers/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/Owin.Security.Providers/packages.config b/Owin.Security.Providers/packages.config deleted file mode 100644 index 95a4345..0000000 --- a/Owin.Security.Providers/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/OwinOAuthProviders.sln b/OwinOAuthProviders.sln index 0f05d64..3cf5f2b 100644 --- a/OwinOAuthProviders.sln +++ b/OwinOAuthProviders.sln @@ -1,11 +1,96 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OwinOAuthProvidersDemo", "OwinOAuthProvidersDemo\OwinOAuthProvidersDemo.csproj", "{5A438007-0C90-4DAC-BAA1-54A32164067F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers", "Owin.Security.Providers\Owin.Security.Providers.csproj", "{6AD9BA00-1330-426D-8BAE-2D3BC0D976E4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISOnline", "src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj", "{8A49FAEF-D365-4D25-942C-1CAD03845A5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Asana", "src\Owin.Security.Providers.Asana\Owin.Security.Providers.Asana.csproj", "{F3E27220-1D8C-4037-94AA-7B7F4A12F351}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Backlog", "src\Owin.Security.Providers.Backlog\Owin.Security.Providers.Backlog.csproj", "{2DC03778-9EF1-466A-83EC-7D8422DECD23}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.BattleNet", "src\Owin.Security.Providers.BattleNet\Owin.Security.Providers.BattleNet.csproj", "{99A175DA-ADE4-436C-A272-C8AE44B7A086}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Bitbucket", "src\Owin.Security.Providers.Bitbucket\Owin.Security.Providers.Bitbucket.csproj", "{E5212FC7-ABCB-462F-9989-8E022DFFE43C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Buffer", "src\Owin.Security.Providers.Buffer\Owin.Security.Providers.Buffer.csproj", "{6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Cosign", "src\Owin.Security.Providers.Cosign\Owin.Security.Providers.Cosign.csproj", "{1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.DeviantArt", "src\Owin.Security.Providers.DeviantArt\Owin.Security.Providers.DeviantArt.csproj", "{FABD2E54-976D-41F5-8800-DEE58ACC027C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.DoYouBuzz", "src\Owin.Security.Providers.DoYouBuzz\Owin.Security.Providers.DoYouBuzz.csproj", "{4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Dropbox", "src\Owin.Security.Providers.Dropbox\Owin.Security.Providers.Dropbox.csproj", "{CEF697B1-3651-49E5-9060-65F2E26C039C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.EVEOnline", "src\Owin.Security.Providers.EVEOnline\Owin.Security.Providers.EVEOnline.csproj", "{F5DC23F4-5042-4024-9E34-ACA648602BA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Fitbit", "src\Owin.Security.Providers.Fitbit\Owin.Security.Providers.Fitbit.csproj", "{CA44D014-5A74-4749-A891-1F711FD3A266}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Flickr", "src\Owin.Security.Providers.Flickr\Owin.Security.Providers.Flickr.csproj", "{AF6CBEB8-5638-43D4-839E-C81F305960BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Foursquare", "src\Owin.Security.Providers.Foursquare\Owin.Security.Providers.Foursquare.csproj", "{8ACD9194-1EFE-4128-AC42-856D856332A4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.GitHub", "src\Owin.Security.Providers.GitHub\Owin.Security.Providers.GitHub.csproj", "{803F9EB7-029C-45AC-AB81-135E60D5BEAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Gitter", "src\Owin.Security.Providers.Gitter\Owin.Security.Providers.Gitter.csproj", "{42EC50EB-0C51-460C-93A4-1E007BF1F323}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.GooglePlus", "src\Owin.Security.Providers.GooglePlus\Owin.Security.Providers.GooglePlus.csproj", "{D3FEF959-0E0E-4F50-954C-F123A0B629DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.HealthGraph", "src\Owin.Security.Providers.HealthGraph\Owin.Security.Providers.HealthGraph.csproj", "{157BB715-29B2-4202-8A59-CCBACFCBEDD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Imgur", "src\Owin.Security.Providers.Imgur\Owin.Security.Providers.Imgur.csproj", "{101841D3-645E-4A44-AF8B-8AAA85CEEA4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Instagram", "src\Owin.Security.Providers.Instagram\Owin.Security.Providers.Instagram.csproj", "{041178C4-6131-4D68-9896-CE33124D83A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.LinkedIn", "src\Owin.Security.Providers.LinkedIn\Owin.Security.Providers.LinkedIn.csproj", "{9FA87825-30E9-48D7-AC4A-39E8F0C2777C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Onshape", "src\Owin.Security.Providers.Onshape\Owin.Security.Providers.Onshape.csproj", "{9FEC99F8-6F45-40A2-8200-85381434C79A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.OpenID", "src\Owin.Security.Providers.OpenID\Owin.Security.Providers.OpenID.csproj", "{90C152D7-9C66-4949-9998-C7CE48B593DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.PayPal", "src\Owin.Security.Providers.PayPal\Owin.Security.Providers.PayPal.csproj", "{F7129064-3DB7-4B79-81D3-80130D664E45}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Reddit", "src\Owin.Security.Providers.Reddit\Owin.Security.Providers.Reddit.csproj", "{D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Salesforce", "src\Owin.Security.Providers.Salesforce\Owin.Security.Providers.Salesforce.csproj", "{827A9D68-0DD4-4C5E-B763-8302FAEEDECC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Shopify", "src\Owin.Security.Providers.Shopify\Owin.Security.Providers.Shopify.csproj", "{67F12BFB-EB3A-4A86-B5DC-F4C066FDF792}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Slack", "src\Owin.Security.Providers.Slack\Owin.Security.Providers.Slack.csproj", "{3E6F293D-8500-428D-BDC9-27440CC91E16}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.SoundCloud", "src\Owin.Security.Providers.SoundCloud\Owin.Security.Providers.SoundCloud.csproj", "{2C959026-7058-4302-A6C4-DFD10A030585}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Spotify", "src\Owin.Security.Providers.Spotify\Owin.Security.Providers.Spotify.csproj", "{683B4041-A399-40CE-84B8-392F08A6805D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.StackExchange", "src\Owin.Security.Providers.StackExchange\Owin.Security.Providers.StackExchange.csproj", "{2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Steam", "src\Owin.Security.Providers.Steam\Owin.Security.Providers.Steam.csproj", "{312C4ED7-8CA1-4723-9203-ABC694DFDC7C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.TripIt", "src\Owin.Security.Providers.TripIt\Owin.Security.Providers.TripIt.csproj", "{B35E2616-DC00-48B4-BD58-7E23046257F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Twitch", "src\Owin.Security.Providers.Twitch\Owin.Security.Providers.Twitch.csproj", "{C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Untappd", "src\Owin.Security.Providers.Untappd\Owin.Security.Providers.Untappd.csproj", "{3E89ECA3-F4E7-4181-B26B-8250D5151044}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Vimeo", "src\Owin.Security.Providers.Vimeo\Owin.Security.Providers.Vimeo.csproj", "{98ECC703-D651-4EAD-A55D-AA3E903AE4D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.VisualStudio", "src\Owin.Security.Providers.VisualStudio\Owin.Security.Providers.VisualStudio.csproj", "{3B19FA31-DDFF-427F-9D73-F860DE74BBC2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.VKontakte", "src\Owin.Security.Providers.VKontakte\Owin.Security.Providers.VKontakte.csproj", "{32D70E31-3799-482A-AC7A-081FF9206FC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Wargaming", "src\Owin.Security.Providers.Wargaming\Owin.Security.Providers.Wargaming.csproj", "{AA72BFCE-8495-4A4D-988D-F8D490521776}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.WordPress", "src\Owin.Security.Providers.WordPress\Owin.Security.Providers.WordPress.csproj", "{0EDE8223-DD5F-4DB8-A98A-64B1F4591F48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Xing", "src\Owin.Security.Providers.Xing\Owin.Security.Providers.Xing.csproj", "{D497D8BD-6EF9-4C30-B195-B0DD153418D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Yahoo", "src\Owin.Security.Providers.Yahoo\Owin.Security.Providers.Yahoo.csproj", "{1765BEDB-9E4B-468C-BAF6-06784CDCED67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Yammer", "src\Owin.Security.Providers.Yammer\Owin.Security.Providers.Yammer.csproj", "{8D029A93-E687-4DDF-82B0-700EBBF477F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.OpenIDBase", "src\Owin.Security.Providers.OpenIDBase\Owin.Security.Providers.OpenIDBase.csproj", "{4FD7B873-1994-4990-AA40-C37060121494}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,10 +102,182 @@ Global {5A438007-0C90-4DAC-BAA1-54A32164067F}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A438007-0C90-4DAC-BAA1-54A32164067F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A438007-0C90-4DAC-BAA1-54A32164067F}.Release|Any CPU.Build.0 = Release|Any CPU - {6AD9BA00-1330-426D-8BAE-2D3BC0D976E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AD9BA00-1330-426D-8BAE-2D3BC0D976E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AD9BA00-1330-426D-8BAE-2D3BC0D976E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AD9BA00-1330-426D-8BAE-2D3BC0D976E4}.Release|Any CPU.Build.0 = Release|Any CPU + {8A49FAEF-D365-4D25-942C-1CAD03845A5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A49FAEF-D365-4D25-942C-1CAD03845A5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A49FAEF-D365-4D25-942C-1CAD03845A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A49FAEF-D365-4D25-942C-1CAD03845A5E}.Release|Any CPU.Build.0 = Release|Any CPU + {F3E27220-1D8C-4037-94AA-7B7F4A12F351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3E27220-1D8C-4037-94AA-7B7F4A12F351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3E27220-1D8C-4037-94AA-7B7F4A12F351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3E27220-1D8C-4037-94AA-7B7F4A12F351}.Release|Any CPU.Build.0 = Release|Any CPU + {2DC03778-9EF1-466A-83EC-7D8422DECD23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DC03778-9EF1-466A-83EC-7D8422DECD23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DC03778-9EF1-466A-83EC-7D8422DECD23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DC03778-9EF1-466A-83EC-7D8422DECD23}.Release|Any CPU.Build.0 = Release|Any CPU + {99A175DA-ADE4-436C-A272-C8AE44B7A086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99A175DA-ADE4-436C-A272-C8AE44B7A086}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99A175DA-ADE4-436C-A272-C8AE44B7A086}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99A175DA-ADE4-436C-A272-C8AE44B7A086}.Release|Any CPU.Build.0 = Release|Any CPU + {E5212FC7-ABCB-462F-9989-8E022DFFE43C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5212FC7-ABCB-462F-9989-8E022DFFE43C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5212FC7-ABCB-462F-9989-8E022DFFE43C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5212FC7-ABCB-462F-9989-8E022DFFE43C}.Release|Any CPU.Build.0 = Release|Any CPU + {6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5}.Release|Any CPU.Build.0 = Release|Any CPU + {1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF}.Release|Any CPU.Build.0 = Release|Any CPU + {FABD2E54-976D-41F5-8800-DEE58ACC027C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FABD2E54-976D-41F5-8800-DEE58ACC027C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FABD2E54-976D-41F5-8800-DEE58ACC027C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FABD2E54-976D-41F5-8800-DEE58ACC027C}.Release|Any CPU.Build.0 = Release|Any CPU + {4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912}.Release|Any CPU.Build.0 = Release|Any CPU + {CEF697B1-3651-49E5-9060-65F2E26C039C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEF697B1-3651-49E5-9060-65F2E26C039C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEF697B1-3651-49E5-9060-65F2E26C039C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEF697B1-3651-49E5-9060-65F2E26C039C}.Release|Any CPU.Build.0 = Release|Any CPU + {F5DC23F4-5042-4024-9E34-ACA648602BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5DC23F4-5042-4024-9E34-ACA648602BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5DC23F4-5042-4024-9E34-ACA648602BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5DC23F4-5042-4024-9E34-ACA648602BA0}.Release|Any CPU.Build.0 = Release|Any CPU + {CA44D014-5A74-4749-A891-1F711FD3A266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA44D014-5A74-4749-A891-1F711FD3A266}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA44D014-5A74-4749-A891-1F711FD3A266}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA44D014-5A74-4749-A891-1F711FD3A266}.Release|Any CPU.Build.0 = Release|Any CPU + {AF6CBEB8-5638-43D4-839E-C81F305960BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF6CBEB8-5638-43D4-839E-C81F305960BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF6CBEB8-5638-43D4-839E-C81F305960BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF6CBEB8-5638-43D4-839E-C81F305960BE}.Release|Any CPU.Build.0 = Release|Any CPU + {8ACD9194-1EFE-4128-AC42-856D856332A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ACD9194-1EFE-4128-AC42-856D856332A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ACD9194-1EFE-4128-AC42-856D856332A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ACD9194-1EFE-4128-AC42-856D856332A4}.Release|Any CPU.Build.0 = Release|Any CPU + {803F9EB7-029C-45AC-AB81-135E60D5BEAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {803F9EB7-029C-45AC-AB81-135E60D5BEAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {803F9EB7-029C-45AC-AB81-135E60D5BEAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {803F9EB7-029C-45AC-AB81-135E60D5BEAE}.Release|Any CPU.Build.0 = Release|Any CPU + {42EC50EB-0C51-460C-93A4-1E007BF1F323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42EC50EB-0C51-460C-93A4-1E007BF1F323}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42EC50EB-0C51-460C-93A4-1E007BF1F323}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42EC50EB-0C51-460C-93A4-1E007BF1F323}.Release|Any CPU.Build.0 = Release|Any CPU + {D3FEF959-0E0E-4F50-954C-F123A0B629DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3FEF959-0E0E-4F50-954C-F123A0B629DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3FEF959-0E0E-4F50-954C-F123A0B629DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3FEF959-0E0E-4F50-954C-F123A0B629DC}.Release|Any CPU.Build.0 = Release|Any CPU + {157BB715-29B2-4202-8A59-CCBACFCBEDD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {157BB715-29B2-4202-8A59-CCBACFCBEDD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {157BB715-29B2-4202-8A59-CCBACFCBEDD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {157BB715-29B2-4202-8A59-CCBACFCBEDD3}.Release|Any CPU.Build.0 = Release|Any CPU + {101841D3-645E-4A44-AF8B-8AAA85CEEA4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {101841D3-645E-4A44-AF8B-8AAA85CEEA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {101841D3-645E-4A44-AF8B-8AAA85CEEA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {101841D3-645E-4A44-AF8B-8AAA85CEEA4E}.Release|Any CPU.Build.0 = Release|Any CPU + {041178C4-6131-4D68-9896-CE33124D83A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {041178C4-6131-4D68-9896-CE33124D83A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {041178C4-6131-4D68-9896-CE33124D83A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {041178C4-6131-4D68-9896-CE33124D83A0}.Release|Any CPU.Build.0 = Release|Any CPU + {9FA87825-30E9-48D7-AC4A-39E8F0C2777C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FA87825-30E9-48D7-AC4A-39E8F0C2777C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FA87825-30E9-48D7-AC4A-39E8F0C2777C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FA87825-30E9-48D7-AC4A-39E8F0C2777C}.Release|Any CPU.Build.0 = Release|Any CPU + {9FEC99F8-6F45-40A2-8200-85381434C79A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FEC99F8-6F45-40A2-8200-85381434C79A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FEC99F8-6F45-40A2-8200-85381434C79A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FEC99F8-6F45-40A2-8200-85381434C79A}.Release|Any CPU.Build.0 = Release|Any CPU + {90C152D7-9C66-4949-9998-C7CE48B593DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90C152D7-9C66-4949-9998-C7CE48B593DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90C152D7-9C66-4949-9998-C7CE48B593DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90C152D7-9C66-4949-9998-C7CE48B593DE}.Release|Any CPU.Build.0 = Release|Any CPU + {F7129064-3DB7-4B79-81D3-80130D664E45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7129064-3DB7-4B79-81D3-80130D664E45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7129064-3DB7-4B79-81D3-80130D664E45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7129064-3DB7-4B79-81D3-80130D664E45}.Release|Any CPU.Build.0 = Release|Any CPU + {D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD}.Release|Any CPU.Build.0 = Release|Any CPU + {827A9D68-0DD4-4C5E-B763-8302FAEEDECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827A9D68-0DD4-4C5E-B763-8302FAEEDECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827A9D68-0DD4-4C5E-B763-8302FAEEDECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827A9D68-0DD4-4C5E-B763-8302FAEEDECC}.Release|Any CPU.Build.0 = Release|Any CPU + {67F12BFB-EB3A-4A86-B5DC-F4C066FDF792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F12BFB-EB3A-4A86-B5DC-F4C066FDF792}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F12BFB-EB3A-4A86-B5DC-F4C066FDF792}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F12BFB-EB3A-4A86-B5DC-F4C066FDF792}.Release|Any CPU.Build.0 = Release|Any CPU + {3E6F293D-8500-428D-BDC9-27440CC91E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E6F293D-8500-428D-BDC9-27440CC91E16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E6F293D-8500-428D-BDC9-27440CC91E16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E6F293D-8500-428D-BDC9-27440CC91E16}.Release|Any CPU.Build.0 = Release|Any CPU + {2C959026-7058-4302-A6C4-DFD10A030585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C959026-7058-4302-A6C4-DFD10A030585}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C959026-7058-4302-A6C4-DFD10A030585}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C959026-7058-4302-A6C4-DFD10A030585}.Release|Any CPU.Build.0 = Release|Any CPU + {683B4041-A399-40CE-84B8-392F08A6805D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {683B4041-A399-40CE-84B8-392F08A6805D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {683B4041-A399-40CE-84B8-392F08A6805D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {683B4041-A399-40CE-84B8-392F08A6805D}.Release|Any CPU.Build.0 = Release|Any CPU + {2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A}.Release|Any CPU.Build.0 = Release|Any CPU + {312C4ED7-8CA1-4723-9203-ABC694DFDC7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {312C4ED7-8CA1-4723-9203-ABC694DFDC7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {312C4ED7-8CA1-4723-9203-ABC694DFDC7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {312C4ED7-8CA1-4723-9203-ABC694DFDC7C}.Release|Any CPU.Build.0 = Release|Any CPU + {B35E2616-DC00-48B4-BD58-7E23046257F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B35E2616-DC00-48B4-BD58-7E23046257F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B35E2616-DC00-48B4-BD58-7E23046257F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B35E2616-DC00-48B4-BD58-7E23046257F1}.Release|Any CPU.Build.0 = Release|Any CPU + {C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48}.Release|Any CPU.Build.0 = Release|Any CPU + {3E89ECA3-F4E7-4181-B26B-8250D5151044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E89ECA3-F4E7-4181-B26B-8250D5151044}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E89ECA3-F4E7-4181-B26B-8250D5151044}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E89ECA3-F4E7-4181-B26B-8250D5151044}.Release|Any CPU.Build.0 = Release|Any CPU + {98ECC703-D651-4EAD-A55D-AA3E903AE4D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98ECC703-D651-4EAD-A55D-AA3E903AE4D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98ECC703-D651-4EAD-A55D-AA3E903AE4D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98ECC703-D651-4EAD-A55D-AA3E903AE4D7}.Release|Any CPU.Build.0 = Release|Any CPU + {3B19FA31-DDFF-427F-9D73-F860DE74BBC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B19FA31-DDFF-427F-9D73-F860DE74BBC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B19FA31-DDFF-427F-9D73-F860DE74BBC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B19FA31-DDFF-427F-9D73-F860DE74BBC2}.Release|Any CPU.Build.0 = Release|Any CPU + {32D70E31-3799-482A-AC7A-081FF9206FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32D70E31-3799-482A-AC7A-081FF9206FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32D70E31-3799-482A-AC7A-081FF9206FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32D70E31-3799-482A-AC7A-081FF9206FC3}.Release|Any CPU.Build.0 = Release|Any CPU + {AA72BFCE-8495-4A4D-988D-F8D490521776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA72BFCE-8495-4A4D-988D-F8D490521776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA72BFCE-8495-4A4D-988D-F8D490521776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA72BFCE-8495-4A4D-988D-F8D490521776}.Release|Any CPU.Build.0 = Release|Any CPU + {0EDE8223-DD5F-4DB8-A98A-64B1F4591F48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EDE8223-DD5F-4DB8-A98A-64B1F4591F48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EDE8223-DD5F-4DB8-A98A-64B1F4591F48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EDE8223-DD5F-4DB8-A98A-64B1F4591F48}.Release|Any CPU.Build.0 = Release|Any CPU + {D497D8BD-6EF9-4C30-B195-B0DD153418D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D497D8BD-6EF9-4C30-B195-B0DD153418D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D497D8BD-6EF9-4C30-B195-B0DD153418D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D497D8BD-6EF9-4C30-B195-B0DD153418D6}.Release|Any CPU.Build.0 = Release|Any CPU + {1765BEDB-9E4B-468C-BAF6-06784CDCED67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1765BEDB-9E4B-468C-BAF6-06784CDCED67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1765BEDB-9E4B-468C-BAF6-06784CDCED67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1765BEDB-9E4B-468C-BAF6-06784CDCED67}.Release|Any CPU.Build.0 = Release|Any CPU + {8D029A93-E687-4DDF-82B0-700EBBF477F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D029A93-E687-4DDF-82B0-700EBBF477F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D029A93-E687-4DDF-82B0-700EBBF477F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D029A93-E687-4DDF-82B0-700EBBF477F7}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD7B873-1994-4990-AA40-C37060121494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD7B873-1994-4990-AA40-C37060121494}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD7B873-1994-4990-AA40-C37060121494}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD7B873-1994-4990-AA40-C37060121494}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OwinOAuthProviders.sln.DotSettings b/OwinOAuthProviders.sln.DotSettings new file mode 100644 index 0000000..c60ebf8 --- /dev/null +++ b/OwinOAuthProviders.sln.DotSettings @@ -0,0 +1,8 @@ + + GIS + True + True + True + True + True + True \ No newline at end of file diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf index c9aedfb..6e1f5ae 100644 Binary files a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf and b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf differ diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf index 04b6f6f..6a8712c 100644 Binary files a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf and b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf differ diff --git a/OwinOAuthProvidersDemo/App_Start/BundleConfig.cs b/OwinOAuthProvidersDemo/App_Start/BundleConfig.cs index 1be4f14..0920fba 100644 --- a/OwinOAuthProvidersDemo/App_Start/BundleConfig.cs +++ b/OwinOAuthProvidersDemo/App_Start/BundleConfig.cs @@ -1,5 +1,4 @@ -using System.Web; -using System.Web.Optimization; +using System.Web.Optimization; namespace OwinOAuthProvidersDemo { diff --git a/OwinOAuthProvidersDemo/App_Start/FilterConfig.cs b/OwinOAuthProvidersDemo/App_Start/FilterConfig.cs index f775703..9df89de 100644 --- a/OwinOAuthProvidersDemo/App_Start/FilterConfig.cs +++ b/OwinOAuthProvidersDemo/App_Start/FilterConfig.cs @@ -1,5 +1,4 @@ -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; namespace OwinOAuthProvidersDemo { diff --git a/OwinOAuthProvidersDemo/App_Start/RouteConfig.cs b/OwinOAuthProvidersDemo/App_Start/RouteConfig.cs index b4b32ed..bd1d97b 100644 --- a/OwinOAuthProvidersDemo/App_Start/RouteConfig.cs +++ b/OwinOAuthProvidersDemo/App_Start/RouteConfig.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.Routing; namespace OwinOAuthProvidersDemo diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs index e3afa95..75ddad9 100755 --- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs +++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs @@ -1,49 +1,7 @@ -using System; -using System.Security.Claims; -using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; -using Owin.Security.Providers.Asana; -using Owin.Security.Providers.ArcGISOnline; -using Owin.Security.Providers.BattleNet; -using Owin.Security.Providers.Buffer; -using Owin.Security.Providers.DeviantArt; -using Owin.Security.Providers.Dropbox; -using Owin.Security.Providers.EveOnline; -using Owin.Security.Providers.Flickr; -using Owin.Security.Providers.Foursquare; -using Owin.Security.Providers.GitHub; -using Owin.Security.Providers.Gitter; -using Owin.Security.Providers.GooglePlus; -using Owin.Security.Providers.GooglePlus.Provider; -using Owin.Security.Providers.HealthGraph; -using Owin.Security.Providers.Imgur; -using Owin.Security.Providers.Instagram; -using Owin.Security.Providers.LinkedIn; -using Owin.Security.Providers.OpenID; -using Owin.Security.Providers.PayPal; -using Owin.Security.Providers.Reddit; -using Owin.Security.Providers.Salesforce; -using Owin.Security.Providers.Slack; -using Owin.Security.Providers.SoundCloud; -using Owin.Security.Providers.Spotify; -using Owin.Security.Providers.StackExchange; -using Owin.Security.Providers.Steam; -using Owin.Security.Providers.Shopify; -using Owin.Security.Providers.TripIt; -using Owin.Security.Providers.Twitch; -using Owin.Security.Providers.Untappd; -using Owin.Security.Providers.Wargaming; -using Owin.Security.Providers.WordPress; -using Owin.Security.Providers.Yahoo; -using Owin.Security.Providers.Backlog; -using Owin.Security.Providers.DoYouBuzz; -using Owin.Security.Providers.Vimeo; -using Owin.Security.Providers.Fitbit; -using Owin.Security.Providers.Onshape; -using Owin.Security.Providers.VKontakte; -using Owin.Security.Providers.Xing; namespace OwinOAuthProvidersDemo { diff --git a/OwinOAuthProvidersDemo/Controllers/AccountController.cs b/OwinOAuthProvidersDemo/Controllers/AccountController.cs index 5813dca..83a640f 100644 --- a/OwinOAuthProvidersDemo/Controllers/AccountController.cs +++ b/OwinOAuthProvidersDemo/Controllers/AccountController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity; @@ -137,7 +133,7 @@ namespace OwinOAuthProvidersDemo.Controllers [ValidateAntiForgeryToken] public async Task Manage(ManageUserViewModel model) { - bool hasPassword = HasPassword(); + var hasPassword = HasPassword(); ViewBag.HasLocalPassword = hasPassword; ViewBag.ReturnUrl = Url.Action("Manage"); if (hasPassword) @@ -415,12 +411,12 @@ namespace OwinOAuthProvidersDemo.Controllers var properties = new AuthenticationProperties() { RedirectUri = RedirectUri }; if (UserId != null) { - properties.Dictionary[XsrfKey] = this.UserId; + properties.Dictionary[XsrfKey] = UserId; } - if (!string.IsNullOrWhiteSpace(this.ShopName)) + if (!string.IsNullOrWhiteSpace(ShopName)) { - properties.Dictionary[ShopNameKey] = this.ShopName; + properties.Dictionary[ShopNameKey] = ShopName; } context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); diff --git a/OwinOAuthProvidersDemo/Controllers/HomeController.cs b/OwinOAuthProvidersDemo/Controllers/HomeController.cs index ded1b15..972d639 100644 --- a/OwinOAuthProvidersDemo/Controllers/HomeController.cs +++ b/OwinOAuthProvidersDemo/Controllers/HomeController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; namespace OwinOAuthProvidersDemo.Controllers { diff --git a/OwinOAuthProvidersDemo/Global.asax.cs b/OwinOAuthProvidersDemo/Global.asax.cs index 4cc7306..6190f8f 100644 --- a/OwinOAuthProvidersDemo/Global.asax.cs +++ b/OwinOAuthProvidersDemo/Global.asax.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; diff --git a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj index e038106..bbaaf3f 100644 --- a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj +++ b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj @@ -20,6 +20,7 @@ enabled disabled false + true @@ -40,69 +41,77 @@ - False ..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll + True - False ..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll + True - False ..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll + True - False ..\packages\Microsoft.AspNet.Identity.Core.2.1.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + True - False ..\packages\Microsoft.AspNet.Identity.EntityFramework.2.1.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll + True - False ..\packages\Microsoft.AspNet.Identity.Owin.2.1.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll + True - False ..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll + True - False ..\packages\Microsoft.Owin.Host.SystemWeb.2.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True - False ..\packages\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll + True - False ..\packages\Microsoft.Owin.Security.Cookies.2.1.0\lib\net45\Microsoft.Owin.Security.Cookies.dll + True - False ..\packages\Microsoft.Owin.Security.Facebook.2.1.0\lib\net45\Microsoft.Owin.Security.Facebook.dll + True - False ..\packages\Microsoft.Owin.Security.Google.2.1.0\lib\net45\Microsoft.Owin.Security.Google.dll + True - False ..\packages\Microsoft.Owin.Security.MicrosoftAccount.2.1.0\lib\net45\Microsoft.Owin.Security.MicrosoftAccount.dll + True - False ..\packages\Microsoft.Owin.Security.OAuth.2.1.0\lib\net45\Microsoft.Owin.Security.OAuth.dll + True - False ..\packages\Microsoft.Owin.Security.Twitter.2.1.0\lib\net45\Microsoft.Owin.Security.Twitter.dll + True + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True - False ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True @@ -114,32 +123,32 @@ - False ..\packages\Microsoft.AspNet.WebPages.3.2.0\lib\net45\System.Web.Helpers.dll + True - False ..\packages\Microsoft.AspNet.Mvc.5.2.0\lib\net45\System.Web.Mvc.dll + True - False ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll + True - False ..\packages\Microsoft.AspNet.Razor.3.2.0\lib\net45\System.Web.Razor.dll + True - False ..\packages\Microsoft.AspNet.WebPages.3.2.0\lib\net45\System.Web.WebPages.dll + True - False ..\packages\Microsoft.AspNet.WebPages.3.2.0\lib\net45\System.Web.WebPages.Deployment.dll + True - False ..\packages\Microsoft.AspNet.WebPages.3.2.0\lib\net45\System.Web.WebPages.Razor.dll + True @@ -150,22 +159,13 @@ - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False ..\packages\WebGrease.1.6.0\lib\WebGrease.dll - - - - - ..\packages\Owin.1.0\lib\net40\Owin.dll + True @@ -195,14 +195,13 @@ - + - @@ -245,13 +244,9 @@ - - - {6ad9ba00-1330-426d-8bae-2d3bc0d976e4} - Owin.Security.Providers - + 10.0 diff --git a/OwinOAuthProvidersDemo/Project_Readme.html b/OwinOAuthProvidersDemo/Project_Readme.html deleted file mode 100644 index cb9e793..0000000 --- a/OwinOAuthProvidersDemo/Project_Readme.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Your ASP.NET application - - - - - - -
-
-

This application consists of:

-
    -
  • Sample pages showing basic nav between Home, About, and Contact
  • -
  • Theming using Bootstrap
  • -
  • Authentication, if selected, shows how to register and sign in
  • -
  • ASP.NET features managed using NuGet
  • -
-
- - - - - -
-

Get help

- -
-
- - - \ No newline at end of file diff --git a/OwinOAuthProvidersDemo/Properties/AssemblyInfo.cs b/OwinOAuthProvidersDemo/Properties/AssemblyInfo.cs index bbee314..6155de8 100644 --- a/OwinOAuthProvidersDemo/Properties/AssemblyInfo.cs +++ b/OwinOAuthProvidersDemo/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/OwinOAuthProvidersDemo/Scripts/_references.js b/OwinOAuthProvidersDemo/Scripts/_references.js index c5c11ee..c93550e 100644 --- a/OwinOAuthProvidersDemo/Scripts/_references.js +++ b/OwinOAuthProvidersDemo/Scripts/_references.js @@ -1,9 +1,10 @@ /// -/// +/// +/// +/// /// -/// -/// +/// /// /// -/// -/// +/// +/// diff --git a/OwinOAuthProvidersDemo/Scripts/modernizr-2.6.2.js b/OwinOAuthProvidersDemo/Scripts/modernizr-2.6.2.js index cbfe1f3..6c2bea3 100644 --- a/OwinOAuthProvidersDemo/Scripts/modernizr-2.6.2.js +++ b/OwinOAuthProvidersDemo/Scripts/modernizr-2.6.2.js @@ -49,7 +49,7 @@ window.Modernizr = (function( window, document, undefined ) { var version = '2.6.2', - Modernizr = {}, + modernizr = {}, /*>>cssclasses*/ // option for enabling the HTML classes to be added @@ -207,7 +207,7 @@ window.Modernizr = (function( window, document, undefined ) { // ... isEventSupported = (function() { - var TAGNAMES = { + var tagnames = { 'select': 'input', 'change': 'input', 'submit': 'form', 'reset': 'form', 'error': 'img', 'load': 'img', 'abort': 'img' @@ -215,7 +215,7 @@ window.Modernizr = (function( window, document, undefined ) { function isEventSupported( eventName, element ) { - element = element || document.createElement(TAGNAMES[eventName] || 'div'); + element = element || document.createElement(tagnames[eventName] || 'div'); eventName = 'on' + eventName; // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those @@ -248,11 +248,11 @@ window.Modernizr = (function( window, document, undefined ) { // TODO :: Add flag for hasownprop ? didn't last time // hasOwnProperty shim by kangax needed for Safari 2.0 support - _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; + hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; - if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { + if ( !is(hasOwnProperty, 'undefined') && !is(hasOwnProperty.call, 'undefined') ) { hasOwnProp = function (object, property) { - return _hasOwnProperty.call(object, property); + return hasOwnProperty.call(object, property); }; } else { @@ -278,9 +278,9 @@ window.Modernizr = (function( window, document, undefined ) { if (this instanceof bound) { - var F = function(){}; - F.prototype = target.prototype; - var self = new F(); + var f = function(){}; + f.prototype = target.prototype; + var self = new f(); var result = target.apply( self, @@ -370,7 +370,7 @@ window.Modernizr = (function( window, document, undefined ) { * testDOMProps is a generic DOM property test; if a browser supports * a certain property, it won't return undefined for it. */ - function testDOMProps( props, obj, elem ) { + function testDomProps( props, obj, elem ) { for ( var i in props ) { var item = obj[props[i]]; if ( item !== undefined) { @@ -410,7 +410,7 @@ window.Modernizr = (function( window, document, undefined ) { // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) } else { props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); - return testDOMProps(props, prefixed, elem); + return testDomProps(props, prefixed, elem); } } /*>>testallprops*/ @@ -445,7 +445,7 @@ window.Modernizr = (function( window, document, undefined ) { }; tests['canvastext'] = function() { - return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); + return !!(modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); }; // webk.it/70117 is tracking a legit WebGL feature detect proposal @@ -881,7 +881,7 @@ window.Modernizr = (function( window, document, undefined ) { // Only input placeholder is tested while textarea's placeholder is not. // Currently Safari 4 and Opera 11 have support only for the input placeholder // Both tests are available in feature-detects/forms-placeholder.js - Modernizr['input'] = (function( props ) { + modernizr['input'] = (function( props ) { for ( var i = 0, len = props.length; i < len; i++ ) { attrs[ props[i] ] = !!(props[i] in inputElem); } @@ -901,7 +901,7 @@ window.Modernizr = (function( window, document, undefined ) { // containing each input type with its corresponding true/false value // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/ - Modernizr['inputtypes'] = (function(props) { + modernizr['inputtypes'] = (function(props) { for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { @@ -969,15 +969,15 @@ window.Modernizr = (function( window, document, undefined ) { // then based on that boolean, define an appropriate className // and push it into an array of classes we'll join later. featureName = feature.toLowerCase(); - Modernizr[featureName] = tests[feature](); + modernizr[featureName] = tests[feature](); - classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); + classes.push((modernizr[featureName] ? '' : 'no-') + featureName); } } /*>>webforms*/ // input tests need to run. - Modernizr.input || webforms(); + modernizr.input || webforms(); /*>>webforms*/ @@ -989,24 +989,24 @@ window.Modernizr = (function( window, document, undefined ) { * @param feature - String naming the feature * @param test - Function returning true if feature is supported, false if not */ - Modernizr.addTest = function ( feature, test ) { + modernizr.addTest = function ( feature, test ) { if ( typeof feature == 'object' ) { for ( var key in feature ) { if ( hasOwnProp( feature, key ) ) { - Modernizr.addTest( key, feature[ key ] ); + modernizr.addTest( key, feature[ key ] ); } } } else { feature = feature.toLowerCase(); - if ( Modernizr[feature] !== undefined ) { + if ( modernizr[feature] !== undefined ) { // we're going to quit if you're trying to overwrite an existing test // if we were to allow it, we'd do this: // var re = new RegExp("\\b(no-)?" + feature + "\\b"); // docElement.className = docElement.className.replace( re, '' ); // but, no rly, stuff 'em. - return Modernizr; + return modernizr; } test = typeof test == 'function' ? test() : test; @@ -1014,11 +1014,11 @@ window.Modernizr = (function( window, document, undefined ) { if (typeof enableClasses !== "undefined" && enableClasses) { docElement.className += ' ' + (test ? '' : 'no-') + feature; } - Modernizr[feature] = test; + modernizr[feature] = test; } - return Modernizr; // allow chaining. + return modernizr; // allow chaining. }; @@ -1046,7 +1046,7 @@ window.Modernizr = (function( window, document, undefined ) { var expando = '_html5shiv'; /** The id for the the documents expando */ - var expanID = 0; + var expanId = 0; /** Cached data for each document */ var expandoData = {}; @@ -1115,9 +1115,9 @@ window.Modernizr = (function( window, document, undefined ) { var data = expandoData[ownerDocument[expando]]; if (!data) { data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; + expanId++; + ownerDocument[expando] = expanId; + expandoData[expanId] = data; } return data; } @@ -1318,15 +1318,15 @@ window.Modernizr = (function( window, document, undefined ) { /*>>shiv*/ // Assign private properties to the return object with prefix - Modernizr._version = version; + modernizr._version = version; // expose these for the plugin API. Look in the source for how to join() them against your input /*>>prefixes*/ - Modernizr._prefixes = prefixes; + modernizr._prefixes = prefixes; /*>>prefixes*/ /*>>domprefixes*/ - Modernizr._domPrefixes = domPrefixes; - Modernizr._cssomPrefixes = cssomPrefixes; + modernizr._domPrefixes = domPrefixes; + modernizr._cssomPrefixes = cssomPrefixes; /*>>domprefixes*/ /*>>mq*/ @@ -1338,20 +1338,20 @@ window.Modernizr = (function( window, document, undefined ) { // Modernizr.mq('(min-width:0)') // usage: // Modernizr.mq('only screen and (max-width:768)') - Modernizr.mq = testMediaQuery; + modernizr.mq = testMediaQuery; /*>>mq*/ /*>>hasevent*/ // Modernizr.hasEvent() detects support for a given event, with an optional element to test on // Modernizr.hasEvent('gesturestart', elem) - Modernizr.hasEvent = isEventSupported; + modernizr.hasEvent = isEventSupported; /*>>hasevent*/ /*>>testprop*/ // Modernizr.testProp() investigates whether a given style property is recognized // Note that the property names must be provided in the camelCase variant. // Modernizr.testProp('pointerEvents') - Modernizr.testProp = function(prop){ + modernizr.testProp = function(prop){ return testProps([prop]); }; /*>>testprop*/ @@ -1361,14 +1361,14 @@ window.Modernizr = (function( window, document, undefined ) { // or any of its vendor-prefixed variants, is recognized // Note that the property names must be provided in the camelCase variant. // Modernizr.testAllProps('boxSizing') - Modernizr.testAllProps = testPropsAll; + modernizr.testAllProps = testPropsAll; /*>>testallprops*/ /*>>teststyles*/ // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) - Modernizr.testStyles = injectElementWithStyles; + modernizr.testStyles = injectElementWithStyles; /*>>teststyles*/ @@ -1392,7 +1392,7 @@ window.Modernizr = (function( window, document, undefined ) { // }, // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; - Modernizr.prefixed = function(prop, obj, elem){ + modernizr.prefixed = function(prop, obj, elem){ if(!obj) { return testPropsAll(prop, 'pfx'); } else { @@ -1411,6 +1411,6 @@ window.Modernizr = (function( window, document, undefined ) { (enableClasses ? ' js ' + classes.join(' ') : ''); /*>>cssclasses*/ - return Modernizr; + return modernizr; })(this, this.document); diff --git a/OwinOAuthProvidersDemo/Views/Account/Manage.cshtml b/OwinOAuthProvidersDemo/Views/Account/Manage.cshtml index a798d39..8f9c776 100644 --- a/OwinOAuthProvidersDemo/Views/Account/Manage.cshtml +++ b/OwinOAuthProvidersDemo/Views/Account/Manage.cshtml @@ -1,6 +1,4 @@ -@using OwinOAuthProvidersDemo.Models; -@using Microsoft.AspNet.Identity; -@{ +@{ ViewBag.Title = "Manage Account"; } diff --git a/README.md b/README.md index 6526a51..378079c 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,14 @@ For above listed provider implementation guide, visit Jerrie Pelser's blog - [Be To use these providers you will need to install the ```Owin.Security.Providers``` NuGet package. ``` -PM> Install-Package Owin.Security.Providers +PM> Install-Package Owin.Security.Providers.* ``` +Where * is the name of the provider you need e.g.: +``` +PM> Install-Package Owin.Security.Providers.GitHub +``` +I haven't published all of the providers yet as of 4/12/2016. + ## Contributions @@ -82,6 +88,7 @@ A big thanks goes out to all these contributors without whom this would not have * Jaspalsinh Chauhan (https://github.com/jsinh) * Jason Loeffler (https://github.com/jmloeffler) * Ben Foster (https://github.com/benfoster) +* Jonathan Peterson (https://github.com/eonasdan) For most accurate and up to date list of contributors please see https://github.com/RockstarLabs/OwinOAuthProviders/graphs/contributors diff --git a/nuget/NuGet.exe b/nuget/NuGet.exe new file mode 100644 index 0000000..9f8781d Binary files /dev/null and b/nuget/NuGet.exe differ diff --git a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs rename to src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs index d5d6367..874c774 100644 --- a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.ArcGISOnline ArcGISOnlineAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(ArcGISOnlineAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs new file mode 100644 index 0000000..2aff21e --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationHandler.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; + +namespace Owin.Security.Providers.ArcGISOnline +{ + public class ArcGISOnlineAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public ArcGISOnlineAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + requestMessage.Content = new FormUrlEncodedContent(body); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the ArcGISOnline user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?f=json&token=" + Uri.EscapeDataString(accessToken)); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JsonConvert.DeserializeObject(text); + + var context = new ArcGISOnlineAuthenticatedContext(Context, user, accessToken) + { + 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:ArcGISOnline:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:ArcGISOnline: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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + var properties=challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + GenerateCorrelationId(properties); + var state = Options.StateDataFormat.Protect(properties); + // comma separated + var scope = string.Join(",", Options.Scope); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&response_type=" + Uri.EscapeDataString(scope) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new ArcGISOnlineReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs index 0460921..d5a9807 100644 --- a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.ArcGISOnline { public class ArcGISOnlineAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public ArcGISOnlineAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, ArcGISOnlineAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new ArcGISOnlineAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (ArcGISOnlineAuthenticationMiddleware).FullName, Options.AuthenticationType, "v2"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin ArcGISOnline middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin ArcGISOnline middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.ArcGISOnline /// protected override AuthenticationHandler CreateHandler() { - return new ArcGISOnlineAuthenticationHandler(httpClient, logger); + return new ArcGISOnlineAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(ArcGISOnlineAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(ArcGISOnlineAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs similarity index 98% rename from Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs rename to src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs index fc4da33..8d1ea9a 100644 --- a/Owin.Security.Providers/ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/ArcGISOnlineAuthenticationOptions.cs @@ -123,8 +123,7 @@ namespace Owin.Security.Providers.ArcGISOnline /// /// Initializes a new /// - public ArcGISOnlineAuthenticationOptions() - : base("ArcGIS Online") + public ArcGISOnlineAuthenticationOptions() : base("ArcGIS Online") { Caption = Constants.DefaultAuthenticationType; CallbackPath = new PathString("/signin-arcgis-online"); diff --git a/Owin.Security.Providers/ArcGISOnline/Constants.cs b/src/Owin.Security.Providers.ArcGISOnline/Constants.cs similarity index 57% rename from Owin.Security.Providers/ArcGISOnline/Constants.cs rename to src/Owin.Security.Providers.ArcGISOnline/Constants.cs index 4134be6..30e3e48 100644 --- a/Owin.Security.Providers/ArcGISOnline/Constants.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/Constants.cs @@ -2,6 +2,6 @@ { internal static class Constants { - public const string DefaultAuthenticationType = "ArcGIS Online"; + internal const string DefaultAuthenticationType = "ArcGIS Online"; } } \ No newline at end of file diff --git a/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.csproj b/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.csproj new file mode 100644 index 0000000..9aa2085 --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {8A49FAEF-D365-4D25-942C-1CAD03845A5E} + Library + Properties + Owin.Security.Providers.ArcGISOnline + Owin.Security.Providers.ArcGISOnline + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.ArcGISOnline.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.nuspec b/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.nuspec new file mode 100644 index 0000000..1154aaa --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/Owin.Security.Providers.ArcGISOnline.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.ArcGISOnline + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a ArcGISOnline OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth ArcGISOnline + + + + + + + + + diff --git a/src/Owin.Security.Providers.ArcGISOnline/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.ArcGISOnline/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f6d41da --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.ArcGISOnline")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.ArcGISOnline")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("edf75484-ed98-4f6c-bd71-f4d8fa3fc019")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs similarity index 90% rename from Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs rename to src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs index 074a970..ebf6ac1 100644 --- a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticatedContext.cs @@ -1,13 +1,9 @@ // 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 System.Linq; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; -using Newtonsoft.Json.Linq; using Owin.Security.Providers.ArcGISOnline.Provider; namespace Owin.Security.Providers.ArcGISOnline @@ -28,11 +24,11 @@ namespace Owin.Security.Providers.ArcGISOnline { AccessToken = accessToken; - Id = user.user.username; - Name = user.user.fullName; + Id = user.User.Username; + Name = user.User.FullName; Link = "https://www.arcgis.com/sharing/rest/community/users/" + Id; UserName = Id; - Email = user.user.email; + Email = user.User.Email; } /// @@ -43,7 +39,7 @@ namespace Owin.Security.Providers.ArcGISOnline /// /// Gets the ArcGISOnline user ID /// - public string Id { get; private set; } + public string Id { get; } /// /// Gets the user's name diff --git a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs rename to src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs index d122109..7a8024b 100644 --- a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.ArcGISOnline public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever ArcGISOnline succesfully authenticates a user + /// Invoked whenever ArcGISOnline successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineReturnEndpointContext.cs b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/ArcGISOnline/Provider/ArcGISOnlineReturnEndpointContext.cs rename to src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineUser.cs b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineUser.cs new file mode 100644 index 0000000..0ec6a9a --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/Provider/ArcGISOnlineUser.cs @@ -0,0 +1,14 @@ +namespace Owin.Security.Providers.ArcGISOnline.Provider +{ + public class ArcGISOnlineUser + { + public User User { get; set; } + } + + public class User + { + public string Username { get; set; } + public string FullName { get; set; } + public string Email { get; set; } + } +} diff --git a/Owin.Security.Providers/ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs b/src/Owin.Security.Providers.ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs rename to src/Owin.Security.Providers.ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs index f8b2164..61c1990 100644 --- a/Owin.Security.Providers/ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/Provider/IArcGISOnlineAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.ArcGISOnline public interface IArcGISOnlineAuthenticationProvider { /// - /// Invoked whenever ArcGISOnline succesfully authenticates a user + /// Invoked whenever ArcGISOnline successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Properties/Resources.Designer.cs b/src/Owin.Security.Providers.ArcGISOnline/Resources.Designer.cs similarity index 92% rename from Owin.Security.Providers/Properties/Resources.Designer.cs rename to src/Owin.Security.Providers.ArcGISOnline/Resources.Designer.cs index c91e8e5..c9accb5 100644 --- a/Owin.Security.Providers/Properties/Resources.Designer.cs +++ b/src/Owin.Security.Providers.ArcGISOnline/Resources.Designer.cs @@ -1,14 +1,14 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34003 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace Owin.Security.Providers.Properties { +namespace Owin.Security.Providers.ArcGISOnline { using System; @@ -39,7 +39,7 @@ namespace Owin.Security.Providers.Properties { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Properties.Resources", typeof(Resources).Assembly); + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.ArcGISOnline.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/Owin.Security.Providers/Properties/Resources.resx b/src/Owin.Security.Providers.ArcGISOnline/Resources.resx similarity index 100% rename from Owin.Security.Providers/Properties/Resources.resx rename to src/Owin.Security.Providers.ArcGISOnline/Resources.resx diff --git a/src/Owin.Security.Providers.ArcGISOnline/packages.config b/src/Owin.Security.Providers.ArcGISOnline/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.ArcGISOnline/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Asana/AsanaAuthenticationExtensions.cs b/src/Owin.Security.Providers.Asana/AsanaAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Asana/AsanaAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Asana/AsanaAuthenticationExtensions.cs index bbb8dbd..a7677cd 100644 --- a/Owin.Security.Providers/Asana/AsanaAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Asana/AsanaAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Asana AsanaAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(AsanaAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Asana/AsanaAuthenticationHandler.cs b/src/Owin.Security.Providers.Asana/AsanaAuthenticationHandler.cs similarity index 53% rename from Owin.Security.Providers/Asana/AsanaAuthenticationHandler.cs rename to src/Owin.Security.Providers.Asana/AsanaAuthenticationHandler.cs index 04d8b9c..e2f1c0a 100644 --- a/Owin.Security.Providers/Asana/AsanaAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Asana/AsanaAuthenticationHandler.cs @@ -4,13 +4,11 @@ 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.Asana { @@ -18,13 +16,13 @@ namespace Owin.Security.Providers.Asana { private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public AsanaAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -36,8 +34,8 @@ namespace Owin.Security.Providers.Asana string code = null; string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = values[0]; @@ -55,21 +53,23 @@ namespace Owin.Security.Providers.Asana } // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Build up the body for the token request - var body = new List>(); - body.Add(new KeyValuePair("grant_type", "authorization_code")); - 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)); + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("code", code) + }; /*Your app makes a POST request to https://app.asana.com/-/oauth_token, passing the parameters as part of a standard form-encoded post body. grant_type - required Must be authorization_code @@ -83,13 +83,13 @@ namespace Owin.Security.Providers.Asana var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); requestMessage.Content = new FormUrlEncodedContent(body); - HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); + var tokenResponse = await _httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; + var accessToken = (string)response.access_token; /* * In the response, you will receive a JSON payload with the following parameters: @@ -102,11 +102,13 @@ namespace Owin.Security.Providers.Asana // Get the Asana user - var context = new AsanaAuthenticatedContext(Context, response.data, accessToken); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); + var context = new AsanaAuthenticatedContext(Context, response.data, accessToken) + { + 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)); @@ -127,7 +129,7 @@ namespace Owin.Security.Providers.Asana } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); } @@ -139,45 +141,44 @@ namespace Owin.Security.Providers.Asana return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - string baseUri = - Request.Scheme + - Uri.SchemeDelimiter + - Request.Host + - Request.PathBase; + properties.RedirectUri = currentUri; + } - string currentUri = - baseUri + - Request.Path + - Request.QueryString; + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); - string redirectUri = - baseUri + - Options.CallbackPath; - - AuthenticationProperties properties = challenge.Properties; - if (string.IsNullOrEmpty(properties.RedirectUri)) - { - properties.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - string state = Options.StateDataFormat.Protect(properties); - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state) - ; + var state = Options.StateDataFormat.Protect(properties); + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state) + ; - /*Your app redirects the user to https://app.asana.com/-/oauth_authorize, passing parameters along as a standard query string: + /*Your app redirects the user to https://app.asana.com/-/oauth_authorize, passing parameters along as a standard query string: client_id - required The Client ID uniquely identifies the application making the request. redirect_uri - required The URI to redirect to on success or error. This must match the Redirect URL specified in the application settings. @@ -185,8 +186,7 @@ namespace Owin.Security.Providers.Asana state - optional Encodes state of the app, which will be returned verbatim in the response and can be used to match the response up to a given request. */ - Response.Redirect(authorizationEndpoint); - } + Response.Redirect(authorizationEndpoint); return Task.FromResult(null); } @@ -198,50 +198,47 @@ namespace Owin.Security.Providers.Asana private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // 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 AsanaReturnEndpointContext(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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new AsanaReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Asana/AsanaAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Asana/AsanaAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/Asana/AsanaAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Asana/AsanaAuthenticationMiddleware.cs index 8e7a95e..f5513de 100644 --- a/Owin.Security.Providers/Asana/AsanaAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Asana/AsanaAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.Asana { public class AsanaAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public AsanaAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, AsanaAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new AsanaAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (AsanaAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Asana middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Asana middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.Asana /// protected override AuthenticationHandler CreateHandler() { - return new AsanaAuthenticationHandler(httpClient, logger); + return new AsanaAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(AsanaAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(AsanaAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Asana/AsanaAuthenticationOptions.cs b/src/Owin.Security.Providers.Asana/AsanaAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/Asana/AsanaAuthenticationOptions.cs rename to src/Owin.Security.Providers.Asana/AsanaAuthenticationOptions.cs index 92ea3a6..63e018a 100644 --- a/Owin.Security.Providers/Asana/AsanaAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Asana/AsanaAuthenticationOptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; diff --git a/Owin.Security.Providers/Asana/Constants.cs b/src/Owin.Security.Providers.Asana/Constants.cs similarity index 100% rename from Owin.Security.Providers/Asana/Constants.cs rename to src/Owin.Security.Providers.Asana/Constants.cs diff --git a/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.csproj b/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.csproj new file mode 100644 index 0000000..e8125d0 --- /dev/null +++ b/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {F3E27220-1D8C-4037-94AA-7B7F4A12F351} + Library + Properties + Owin.Security.Providers.Asana + Owin.Security.Providers.Asana + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Asana.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.nuspec b/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.nuspec new file mode 100644 index 0000000..2934a27 --- /dev/null +++ b/src/Owin.Security.Providers.Asana/Owin.Security.Providers.Asana.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Asana + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Asana OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Asana + + + + + + + + + diff --git a/src/Owin.Security.Providers.Asana/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Asana/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3dea4d1 --- /dev/null +++ b/src/Owin.Security.Providers.Asana/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Asana")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Asana")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("2dd55191-411e-4f28-8051-0f60eb996a53")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Asana/Provider/AsanaAuthenticatedContext.cs b/src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/Asana/Provider/AsanaAuthenticatedContext.cs rename to src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticatedContext.cs index c2a1a90..f4fb180 100644 --- a/Owin.Security.Providers/Asana/Provider/AsanaAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/Asana/Provider/AsanaAuthenticationProvider.cs b/src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Asana/Provider/AsanaAuthenticationProvider.cs rename to src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticationProvider.cs index b0f2a3c..bf98194 100644 --- a/Owin.Security.Providers/Asana/Provider/AsanaAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Asana/Provider/AsanaAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Asana public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Asana succesfully authenticates a user + /// Invoked whenever Asana successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Asana/Provider/AsanaReturnEndpointContext.cs b/src/Owin.Security.Providers.Asana/Provider/AsanaReturnEndpointContext.cs similarity index 80% rename from Owin.Security.Providers/Asana/Provider/AsanaReturnEndpointContext.cs rename to src/Owin.Security.Providers.Asana/Provider/AsanaReturnEndpointContext.cs index 19d0f29..95f54b4 100644 --- a/Owin.Security.Providers/Asana/Provider/AsanaReturnEndpointContext.cs +++ b/src/Owin.Security.Providers.Asana/Provider/AsanaReturnEndpointContext.cs @@ -16,10 +16,7 @@ namespace Owin.Security.Providers.Asana /// /// OWIN environment /// The authentication ticket - public AsanaReturnEndpointContext( - IOwinContext context, - AuthenticationTicket ticket) - : base(context, ticket) + public AsanaReturnEndpointContext(IOwinContext context,AuthenticationTicket ticket) : base(context, ticket) { } } diff --git a/Owin.Security.Providers/Asana/Provider/IAsanaAuthenticationProvider.cs b/src/Owin.Security.Providers.Asana/Provider/IAsanaAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Asana/Provider/IAsanaAuthenticationProvider.cs rename to src/Owin.Security.Providers.Asana/Provider/IAsanaAuthenticationProvider.cs index 418f121..3d98d1a 100644 --- a/Owin.Security.Providers/Asana/Provider/IAsanaAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Asana/Provider/IAsanaAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Asana public interface IAsanaAuthenticationProvider { /// - /// Invoked whenever Asana succesfully authenticates a user + /// Invoked whenever Asana successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Asana/Resources.Designer.cs b/src/Owin.Security.Providers.Asana/Resources.Designer.cs new file mode 100644 index 0000000..ec8e720 --- /dev/null +++ b/src/Owin.Security.Providers.Asana/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Asana { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Asana.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Asana/Resources.resx b/src/Owin.Security.Providers.Asana/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Asana/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Asana/packages.config b/src/Owin.Security.Providers.Asana/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Asana/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Backlog/BacklogAuthenticationExtensions.cs b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Backlog/BacklogAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Backlog/BacklogAuthenticationExtensions.cs index d26f6e2..4b44a8e 100644 --- a/Owin.Security.Providers/Backlog/BacklogAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Backlog BacklogAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(BacklogAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Backlog/BacklogAuthenticationHandler.cs b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationHandler.cs new file mode 100644 index 0000000..2561d61 --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationHandler.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net.Http.Headers; + +namespace Owin.Security.Providers.Backlog +{ + public class BacklogAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public BacklogAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Get token + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint); + tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + tokenRequest.Content = new FormUrlEncodedContent(body); + + var tokenResponse = await _httpClient.SendAsync(tokenRequest, Request.CallCancelled); + + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string) response.expires_in; + string refreshToken = null; + if (response.refresh_token != null) + refreshToken = (string) response.refresh_token; + var tokenType = (string)response.token_type; + + // Get the Backlog user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.UserInfoEndpoint); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, Uri.EscapeDataString(accessToken)); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new BacklogAuthenticatedContext(Context, user, accessToken, expires, refreshToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.MailAddress)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.MailAddress, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.AuthorizationEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BacklogReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Backlog/BacklogAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationMiddleware.cs similarity index 60% rename from Owin.Security.Providers/Backlog/BacklogAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Backlog/BacklogAuthenticationMiddleware.cs index 3eccf7c..2189b18 100644 --- a/Owin.Security.Providers/Backlog/BacklogAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationMiddleware.cs @@ -7,46 +7,45 @@ 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.Backlog { public class BacklogAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public BacklogAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, BacklogAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - if (String.IsNullOrWhiteSpace(Options.ContractName)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ContractName)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ContractName")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new BacklogAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (BacklogAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -63,24 +62,22 @@ namespace Owin.Security.Providers.Backlog /// protected override AuthenticationHandler CreateHandler() { - return new BacklogAuthenticationHandler(httpClient, logger); + return new BacklogAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(BacklogAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(BacklogAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Backlog/BacklogAuthenticationOptions.cs b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationOptions.cs similarity index 94% rename from Owin.Security.Providers/Backlog/BacklogAuthenticationOptions.cs rename to src/Owin.Security.Providers.Backlog/BacklogAuthenticationOptions.cs index e67bc38..584287f 100644 --- a/Owin.Security.Providers/Backlog/BacklogAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Backlog/BacklogAuthenticationOptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; @@ -77,7 +76,7 @@ namespace Owin.Security.Providers.Backlog get { var ub = new UriBuilder(TempTokenEndpoint); - ub.Host = ub.Host.Replace("contractname", this.ContractName); + ub.Host = ub.Host.Replace("contractname", ContractName); return ub.Uri.ToString(); } @@ -88,7 +87,7 @@ namespace Owin.Security.Providers.Backlog get { var ub = new UriBuilder(TempUserInfoEndpoint); - ub.Host = ub.Host.Replace("contractname", this.ContractName); + ub.Host = ub.Host.Replace("contractname", ContractName); return ub.Uri.ToString(); } @@ -99,7 +98,7 @@ namespace Owin.Security.Providers.Backlog get { var ub = new UriBuilder(TempAuthorizationEndpoint); - ub.Host = ub.Host.Replace("contractname", this.ContractName); + ub.Host = ub.Host.Replace("contractname", ContractName); return ub.Uri.ToString(); } diff --git a/Owin.Security.Providers/Backlog/Constants.cs b/src/Owin.Security.Providers.Backlog/Constants.cs similarity index 100% rename from Owin.Security.Providers/Backlog/Constants.cs rename to src/Owin.Security.Providers.Backlog/Constants.cs diff --git a/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.csproj b/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.csproj new file mode 100644 index 0000000..cc7fcdb --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {2DC03778-9EF1-466A-83EC-7D8422DECD23} + Library + Properties + Owin.Security.Providers.Backlog + Owin.Security.Providers.Backlog + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Backlog.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.nuspec b/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.nuspec new file mode 100644 index 0000000..36b541e --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/Owin.Security.Providers.Backlog.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Backlog + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Backlog OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Backlog + + + + + + + + + diff --git a/src/Owin.Security.Providers.Backlog/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Backlog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3aab602 --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Backlog")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Backlog")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("59898d6e-035f-44ce-b4da-c79ffefdf6b3")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Backlog/Provider/BacklogAuthenticatedContext.cs b/src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticatedContext.cs similarity index 95% rename from Owin.Security.Providers/Backlog/Provider/BacklogAuthenticatedContext.cs rename to src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticatedContext.cs index 828f95f..39256ff 100644 --- a/Owin.Security.Providers/Backlog/Provider/BacklogAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticatedContext.cs @@ -2,7 +2,6 @@ using System; using System.Globalization; -using System.Linq; using System.Security.Claims; using Microsoft.Owin; using Microsoft.Owin.Security; @@ -21,9 +20,9 @@ namespace Owin.Security.Providers.Backlog /// /// The OWIN environment /// The JSON-serialized user - /// /// Google+ Access token /// Seconds until expiration + /// public BacklogAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires, string refreshToken) : base(context) { @@ -32,7 +31,7 @@ namespace Owin.Security.Providers.Backlog RefreshToken = refreshToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/Backlog/Provider/BacklogAuthenticationProvider.cs b/src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Backlog/Provider/BacklogAuthenticationProvider.cs rename to src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticationProvider.cs index 5dc6bf8..71273ca 100644 --- a/Owin.Security.Providers/Backlog/Provider/BacklogAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Backlog/Provider/BacklogAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Backlog public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Backlog/Provider/BacklogReturnEndpointContext.cs b/src/Owin.Security.Providers.Backlog/Provider/BacklogReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Backlog/Provider/BacklogReturnEndpointContext.cs rename to src/Owin.Security.Providers.Backlog/Provider/BacklogReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Backlog/Provider/IGBacklogAuthenticationProvider.cs b/src/Owin.Security.Providers.Backlog/Provider/IGBacklogAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Backlog/Provider/IGBacklogAuthenticationProvider.cs rename to src/Owin.Security.Providers.Backlog/Provider/IGBacklogAuthenticationProvider.cs index d38fce9..1cd0ff3 100644 --- a/Owin.Security.Providers/Backlog/Provider/IGBacklogAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Backlog/Provider/IGBacklogAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Backlog public interface IBacklogAuthenticationProvider { /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Backlog/Resources.Designer.cs b/src/Owin.Security.Providers.Backlog/Resources.Designer.cs new file mode 100644 index 0000000..1da08d9 --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Backlog { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Backlog.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Backlog/Resources.resx b/src/Owin.Security.Providers.Backlog/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Backlog/packages.config b/src/Owin.Security.Providers.Backlog/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Backlog/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationExtensions.cs b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationExtensions.cs similarity index 96% rename from Owin.Security.Providers/BattleNet/BattleNetAuthenticationExtensions.cs rename to src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationExtensions.cs index 64ad5ea..5c481ba 100644 --- a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationExtensions.cs @@ -22,7 +22,7 @@ namespace Owin.Security.Providers.BattleNet { ClientId = clientId, ClientSecret = clientSecret, - Region = Region.US + Region = Region.Us }); } } diff --git a/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationHandler.cs b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationHandler.cs new file mode 100644 index 0000000..a06209b --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationHandler.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +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.BattleNet +{ + public class BattleNetAuthenticationHandler : AuthenticationHandler + { + + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private string _tokenEndpoint = "https://eu.battle.net/oauth/token"; + private string _accountUserIdEndpoint = "https://eu.api.battle.net/account/user/id"; + private string _accountUserBattleTagEndpoint = "https://eu.api.battle.net/account/user/battletag"; + private string _oauthAuthEndpoint = "https://eu.battle.net/oauth/authorize"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public BattleNetAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override Task InitializeCoreAsync() + { + switch (Options.Region) + { + case Region.China: + _tokenEndpoint = "https://cn.battle.net/oauth/token"; + _accountUserIdEndpoint = "https://cn.api.battle.net/account/user/id"; + _accountUserBattleTagEndpoint = "https://cn.api.battle.net/account/user/battletag"; + _oauthAuthEndpoint = "https://cn.battle.net/oauth/authorize"; + break; + case Region.Korea: + _tokenEndpoint = "https://kr.battle.net/oauth/token"; + _accountUserIdEndpoint = "https://kr.api.battle.net/account/user/id"; + _accountUserBattleTagEndpoint = "https://kr.api.battle.net/account/user/battletag"; + _oauthAuthEndpoint = "https://kr.battle.net/oauth/authorize"; + break; + case Region.Taiwan: + _tokenEndpoint = "https://tw.battle.net/oauth/token"; + _accountUserIdEndpoint = "https://tw.api.battle.net/account/user/id"; + _accountUserBattleTagEndpoint = "https://tw.api.battle.net/account/user/battletag"; + _oauthAuthEndpoint = "https://tw.battle.net/oauth/authorize"; + break; + case Region.Europe: + _tokenEndpoint = "https://eu.battle.net/oauth/token"; + _accountUserIdEndpoint = "https://eu.api.battle.net/account/user/id"; + _accountUserBattleTagEndpoint = "https://eu.api.battle.net/account/user/battletag"; + _oauthAuthEndpoint = "https://eu.battle.net/oauth/authorize"; + break; + default: + _tokenEndpoint = "https://us.battle.net/oauth/token"; + _accountUserIdEndpoint = "https://us.api.battle.net/account/user/id"; + _accountUserBattleTagEndpoint = "https://us.api.battle.net/account/user/battletag"; + _oauthAuthEndpoint = "https://us.battle.net/oauth/authorize"; + break; + } + + return Task.FromResult(true); + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = await _httpClient.PostAsync(_tokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + var response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string)response.expires_in; + + // Get WoW User Id + var graphResponse = await _httpClient.GetAsync(_accountUserIdEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var userId = JObject.Parse(text); + + // Get WoW BattleTag + graphResponse = await _httpClient.GetAsync(_accountUserBattleTagEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var battleTag = JObject.Parse(text); + + + var context = new BattleNetAuthenticatedContext(Context, userId, battleTag, accessToken, expires) + { + 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.BattleTag)) + { + context.Identity.AddClaim(new Claim("urn:battlenet:battletag", context.BattleTag, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + context.Identity.AddClaim(new Claim("urn:battlenet:accesstoken", context.AccessToken, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + _oauthAuthEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BattleNetReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} diff --git a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationMiddleware.cs b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationMiddleware.cs similarity index 64% rename from Owin.Security.Providers/BattleNet/BattleNetAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationMiddleware.cs index acda7ca..f159f6d 100644 --- a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationMiddleware.cs @@ -7,26 +7,25 @@ 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.BattleNet { public class BattleNetAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public BattleNetAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, BattleNetAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new BattleNetAuthenticationProvider(); @@ -39,10 +38,10 @@ namespace Owin.Security.Providers.BattleNet Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 @@ -59,26 +58,24 @@ namespace Owin.Security.Providers.BattleNet /// protected override AuthenticationHandler CreateHandler() { - return new BattleNetAuthenticationHandler(httpClient, logger); + return new BattleNetAuthenticationHandler(_httpClient, _logger); } private static HttpMessageHandler ResolveHttpMessageHandler(BattleNetAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) - { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; - } + 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; + return handler; } } } diff --git a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationOptions.cs b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/BattleNet/BattleNetAuthenticationOptions.cs rename to src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationOptions.cs index 91c1d5c..ffbd338 100644 --- a/Owin.Security.Providers/BattleNet/BattleNetAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.BattleNet/BattleNetAuthenticationOptions.cs @@ -9,7 +9,7 @@ namespace Owin.Security.Providers.BattleNet public enum Region { Europe, - US, + Us, Korea, Taiwan, China diff --git a/Owin.Security.Providers/BattleNet/Constants.cs b/src/Owin.Security.Providers.BattleNet/Constants.cs similarity index 100% rename from Owin.Security.Providers/BattleNet/Constants.cs rename to src/Owin.Security.Providers.BattleNet/Constants.cs diff --git a/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.csproj b/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.csproj new file mode 100644 index 0000000..4018da3 --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {99A175DA-ADE4-436C-A272-C8AE44B7A086} + Library + Properties + Owin.Security.Providers.BattleNet + Owin.Security.Providers.BattleNet + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.BattleNet.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.nuspec b/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.nuspec new file mode 100644 index 0000000..d5c14cb --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/Owin.Security.Providers.BattleNet.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.BattleNet + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a BattleNet OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth BattleNet + + + + + + + + + diff --git a/src/Owin.Security.Providers.BattleNet/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.BattleNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fdae3e4 --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.BattleNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.BattleNet")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("e327078f-e929-40f2-a36d-404938186e71")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/BattleNet/Provider/BattleNetAuthenticatedContext.cs b/src/Owin.Security.Providers.BattleNet/Provider/BattleNetAuthenticatedContext.cs similarity index 96% rename from Owin.Security.Providers/BattleNet/Provider/BattleNetAuthenticatedContext.cs rename to src/Owin.Security.Providers.BattleNet/Provider/BattleNetAuthenticatedContext.cs index bb98895..ce13a42 100644 --- a/Owin.Security.Providers/BattleNet/Provider/BattleNetAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.BattleNet/Provider/BattleNetAuthenticatedContext.cs @@ -31,7 +31,7 @@ namespace Owin.Security.Providers.BattleNet AccessToken = accessToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/BattleNet/Provider/BattleNetAuthenticationProvider.cs b/src/Owin.Security.Providers.BattleNet/Provider/BattleNetAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/BattleNet/Provider/BattleNetAuthenticationProvider.cs rename to src/Owin.Security.Providers.BattleNet/Provider/BattleNetAuthenticationProvider.cs diff --git a/Owin.Security.Providers/BattleNet/Provider/BattleNetReturnEndpointContext.cs b/src/Owin.Security.Providers.BattleNet/Provider/BattleNetReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/BattleNet/Provider/BattleNetReturnEndpointContext.cs rename to src/Owin.Security.Providers.BattleNet/Provider/BattleNetReturnEndpointContext.cs diff --git a/Owin.Security.Providers/BattleNet/Provider/IBattleNetAuthenticationProvider.cs b/src/Owin.Security.Providers.BattleNet/Provider/IBattleNetAuthenticationProvider.cs similarity index 92% rename from Owin.Security.Providers/BattleNet/Provider/IBattleNetAuthenticationProvider.cs rename to src/Owin.Security.Providers.BattleNet/Provider/IBattleNetAuthenticationProvider.cs index e045a3d..b639f8f 100644 --- a/Owin.Security.Providers/BattleNet/Provider/IBattleNetAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.BattleNet/Provider/IBattleNetAuthenticationProvider.cs @@ -5,7 +5,7 @@ namespace Owin.Security.Providers.BattleNet public interface IBattleNetAuthenticationProvider { /// - /// Invoked whenever Battle.net succesfully authenticates a user + /// Invoked whenever Battle.net successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.BattleNet/Resources.Designer.cs b/src/Owin.Security.Providers.BattleNet/Resources.Designer.cs new file mode 100644 index 0000000..67a8b08 --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.BattleNet { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.BattleNet.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.BattleNet/Resources.resx b/src/Owin.Security.Providers.BattleNet/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.BattleNet/packages.config b/src/Owin.Security.Providers.BattleNet/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.BattleNet/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationExtensions.cs b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/Bitbucket/BitbucketAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationExtensions.cs index bf090cb..9d967e5 100644 --- a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Bitbucket BitbucketAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(BitbucketAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationHandler.cs b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationHandler.cs similarity index 51% rename from Owin.Security.Providers/Bitbucket/BitbucketAuthenticationHandler.cs rename to src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationHandler.cs index d9a30c6..5479a2f 100644 --- a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationHandler.cs @@ -4,7 +4,6 @@ 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; @@ -21,13 +20,13 @@ namespace Owin.Security.Providers.Bitbucket private const string AuthenticatedReturnUrlKey = ".Bitbucket.ReturnUrl"; private const string CorrelationIdKey = ".AspNet.Correlation.Bitbucket"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public BitbucketAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -38,16 +37,18 @@ namespace Owin.Security.Providers.Bitbucket { string code = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = values[0]; } - // Bitbucket doesn't include a state parameter in the callback url, so we need to retreive a few things required for OWIN - properties = new AuthenticationProperties(); - properties.RedirectUri = Context.Request.Cookies[AuthenticatedReturnUrlKey]; + // Bitbucket doesn't include a state parameter in the callback url, so we need to retrieve a few things required for OWIN + properties = new AuthenticationProperties + { + RedirectUri = Context.Request.Cookies[AuthenticatedReturnUrlKey] + }; var xsrfId = Context.Request.Cookies[XsrfIdKey]; if (xsrfId != null) { @@ -56,7 +57,7 @@ namespace Owin.Security.Providers.Bitbucket properties.Dictionary.Add(CorrelationIdKey, Context.Request.Cookies[CorrelationIdKey]); // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } @@ -66,11 +67,12 @@ namespace Owin.Security.Providers.Bitbucket Context.Response.Cookies.Delete(CorrelationIdKey); Context.Response.Cookies.Delete(AuthenticatedReturnUrlKey); - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Build up the body for the token request + // ReSharper disable once CommentTypo // https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html#OAuthonBitbucketCloud-Authorizationcodegrant // curl -X POST -u "client_id:secret" https://bitbucket.org/site/oauth2/access_token -d grant_type=authorization_code -d code={code} @@ -88,31 +90,33 @@ namespace Owin.Security.Providers.Bitbucket "Basic", Convert.ToBase64String( System.Text.Encoding.ASCII.GetBytes( - string.Format("{0}:{1}", Options.ClientId, Options.ClientSecret)))); + $"{Options.ClientId}:{Options.ClientSecret}"))); requestMessage.Content = new FormUrlEncodedContent(body); - HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); + var tokenResponse = await _httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; + var accessToken = (string)response.access_token; // Get the Bitbucket user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint); + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint); userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); userResponse.EnsureSuccessStatusCode(); text = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); + var user = JObject.Parse(text); - var context = new BitbucketAuthenticatedContext(Context, user, accessToken); + var context = new BitbucketAuthenticatedContext(Context, user, accessToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; - 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)); @@ -141,7 +145,7 @@ namespace Owin.Security.Providers.Bitbucket } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); } @@ -153,52 +157,50 @@ namespace Owin.Security.Providers.Bitbucket return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - 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; - } - - // Bitbucket doesn't include a state parameter in the callback url, so we need to persist a few things required for OWIN - Context.Response.Cookies.Append(AuthenticatedReturnUrlKey, properties.RedirectUri); - if (challenge.Properties.Dictionary.ContainsKey(XsrfIdKey)) - { - Context.Response.Cookies.Append(XsrfIdKey, challenge.Properties.Dictionary[XsrfIdKey]); - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - Context.Response.Cookies.Append(CorrelationIdKey, challenge.Properties.Dictionary[CorrelationIdKey]); - - // https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html#OAuthonBitbucketCloud-Accesstokens - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&response_type=code" + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // Bitbucket doesn't include a state parameter in the callback url, so we need to persist a few things required for OWIN + Context.Response.Cookies.Append(AuthenticatedReturnUrlKey, properties.RedirectUri); + if (challenge.Properties.Dictionary.ContainsKey(XsrfIdKey)) + { + Context.Response.Cookies.Append(XsrfIdKey, challenge.Properties.Dictionary[XsrfIdKey]); + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + Context.Response.Cookies.Append(CorrelationIdKey, challenge.Properties.Dictionary[CorrelationIdKey]); + + // https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html#OAuthonBitbucketCloud-Accesstokens + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&response_type=code" + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -209,50 +211,47 @@ namespace Owin.Security.Providers.Bitbucket private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // 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 BitbucketReturnEndpointContext(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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new BitbucketReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/Bitbucket/BitbucketAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationMiddleware.cs index 222fa76..7c8eb0d 100644 --- a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.Bitbucket { public class BitbucketAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public BitbucketAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, BitbucketAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new BitbucketAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (BitbucketAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Bitbucket middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Bitbucket middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.Bitbucket /// protected override AuthenticationHandler CreateHandler() { - return new BitbucketAuthenticationHandler(httpClient, logger); + return new BitbucketAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(BitbucketAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(BitbucketAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Bitbucket/BitbucketAuthenticationOptions.cs b/src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Bitbucket/BitbucketAuthenticationOptions.cs rename to src/Owin.Security.Providers.Bitbucket/BitbucketAuthenticationOptions.cs diff --git a/Owin.Security.Providers/Bitbucket/Constants.cs b/src/Owin.Security.Providers.Bitbucket/Constants.cs similarity index 100% rename from Owin.Security.Providers/Bitbucket/Constants.cs rename to src/Owin.Security.Providers.Bitbucket/Constants.cs diff --git a/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.csproj b/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.csproj new file mode 100644 index 0000000..4d19597 --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {E5212FC7-ABCB-462F-9989-8E022DFFE43C} + Library + Properties + Owin.Security.Providers.Bitbucket + Owin.Security.Providers.Bitbucket + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Bitbucket.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.nuspec b/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.nuspec new file mode 100644 index 0000000..148809a --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/Owin.Security.Providers.Bitbucket.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Bitbucket + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Bitbucket OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Bitbucket + + + + + + + + + diff --git a/src/Owin.Security.Providers.Bitbucket/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Bitbucket/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..79df598 --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Bitbucket")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Bitbucket")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("857b99b3-2c21-4651-8893-7296d060efef")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticatedContext.cs b/src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticatedContext.cs rename to src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticatedContext.cs index 930074f..814499b 100644 --- a/Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticationProvider.cs b/src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticationProvider.cs rename to src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticationProvider.cs index 2081a0c..5036441 100644 --- a/Owin.Security.Providers/Bitbucket/Provider/BitbucketAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Bitbucket/Provider/BitbucketAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Bitbucket public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever GitHub succesfully authenticates a user + /// Invoked whenever GitHub successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Bitbucket/Provider/BitbucketReturnEndpointContext.cs b/src/Owin.Security.Providers.Bitbucket/Provider/BitbucketReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Bitbucket/Provider/BitbucketReturnEndpointContext.cs rename to src/Owin.Security.Providers.Bitbucket/Provider/BitbucketReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Bitbucket/Provider/IBitbucketAuthenticationProvider.cs b/src/Owin.Security.Providers.Bitbucket/Provider/IBitbucketAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Bitbucket/Provider/IBitbucketAuthenticationProvider.cs rename to src/Owin.Security.Providers.Bitbucket/Provider/IBitbucketAuthenticationProvider.cs index 577763d..4bee1a1 100644 --- a/Owin.Security.Providers/Bitbucket/Provider/IBitbucketAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Bitbucket/Provider/IBitbucketAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Bitbucket public interface IBitbucketAuthenticationProvider { /// - /// Invoked whenever GitHub succesfully authenticates a user + /// Invoked whenever GitHub successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Bitbucket/Resources.Designer.cs b/src/Owin.Security.Providers.Bitbucket/Resources.Designer.cs new file mode 100644 index 0000000..0c9456f --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Bitbucket { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Bitbucket.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Bitbucket/Resources.resx b/src/Owin.Security.Providers.Bitbucket/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Bitbucket/packages.config b/src/Owin.Security.Providers.Bitbucket/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Bitbucket/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs b/src/Owin.Security.Providers.Buffer/BufferAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Buffer/BufferAuthenticationExtensions.cs index 56e21dc..5289b46 100644 --- a/Owin.Security.Providers/Buffer/BufferAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Buffer/BufferAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Buffer BufferAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(BufferAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Buffer/BufferAuthenticationHandler.cs b/src/Owin.Security.Providers.Buffer/BufferAuthenticationHandler.cs new file mode 100644 index 0000000..a816abb --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/BufferAuthenticationHandler.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +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.Buffer +{ + public class BufferAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.bufferapp.com/1/oauth2/token.json"; + private const string UserInfoEndpoint = "https://api.bufferapp.com/1/user.json"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public BufferAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string) response.expires_in; + + // Get the Buffer user + var graphResponse = await _httpClient.GetAsync( + UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new BufferAuthenticatedContext(Context, user, accessToken, expires) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://bufferapp.com/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new BufferReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Buffer/BufferAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Buffer/BufferAuthenticationMiddleware.cs index f081de3..3dca384 100644 --- a/Owin.Security.Providers/Buffer/BufferAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Buffer/BufferAuthenticationMiddleware.cs @@ -7,43 +7,42 @@ 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.Buffer { public class BufferAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public BufferAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, BufferAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new BufferAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (BufferAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -60,24 +59,22 @@ namespace Owin.Security.Providers.Buffer /// protected override AuthenticationHandler CreateHandler() { - return new BufferAuthenticationHandler(httpClient, logger); + return new BufferAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(BufferAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(BufferAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs b/src/Owin.Security.Providers.Buffer/BufferAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Buffer/BufferAuthenticationOptions.cs rename to src/Owin.Security.Providers.Buffer/BufferAuthenticationOptions.cs diff --git a/Owin.Security.Providers/Buffer/Constants.cs b/src/Owin.Security.Providers.Buffer/Constants.cs similarity index 100% rename from Owin.Security.Providers/Buffer/Constants.cs rename to src/Owin.Security.Providers.Buffer/Constants.cs diff --git a/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.csproj b/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.csproj new file mode 100644 index 0000000..587fe03 --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {6F75FC1F-D9E9-49B3-A6CE-CFA8FEEA11A5} + Library + Properties + Owin.Security.Providers.Buffer + Owin.Security.Providers.Buffer + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Buffer.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.nuspec b/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.nuspec new file mode 100644 index 0000000..f5ea26c --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/Owin.Security.Providers.Buffer.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Buffer + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Buffer OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Buffer + + + + + + + + + diff --git a/src/Owin.Security.Providers.Buffer/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Buffer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1cc188f --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Buffer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Buffer")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("38815307-3360-4f54-9204-03fae50160bc")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs b/src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticatedContext.cs similarity index 96% rename from Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs rename to src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticatedContext.cs index ee8c9c5..7cf77aa 100644 --- a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticatedContext.cs @@ -31,7 +31,7 @@ namespace Owin.Security.Providers.Buffer AccessToken = accessToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs b/src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs rename to src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticationProvider.cs index 49c6930..a3519a1 100644 --- a/Owin.Security.Providers/Buffer/Provider/BufferAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Buffer/Provider/BufferAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Buffer public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Buffer succesfully authenticates a user + /// Invoked whenever Buffer successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs b/src/Owin.Security.Providers.Buffer/Provider/BufferReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Buffer/Provider/BufferReturnEndpointContext.cs rename to src/Owin.Security.Providers.Buffer/Provider/BufferReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs b/src/Owin.Security.Providers.Buffer/Provider/IBufferAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs rename to src/Owin.Security.Providers.Buffer/Provider/IBufferAuthenticationProvider.cs index 23243ed..d1f8f02 100644 --- a/Owin.Security.Providers/Buffer/Provider/IBufferAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Buffer/Provider/IBufferAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Buffer public interface IBufferAuthenticationProvider { /// - /// Invoked whenever Buffer succesfully authenticates a user + /// Invoked whenever Buffer successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Buffer/Resources.Designer.cs b/src/Owin.Security.Providers.Buffer/Resources.Designer.cs new file mode 100644 index 0000000..f2c3594 --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Buffer { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Buffer.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Buffer/Resources.resx b/src/Owin.Security.Providers.Buffer/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Buffer/packages.config b/src/Owin.Security.Providers.Buffer/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Buffer/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Cosign/Constants.cs b/src/Owin.Security.Providers.Cosign/Constants.cs similarity index 100% rename from Owin.Security.Providers/Cosign/Constants.cs rename to src/Owin.Security.Providers.Cosign/Constants.cs diff --git a/Owin.Security.Providers/Cosign/CosignAuthenticationExtensions.cs b/src/Owin.Security.Providers.Cosign/CosignAuthenticationExtensions.cs similarity index 79% rename from Owin.Security.Providers/Cosign/CosignAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Cosign/CosignAuthenticationExtensions.cs index a8c0b0d..f02195a 100644 --- a/Owin.Security.Providers/Cosign/CosignAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Cosign/CosignAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Cosign CosignAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(CosignAuthenticationMiddleware), app, options); @@ -19,10 +19,7 @@ namespace Owin.Security.Providers.Cosign public static IAppBuilder UseCosignAuthentication(this IAppBuilder app, string clientId, string clientSecret) { - return app.UseCosignAuthentication(new CosignAuthenticationOptions - { - - }); + return app.UseCosignAuthentication(new CosignAuthenticationOptions()); } } } \ No newline at end of file diff --git a/src/Owin.Security.Providers.Cosign/CosignAuthenticationHandler.cs b/src/Owin.Security.Providers.Cosign/CosignAuthenticationHandler.cs new file mode 100644 index 0000000..4e5498e --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/CosignAuthenticationHandler.cs @@ -0,0 +1,323 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.Cosign.Provider; + +namespace Owin.Security.Providers.Cosign +{ + public class CosignAuthenticationHandler : AuthenticationHandler + { + /* + Cosign sends authenticated users to iis web site root (not to application). + We need redirect user back to Identity Server application. + This can be done with different approaches http handler, url rewrite... + Here is UrlRewrite configuration + + + + + + + + + + + + + + + + + + + + */ + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private readonly ILogger _logger; + + public CosignAuthenticationHandler(ILogger logger) + { + + _logger = logger; + } + + protected override Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + /*BUG: IReadableStringCollection has a bug. Some characters can be missed in the collection and replaces with blank space. + Example: having "x" character in QueryString will result in having " " in the collection. + I will use QueryString from Request object instead of IReadableStringCollection*/ + + //IReadableStringCollection query = Request.Query; + //IList values = query.GetValues("cosign-" + Options.ClientServer); + //if (values != null && values.Count == 1) + //{ + // serviceCookieValue = values[0]; + //} + //values = query.GetValues("state"); + //if (values != null && values.Count == 1) + //{ + // state = values[0]; + //} + + var queryString = Request.QueryString.Value; + var values = queryString.Split(new[] {"&"}, StringSplitOptions.RemoveEmptyEntries); + var serviceCookieValue = values.First(a => a.Contains(Options.ClientServer)) + .Replace("cosign-" + Options.ClientServer + "=", ""); + var state = values.First(a => a.Contains("state")) + .Replace("state=", ""); + + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + + //// OAuth2 10.12 CSRF + //if (!ValidateCorrelationId(properties, logger)) + //{ + // return new AuthenticationTicket(null, properties); + //} + + + // Get host related information. + var hostEntry = Dns.GetHostEntry(Options.CosignServer); + + // Loop through the AddressList to obtain the supported AddressFamily. This is to avoid + // an exception that occurs when the host IP Address is not compatible with the address family + // (typical in the IPv6 case). + foreach (var address in hostEntry.AddressList) + { + new IPEndPoint(address, Options.CosignServicePort); + + using (var tcpClient = new TcpClient()) + { + + tcpClient.Connect(address, Options.CosignServicePort); + if (!tcpClient.Connected) continue; + _logger.WriteInformation("Cosign authentication handler. Connected to server ip: " + address); + + //read message from connected server and validate response + var networkStream = tcpClient.GetStream(); + var buffer = new byte[256]; + var bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length); + var receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); + if (receivedData.Substring(0, 3) != "220") + continue; + + //initiate secure negotiation and validate response + // ReSharper disable once StringLiteralTypo + buffer = Encoding.UTF8.GetBytes("STARTTLS 2" + Environment.NewLine); + networkStream.Write(buffer, 0, buffer.Length); + networkStream.Flush(); + buffer = new byte[256]; + bytesRead = networkStream.ReadAsync(buffer, 0, buffer.Length); + receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); + //expected message: 220 Ready to start TLS + if (receivedData.Substring(0, 3) != "220") + continue; + + + var sslStream = new SslStream(tcpClient.GetStream(), false, ValidateServerCertificate, + null); + + var certs = GetCertificateCertificateCollection(Options.ClientServer, + StoreName.My, + StoreLocation.LocalMachine); + try + { + var authResult = sslStream.AuthenticateAsClientAsync(Options.CosignServer, certs, SslProtocols.Tls, false); + authResult.GetAwaiter().GetResult(); + } + catch (AuthenticationException e) + { + _logger.WriteError(e.Message); + if (e.InnerException != null) + { + _logger.WriteError($"Inner exception: {e.InnerException.Message}"); + } + _logger.WriteError("Authentication failed - closing the connection."); + tcpClient.Close(); + continue; + } + catch (Exception ex) + { + _logger.WriteError(ex.Message); + tcpClient.Close(); + continue; + } + + if (!sslStream.IsEncrypted || !sslStream.IsSigned || !sslStream.IsMutuallyAuthenticated) + continue; + // The server name must match the name on the server certificate. + if (!sslStream.IsAuthenticated) + continue; + + + buffer = new byte[256]; + bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length); + receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); + if (receivedData.Substring(0, 3) != "220") + continue; + + var data = + Encoding.UTF8.GetBytes("CHECK " + "cosign-" + Options.ClientServer + "=" + + serviceCookieValue + Environment.NewLine); + + sslStream.Write(data, 0, data.Length); + sslStream.Flush(); + buffer = new byte[256]; + + bytesRead = sslStream.ReadAsync(buffer, 0, buffer.Length); + receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead.Result); + + + switch (receivedData.Substring(0, 1)) + { + case "2": + //Success + _logger.WriteInformation("Cosign authentication handler. 2-Response from Server: Success."); + var context = new CosignAuthenticatedContext(Context, receivedData) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + + + var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType); + if (!string.IsNullOrEmpty(context.Id)) + { + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, + XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.UserId)) + { + identity.AddClaim(new Claim("UserId", context.UserId, XmlSchemaString, + Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.IpAddress)) + { + identity.AddClaim(new Claim("IpAddress", context.IpAddress, XmlSchemaString, + Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Realm)) + { + identity.AddClaim(new Claim("Realm", context.Realm, XmlSchemaString, + Options.AuthenticationType)); + } + + context.Properties = properties; + + return Task.FromResult(new AuthenticationTicket(identity, properties)); + + + case "4": + //Logged out + _logger.WriteInformation("Cosign authentication handler. Response from Server: 4-Logged out."); + break; + case "5": + //Try a different server + _logger.WriteInformation("Cosign authentication handler. Response from Server: 5-Try different server."); + break; + default: + _logger.WriteInformation("Cosign authentication handler. Response from Server: Undefined."); + break; + + } + } + } + } + catch (Exception ex) + { + _logger.WriteError(ex.Message); + } + + + return Task.FromResult(new AuthenticationTicket(null, properties)); + } + + protected override Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) return Task.FromResult(null); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + // Only react to 401 if there is an authentication challenge for the authentication + // type of this handler. + if (challenge == null) return Task.FromResult(null); + var state = challenge.Properties; + + if (string.IsNullOrEmpty(state.RedirectUri)) + { + state.RedirectUri = Request.Uri.ToString(); + } + + var stateString = Options.StateDataFormat.Protect(state); + + var loginUrl = + "https://" + Options.CosignServer + "/?cosign-" + Options.ClientServer + + "&state=" + Uri.EscapeDataString(stateString) + + "&core=" + Options.IdentityServerHostInstance; + _logger.WriteInformation("Cosign authentication handler. Redirecting to cosign. " + loginUrl); + Response.Redirect(loginUrl); + + return Task.FromResult(null); + } + + public override async Task InvokeAsync() + { + // This is always invoked on each request. For passive middleware, only do anything if this is + // for our callback path when the user is redirected back from the authentication provider. + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + var ticket = await AuthenticateAsync(); + + if (ticket == null) return false; + Context.Authentication.SignIn(ticket.Properties, ticket.Identity); + + Response.Redirect(ticket.Properties.RedirectUri); + + // Prevent further processing by the owin pipeline. + return true; + // Let the rest of the pipeline run. + } + + + public static X509CertificateCollection GetCertificateCertificateCollection(string subjectName, + StoreName storeName, + StoreLocation storeLocation) + { + // The following code gets the cert from the keystore + var store = new X509Store(storeName, storeLocation); + store.Open(OpenFlags.ReadOnly); + var certCollection = + store.Certificates.Find(X509FindType.FindBySubjectName, + subjectName, + false); + return certCollection; + } + + private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Cosign/CosignAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Cosign/CosignAuthenticationMiddleware.cs similarity index 57% rename from Owin.Security.Providers/Cosign/CosignAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Cosign/CosignAuthenticationMiddleware.cs index d0af3a9..f7d5dce 100644 --- a/Owin.Security.Providers/Cosign/CosignAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Cosign/CosignAuthenticationMiddleware.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; @@ -8,29 +7,28 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.Cosign.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.Cosign { public class CosignAuthenticationMiddleware : AuthenticationMiddleware { - private readonly ILogger logger; + private readonly ILogger _logger; public CosignAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, CosignAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientServer)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ClientServer)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientServer")); - if (String.IsNullOrWhiteSpace(Options.CosignServer)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.CosignServer)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "CosignServer")); - if ((Options.CosignServicePort==0)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (Options.CosignServicePort==0) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "CosignServicePort")); - logger = app.CreateLogger(); - logger.WriteInformation("CosignAthenticationMiddleware has been created"); + _logger = app.CreateLogger(); + _logger.WriteInformation("CosignAuthenticationMiddleware has been created"); if (Options.Provider == null) Options.Provider = new CosignAuthenticationProvider(); @@ -38,22 +36,17 @@ namespace Owin.Security.Providers.Cosign { options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } - if (options.StateDataFormat == null) - { - var dataProtector = app.CreateDataProtector(typeof (CosignAuthenticationMiddleware).FullName, - options.AuthenticationType); - - options.StateDataFormat = new PropertiesDataFormat(dataProtector); - } - - + if (options.StateDataFormat != null) return; + var dataProtector = app.CreateDataProtector(typeof (CosignAuthenticationMiddleware).FullName, + options.AuthenticationType); + options.StateDataFormat = new PropertiesDataFormat(dataProtector); } protected override AuthenticationHandler CreateHandler() { - return new CosignAuthenticationHandler(logger); + return new CosignAuthenticationHandler(_logger); } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Cosign/CosignAuthenticationOptions.cs b/src/Owin.Security.Providers.Cosign/CosignAuthenticationOptions.cs similarity index 78% rename from Owin.Security.Providers/Cosign/CosignAuthenticationOptions.cs rename to src/Owin.Security.Providers.Cosign/CosignAuthenticationOptions.cs index 3d95137..8949a40 100644 --- a/Owin.Security.Providers/Cosign/CosignAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Cosign/CosignAuthenticationOptions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Net.Http; -using Microsoft.Owin; +using Microsoft.Owin; using Microsoft.Owin.Security; using Owin.Security.Providers.Cosign.Provider; @@ -10,18 +6,6 @@ namespace Owin.Security.Providers.Cosign { public class CosignAuthenticationOptions : AuthenticationOptions { - - - //full login url https://weblogin.umich.edu/?cosign-www.kbv.law.umich.edu&http://www.kbv.law.umich.edu/IdentityServer/core1 - //return url https://www.kbv.law.umich.edu/cosign/valid?cosign-www.kbv.law.umich.edu=COSIGN_VALUE&http://www.kbv.law.umich.edu/IdentityServer/core1 - //private const string LoginEndPoint = "https://weblogin.umich.edu/?"; - //private const string cosignServer = "weblogin.umich.edu"; - //private const string clientServer = "www.kbv.law.umich.edu"; - - - - - /// /// 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. diff --git a/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.csproj b/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.csproj new file mode 100644 index 0000000..27cacd6 --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {1F1F8D6B-7219-46FA-93D3-8D3061A6CBBF} + Library + Properties + Owin.Security.Providers.Cosign + Owin.Security.Providers.Cosign + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Cosign.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.nuspec b/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.nuspec new file mode 100644 index 0000000..66e3f3d --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/Owin.Security.Providers.Cosign.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Cosign + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Cosign OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Cosign + + + + + + + + + diff --git a/src/Owin.Security.Providers.Cosign/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Cosign/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..23250a0 --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Cosign")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Cosign")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("b3ced31e-252d-45e7-9b47-5e0c8d322934")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Cosign/Provider/CosignAuthenticatedContext.cs b/src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticatedContext.cs similarity index 69% rename from Owin.Security.Providers/Cosign/Provider/CosignAuthenticatedContext.cs rename to src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticatedContext.cs index 7b71e82..334143b 100644 --- a/Owin.Security.Providers/Cosign/Provider/CosignAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticatedContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; -using System.Collections.Specialized; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -23,8 +22,8 @@ namespace Owin.Security.Providers.Cosign.Provider /// Response from Cosign server public CosignAuthenticatedContext(IOwinContext context, string cosignResponse): base(context) { - CosignResposponse = cosignResponse; - string[] returnedData = CosignResposponse.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); + CosignResponse = cosignResponse; + var returnedData = CosignResponse.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); Id = TryGetValue(returnedData, "id"); UserId = TryGetValue(returnedData, "userid"); IpAddress = TryGetValue(returnedData, "ipaddress"); @@ -34,7 +33,7 @@ namespace Owin.Security.Providers.Cosign.Provider /// /// Gets the Cosign response /// - public string CosignResposponse { get; private set; } + public string CosignResponse { get; } /// /// Gets the Cosign ID @@ -72,42 +71,32 @@ namespace Owin.Security.Providers.Cosign.Provider switch (propertyName.ToLower()) { case "ipaddress": - if (cosignData.GetUpperBound(0)>=0) - return cosignData[1]; - return ""; + return cosignData.GetUpperBound(0)>=0 ? cosignData[1] : ""; case "userid": - if (cosignData.GetUpperBound(0) >= 1) - return cosignData[2]; - return ""; + return cosignData.GetUpperBound(0) >= 1 ? cosignData[2] : ""; case "id": - if (cosignData.GetUpperBound(0) >= 1) - return sha256_hash( cosignData[2]); - return ""; + return cosignData.GetUpperBound(0) >= 1 ? sha256_hash( cosignData[2]) : ""; case "realm": - if (cosignData.GetUpperBound(0) >=2) - return cosignData[3].Trim(new char[]{ Environment.NewLine.ToCharArray()[0]} ); - return ""; + return cosignData.GetUpperBound(0) >=2 ? cosignData[3].Trim(Environment.NewLine.ToCharArray()[0]) : ""; default: return ""; } - } - private static string sha256_hash(string value) { - StringBuilder Sb = new StringBuilder(); + var sb = new StringBuilder(); - using (SHA256 hash = SHA256.Create()) + using (var hash = SHA256.Create()) { - Encoding enc = Encoding.UTF8; - byte[] result = hash.ComputeHash(enc.GetBytes(value)); + var enc = Encoding.UTF8; + var result = hash.ComputeHash(enc.GetBytes(value)); - foreach (byte b in result) - Sb.Append(b.ToString("x2")); + foreach (var b in result) + sb.Append(b.ToString("x2")); } - return Sb.ToString(); + return sb.ToString(); } } } diff --git a/Owin.Security.Providers/Cosign/Provider/CosignAuthenticationProvider.cs b/src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Cosign/Provider/CosignAuthenticationProvider.cs rename to src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticationProvider.cs index c6bba0b..b0b474d 100644 --- a/Owin.Security.Providers/Cosign/Provider/CosignAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Cosign/Provider/CosignAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Cosign.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Cosign succesfully authenticates a user + /// Invoked whenever Cosign successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Cosign/Provider/CosignReturnEndpointContext.cs b/src/Owin.Security.Providers.Cosign/Provider/CosignReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Cosign/Provider/CosignReturnEndpointContext.cs rename to src/Owin.Security.Providers.Cosign/Provider/CosignReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Cosign/Provider/ICosignAuthenticationProvider.cs b/src/Owin.Security.Providers.Cosign/Provider/ICosignAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Cosign/Provider/ICosignAuthenticationProvider.cs rename to src/Owin.Security.Providers.Cosign/Provider/ICosignAuthenticationProvider.cs index 68cadb8..c903164 100644 --- a/Owin.Security.Providers/Cosign/Provider/ICosignAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Cosign/Provider/ICosignAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Cosign.Provider public interface ICosignAuthenticationProvider { /// - /// Invoked whenever Cosign succesfully authenticates a user + /// Invoked whenever Cosign successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Cosign/Resources.Designer.cs b/src/Owin.Security.Providers.Cosign/Resources.Designer.cs new file mode 100644 index 0000000..5995ee0 --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Cosign { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Cosign.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Cosign/Resources.resx b/src/Owin.Security.Providers.Cosign/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Cosign/packages.config b/src/Owin.Security.Providers.Cosign/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Cosign/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/DeviantArt/Constants.cs b/src/Owin.Security.Providers.DeviantArt/Constants.cs similarity index 100% rename from Owin.Security.Providers/DeviantArt/Constants.cs rename to src/Owin.Security.Providers.DeviantArt/Constants.cs diff --git a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationExtensions.cs b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationExtensions.cs similarity index 89% rename from Owin.Security.Providers/DeviantArt/DeviantAuthenticationExtensions.cs rename to src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationExtensions.cs index 3f5b8f1..3e1915e 100644 --- a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationExtensions.cs @@ -1,5 +1,4 @@ using System; -using Owin.Security.Providers.DeviantArt; namespace Owin.Security.Providers.DeviantArt { @@ -15,9 +14,9 @@ namespace Owin.Security.Providers.DeviantArt DeviantArtAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(DeviantArtAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationHandler.cs b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationHandler.cs new file mode 100644 index 0000000..4fb6466 --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationHandler.cs @@ -0,0 +1,234 @@ +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.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.DeviantArt.Provider; + +namespace Owin.Security.Providers.DeviantArt +{ + public class DeviantArtAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public DeviantArtAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret), + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint) + { + Content = new FormUrlEncodedContent(body) + }; + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the DeviantArt user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new DeviantArtAuthenticatedContext(Context, user, accessToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim("urn:DeviantArt:name", context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope.Distinct()); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&response_type=" + "code" + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new DeviantArtReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationMiddleware.cs b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/DeviantArt/DeviantAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationMiddleware.cs index 8792490..2ff9c51 100644 --- a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationMiddleware.cs @@ -7,51 +7,49 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.DeviantArt; using Owin.Security.Providers.DeviantArt.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.DeviantArt { public class DeviantArtAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public DeviantArtAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, DeviantArtAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new DeviantArtAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (DeviantArtAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin DeviantArt middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin DeviantArt middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -64,24 +62,22 @@ namespace Owin.Security.Providers.DeviantArt /// protected override AuthenticationHandler CreateHandler() { - return new DeviantArtAuthenticationHandler(httpClient, logger); + return new DeviantArtAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(DeviantArtAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(DeviantArtAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/DeviantArt/DeviantAuthenticationOptions.cs b/src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/DeviantArt/DeviantAuthenticationOptions.cs rename to src/Owin.Security.Providers.DeviantArt/DeviantAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.csproj b/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.csproj new file mode 100644 index 0000000..2483d05 --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {FABD2E54-976D-41F5-8800-DEE58ACC027C} + Library + Properties + Owin.Security.Providers.DeviantArt + Owin.Security.Providers.DeviantArt + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.DeviantArt.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.nuspec b/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.nuspec new file mode 100644 index 0000000..50039ce --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/Owin.Security.Providers.DeviantArt.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.DeviantArt + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a DeviantArt OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth DeviantArt + + + + + + + + + diff --git a/src/Owin.Security.Providers.DeviantArt/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.DeviantArt/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..380ebd7 --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.DeviantArt")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.DeviantArt")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("512d5564-84f0-4344-9652-4e0225ac6378")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/DeviantArt/Provider/DeviantAuthenticatedContext.cs b/src/Owin.Security.Providers.DeviantArt/Provider/DeviantAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/DeviantArt/Provider/DeviantAuthenticatedContext.cs rename to src/Owin.Security.Providers.DeviantArt/Provider/DeviantAuthenticatedContext.cs diff --git a/Owin.Security.Providers/DeviantArt/Provider/DeviantAuthenticationProvider.cs b/src/Owin.Security.Providers.DeviantArt/Provider/DeviantAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/DeviantArt/Provider/DeviantAuthenticationProvider.cs rename to src/Owin.Security.Providers.DeviantArt/Provider/DeviantAuthenticationProvider.cs index 2c61f51..4b5b80e 100644 --- a/Owin.Security.Providers/DeviantArt/Provider/DeviantAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.DeviantArt/Provider/DeviantAuthenticationProvider.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Owin.Security.Providers.DeviantArt; namespace Owin.Security.Providers.DeviantArt.Provider { @@ -29,7 +28,7 @@ namespace Owin.Security.Providers.DeviantArt.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever DeviantArt succesfully authenticates a user + /// Invoked whenever DeviantArt successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/DeviantArt/Provider/DeviantReturnEndpointContext.cs b/src/Owin.Security.Providers.DeviantArt/Provider/DeviantReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/DeviantArt/Provider/DeviantReturnEndpointContext.cs rename to src/Owin.Security.Providers.DeviantArt/Provider/DeviantReturnEndpointContext.cs diff --git a/Owin.Security.Providers/DeviantArt/Provider/IDeviantAuthenticationProvider.cs b/src/Owin.Security.Providers.DeviantArt/Provider/IDeviantAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/DeviantArt/Provider/IDeviantAuthenticationProvider.cs rename to src/Owin.Security.Providers.DeviantArt/Provider/IDeviantAuthenticationProvider.cs index 2f3bf98..e5526d5 100644 --- a/Owin.Security.Providers/DeviantArt/Provider/IDeviantAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.DeviantArt/Provider/IDeviantAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.DeviantArt.Provider public interface IDeviantArtAuthenticationProvider { /// - /// Invoked whenever DeviantArt succesfully authenticates a user + /// Invoked whenever DeviantArt successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.DeviantArt/Resources.Designer.cs b/src/Owin.Security.Providers.DeviantArt/Resources.Designer.cs new file mode 100644 index 0000000..bb5114e --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.DeviantArt { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.DeviantArt.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.DeviantArt/Resources.resx b/src/Owin.Security.Providers.DeviantArt/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.DeviantArt/packages.config b/src/Owin.Security.Providers.DeviantArt/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.DeviantArt/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/DoYouBuzz/Constants.cs b/src/Owin.Security.Providers.DoYouBuzz/Constants.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Constants.cs rename to src/Owin.Security.Providers.DoYouBuzz/Constants.cs diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs rename to src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs index 95f1e17..b6134da 100644 --- a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationExtensions.cs @@ -7,9 +7,9 @@ namespace Owin.Security.Providers.DoYouBuzz public static IAppBuilder UseDoYouBuzzAuthentication(this IAppBuilder app, DoYouBuzzAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(DoYouBuzzAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationHandler.cs similarity index 89% rename from Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs rename to src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationHandler.cs index da85ce5..9d01a4a 100644 --- a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationHandler.cs @@ -188,16 +188,14 @@ namespace Owin.Security.Providers.DoYouBuzz Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } @@ -218,14 +216,14 @@ namespace Owin.Security.Providers.DoYouBuzz { "oauth_version", "1.0" } }; - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(GetParameters(authorizationParts))); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(GetParameters(authorizationParts))); - var signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, null, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -279,14 +277,14 @@ namespace Owin.Security.Providers.DoYouBuzz parameterBuilder.Length--; var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); authorizationParts.Remove("oauth_verifier"); @@ -358,14 +356,14 @@ namespace Owin.Security.Providers.DoYouBuzz parameterBuilder.Length--; var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(UserInfoEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(UserInfoEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); authorizationParts.Remove("oauth_verifier"); @@ -417,8 +415,8 @@ namespace Owin.Security.Providers.DoYouBuzz private static string GenerateTimeStamp() { - var secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; - return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + var secondsSinceUnixEpochStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpochStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData) diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs similarity index 95% rename from Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs index d77d0e0..daa37b8 100644 --- a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationMiddleware.cs @@ -9,7 +9,6 @@ using System; using System.Globalization; using System.Net.Http; using Owin.Security.Providers.DoYouBuzz.Messages; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.DoYouBuzz { @@ -56,9 +55,12 @@ namespace Owin.Security.Providers.DoYouBuzz Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } - _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); - _httpClient.Timeout = Options.BackchannelTimeout; - _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + // 10 MB _httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin DoYouBuzz middleware"); _httpClient.DefaultRequestHeaders.ExpectContinue = false; diff --git a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationOptions.cs similarity index 98% rename from Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs rename to src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationOptions.cs index 3bd4d6d..0484491 100644 --- a/Owin.Security.Providers/DoYouBuzz/DoYouBuzzAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.DoYouBuzz/DoYouBuzzAuthenticationOptions.cs @@ -18,6 +18,7 @@ namespace Owin.Security.Providers.DoYouBuzz : base(Constants.DefaultAuthenticationType) { Caption = Constants.DefaultAuthenticationType; + // ReSharper disable once StringLiteralTypo CallbackPath = new PathString("/signin-doyoubuzz"); AuthenticationMode = AuthenticationMode.Passive; BackchannelTimeout = TimeSpan.FromSeconds(60); diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/AccessToken.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/AccessToken.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Messages/AccessToken.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/AccessToken.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/RequestToken.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/RequestToken.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Messages/RequestToken.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/RequestToken.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/RequestTokenSerializer.cs similarity index 85% rename from Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/RequestTokenSerializer.cs index 3fe1a02..3139cf4 100644 --- a/Owin.Security.Providers/DoYouBuzz/Messages/RequestTokenSerializer.cs +++ b/src/Owin.Security.Providers.DoYouBuzz/Messages/RequestTokenSerializer.cs @@ -1,5 +1,4 @@ -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.DataHandler.Serializer; +using Microsoft.Owin.Security.DataHandler.Serializer; using System; using System.IO; @@ -55,11 +54,11 @@ namespace Owin.Security.Providers.DoYouBuzz.Messages { if (writer == null) { - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); } if (token == null) { - throw new ArgumentNullException("token"); + throw new ArgumentNullException(nameof(token)); } writer.Write(FormatVersion); @@ -78,7 +77,7 @@ namespace Owin.Security.Providers.DoYouBuzz.Messages { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (reader.ReadInt32() != FormatVersion) @@ -86,10 +85,10 @@ namespace Owin.Security.Providers.DoYouBuzz.Messages return null; } - string token = reader.ReadString(); - string tokenSecret = reader.ReadString(); - bool callbackConfirmed = reader.ReadBoolean(); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var token = reader.ReadString(); + var tokenSecret = reader.ReadString(); + var callbackConfirmed = reader.ReadBoolean(); + var properties = PropertiesSerializer.Read(reader); if (properties == null) { return null; diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/Resume.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/Resume.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Messages/Resume.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/Resume.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/Serializer.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/Serializer.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Messages/Serializer.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/Serializer.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Messages/UserInfo.cs b/src/Owin.Security.Providers.DoYouBuzz/Messages/UserInfo.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Messages/UserInfo.cs rename to src/Owin.Security.Providers.DoYouBuzz/Messages/UserInfo.cs diff --git a/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.csproj b/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.csproj new file mode 100644 index 0000000..bb45401 --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + {4550D8BD-05A7-44F8-BBC0-C3D8E7AF2912} + Library + Properties + Owin.Security.Providers.DoYouBuzz + Owin.Security.Providers.DoYouBuzz + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.DoYouBuzz.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.nuspec b/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.nuspec new file mode 100644 index 0000000..55985aa --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/Owin.Security.Providers.DoYouBuzz.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.DoYouBuzz + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a DoYouBuzz OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth DoYouBuzz + + + + + + + + + diff --git a/src/Owin.Security.Providers.DoYouBuzz/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.DoYouBuzz/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bed40e5 --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.DoYouBuzz")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.DoYouBuzz")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("82e42454-a6ec-452e-bf23-5787f808cf39")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs b/src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs rename to src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzApplyRedirectContext.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs b/src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs rename to src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzAuthenticatedContext.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs b/src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs rename to src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzAuthenticationProvider.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs b/src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs rename to src/Owin.Security.Providers.DoYouBuzz/Provider/DoYouBuzzReturnEndpointContext.cs diff --git a/Owin.Security.Providers/DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs b/src/Owin.Security.Providers.DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs rename to src/Owin.Security.Providers.DoYouBuzz/Provider/IDoYouBuzzAuthenticationProvider.cs diff --git a/src/Owin.Security.Providers.DoYouBuzz/Resources.Designer.cs b/src/Owin.Security.Providers.DoYouBuzz/Resources.Designer.cs new file mode 100644 index 0000000..77459d0 --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.DoYouBuzz { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.DoYouBuzz.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.DoYouBuzz/Resources.resx b/src/Owin.Security.Providers.DoYouBuzz/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.DoYouBuzz/packages.config b/src/Owin.Security.Providers.DoYouBuzz/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.DoYouBuzz/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/Constants.cs b/src/Owin.Security.Providers.Dropbox/Constants.cs similarity index 100% rename from Owin.Security.Providers/Dropbox/Constants.cs rename to src/Owin.Security.Providers.Dropbox/Constants.cs diff --git a/Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Dropbox/DropboxAuthenticationExtensions.cs index 7fca07e..4ca953e 100644 --- a/Owin.Security.Providers/Dropbox/DropboxAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Dropbox DropboxAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(DropboxAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationHandler.cs b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationHandler.cs new file mode 100644 index 0000000..86070a8 --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationHandler.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +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.Dropbox +{ + public class DropboxAuthenticationHandler : AuthenticationHandler + { + private const string StateCookie = "_DropboxState"; + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.dropbox.com/1/oauth2/token"; + private const string UserInfoEndpoint = "https://api.dropbox.com/1/account/info"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public DropboxAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + + var 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); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.AppKey), + new KeyValuePair("client_secret", Options.AppSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the Dropbox user + var graphResponse = await _httpClient.GetAsync( + UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new DropboxAuthenticatedContext(Context, user, accessToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var authorizationEndpoint = + "https://www.dropbox.com/1/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.AppKey) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri); + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + Response.StatusCode = 302; + Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(properties), cookieOptions); + Response.Headers.Set("Location", 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new DropboxReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Dropbox/DropboxAuthenticationMiddleware.cs index 11e022e..5e62807 100644 --- a/Owin.Security.Providers/Dropbox/DropboxAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationMiddleware.cs @@ -7,43 +7,42 @@ 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.Dropbox { public class DropboxAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public DropboxAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, DropboxAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.AppKey)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppKey)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppKey")); - if (String.IsNullOrWhiteSpace(Options.AppSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new DropboxAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (DropboxAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -60,24 +59,22 @@ namespace Owin.Security.Providers.Dropbox /// protected override AuthenticationHandler CreateHandler() { - return new DropboxAuthenticationHandler(httpClient, logger); + return new DropboxAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(DropboxAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(DropboxAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Dropbox/DropboxAuthenticationOptions.cs b/src/Owin.Security.Providers.Dropbox/DropboxAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Dropbox/DropboxAuthenticationOptions.cs rename to src/Owin.Security.Providers.Dropbox/DropboxAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.csproj b/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.csproj new file mode 100644 index 0000000..20d7d33 --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {CEF697B1-3651-49E5-9060-65F2E26C039C} + Library + Properties + Owin.Security.Providers.Dropbox + Owin.Security.Providers.Dropbox + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Dropbox.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.nuspec b/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.nuspec new file mode 100644 index 0000000..62fe770 --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/Owin.Security.Providers.Dropbox.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Dropbox + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Dropbox OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Dropbox + + + + + + + + + diff --git a/src/Owin.Security.Providers.Dropbox/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Dropbox/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4e48f42 --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Dropbox")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Dropbox")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("06b96939-76a2-4247-ad54-3fd72ce0416e")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs b/src/Owin.Security.Providers.Dropbox/Provider/DropboxAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs rename to src/Owin.Security.Providers.Dropbox/Provider/DropboxAuthenticatedContext.cs index a154eba..b9818bc 100644 --- a/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Dropbox/Provider/DropboxAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticationProvider.cs b/src/Owin.Security.Providers.Dropbox/Provider/DropboxAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Dropbox/Provider/DropboxAuthenticationProvider.cs rename to src/Owin.Security.Providers.Dropbox/Provider/DropboxAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.cs b/src/Owin.Security.Providers.Dropbox/Provider/DropboxReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Dropbox/Provider/DropboxReturnEndpointContext.cs rename to src/Owin.Security.Providers.Dropbox/Provider/DropboxReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs b/src/Owin.Security.Providers.Dropbox/Provider/IDropboxAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Dropbox/Provider/IDropboxAuthenticationProvider.cs rename to src/Owin.Security.Providers.Dropbox/Provider/IDropboxAuthenticationProvider.cs diff --git a/src/Owin.Security.Providers.Dropbox/Resources.Designer.cs b/src/Owin.Security.Providers.Dropbox/Resources.Designer.cs new file mode 100644 index 0000000..7de60d4 --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Dropbox { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Dropbox.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Dropbox/Resources.resx b/src/Owin.Security.Providers.Dropbox/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Dropbox/packages.config b/src/Owin.Security.Providers.Dropbox/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Dropbox/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/EVEOnline/Constants.cs b/src/Owin.Security.Providers.EVEOnline/Constants.cs similarity index 72% rename from Owin.Security.Providers/EVEOnline/Constants.cs rename to src/Owin.Security.Providers.EVEOnline/Constants.cs index 96305ea..43ce5d2 100644 --- a/Owin.Security.Providers/EVEOnline/Constants.cs +++ b/src/Owin.Security.Providers.EVEOnline/Constants.cs @@ -1,4 +1,4 @@ -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { internal static class Constants { diff --git a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationExtensions.cs b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationExtensions.cs similarity index 95% rename from Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationExtensions.cs rename to src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationExtensions.cs index 7435795..63d6331 100644 --- a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { public static class EveOnlineAuthenticationExtensions { diff --git a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationHandler.cs b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationHandler.cs similarity index 66% rename from Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationHandler.cs rename to src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationHandler.cs index dac50df..e20facb 100644 --- a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationHandler.cs @@ -10,7 +10,7 @@ using Microsoft.Owin.Security.Infrastructure; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { public class EveOnlineAuthenticationHandler : AuthenticationHandler { @@ -21,7 +21,7 @@ namespace Owin.Security.Providers.EveOnline private string _oauthAuthEndpoint ; private string _serverHost ; - private const string _serverScheme = "https://"; + private const string ServerScheme = "https://"; private readonly ILogger _logger; private readonly HttpClient _httpClient; @@ -38,16 +38,18 @@ namespace Owin.Security.Providers.EveOnline switch (Options.Server) { case Server.Singularity: + // ReSharper disable StringLiteralTypo _serverHost = "sisilogin.testeveonline.com"; + // ReSharper restore StringLiteralTypo break; default: _serverHost = "login.eveonline.com"; break; } - _tokenEndpoint = _serverScheme + _serverHost + "/oauth/token"; - _oauthAuthEndpoint = _serverScheme + _serverHost + "/oauth/authorize"; - _characterIdEndpoint = _serverScheme + _serverHost + "/oauth/verify"; + _tokenEndpoint = ServerScheme + _serverHost + "/oauth/token"; + _oauthAuthEndpoint = ServerScheme + _serverHost + "/oauth/authorize"; + _characterIdEndpoint = ServerScheme + _serverHost + "/oauth/verify"; }); } @@ -181,48 +183,46 @@ namespace Owin.Security.Providers.EveOnline var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - var baseUri = - Request.Scheme + - Uri.SchemeDelimiter + - Request.Host + - Request.PathBase; - - var currentUri = - baseUri + - Request.Path + - Request.QueryString; - - var redirectUri = - baseUri + - Options.CallbackPath; - - var properties = challenge.Properties; - if (string.IsNullOrEmpty(properties.RedirectUri)) - { - properties.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - // comma separated - var scope = string.Join(" ", Options.Scope); - - var state = Options.StateDataFormat.Protect(properties); - - var authorizationEndpoint = - _oauthAuthEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + _oauthAuthEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -233,52 +233,47 @@ namespace Owin.Security.Providers.EveOnline private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // TODO: error responses - - var ticket = await AuthenticateAsync(); - if (ticket == null) - { - _logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new EveOnlineReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new EveOnlineReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } diff --git a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationMiddleware.cs b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationMiddleware.cs similarity index 71% rename from Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationMiddleware.cs index 6cf75f5..ff28d13 100644 --- a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationMiddleware.cs @@ -7,9 +7,8 @@ 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.EveOnline +namespace Owin.Security.Providers.EVEOnline { public class EveOnlineAuthenticationMiddleware : AuthenticationMiddleware { @@ -19,11 +18,11 @@ namespace Owin.Security.Providers.EveOnline public EveOnlineAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, EveOnlineAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); _logger = app.CreateLogger(); @@ -39,7 +38,7 @@ namespace Owin.Security.Providers.EveOnline Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) @@ -55,7 +54,7 @@ namespace Owin.Security.Providers.EveOnline /// /// /// An configured with the - /// supplied to the constructor. + /// supplied to the constructor. /// protected override AuthenticationHandler CreateHandler() { @@ -64,19 +63,17 @@ namespace Owin.Security.Providers.EveOnline private static HttpMessageHandler ResolveHttpMessageHandler(EveOnlineAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationOptions.cs b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationOptions.cs similarity index 98% rename from Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationOptions.cs rename to src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationOptions.cs index 5a97764..0a5f5e3 100644 --- a/Owin.Security.Providers/EVEOnline/EVEOnlineAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.EVEOnline/EVEOnlineAuthenticationOptions.cs @@ -4,7 +4,7 @@ using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { public enum Server { diff --git a/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.csproj b/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.csproj new file mode 100644 index 0000000..692f2a2 --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {F5DC23F4-5042-4024-9E34-ACA648602BA0} + Library + Properties + Owin.Security.Providers.EVEOnline + Owin.Security.Providers.EVEOnline + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.EVEOnline.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.nuspec b/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.nuspec new file mode 100644 index 0000000..74e15bd --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/Owin.Security.Providers.EVEOnline.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.EVEOnline + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a EVEOnline OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth EVEOnline + + + + + + + + + diff --git a/src/Owin.Security.Providers.EVEOnline/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.EVEOnline/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e71ad58 --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.EVEOnline")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.EVEOnline")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("c070e66e-fb76-423e-9bfb-32dc05bcf2f5")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs similarity index 94% rename from Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs rename to src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs index 185b1e8..3ba0a8f 100644 --- a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticatedContext.cs @@ -8,7 +8,7 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; using Newtonsoft.Json.Linq; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { /// /// Contains information about the login session as well as the user . @@ -22,6 +22,7 @@ namespace Owin.Security.Providers.EveOnline /// The JSON-serialized userId /// /// EveOnline Access token + /// /// Seconds until expiration public EveOnlineAuthenticatedContext(IOwinContext context, JObject characterData, string accessToken, string refreshToken, string expires) : base(context) @@ -31,7 +32,7 @@ namespace Owin.Security.Providers.EveOnline RefreshToken = refreshToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs similarity index 97% rename from Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs rename to src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs index efec8be..6f7dcb2 100644 --- a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineAuthenticationProvider.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { /// /// Default implementation. diff --git a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs similarity index 94% rename from Owin.Security.Providers/EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs rename to src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs index 9fcdc23..5b6a6bc 100644 --- a/Owin.Security.Providers/EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs +++ b/src/Owin.Security.Providers.EVEOnline/Provider/EVEOnlineReturnEndpointContext.cs @@ -4,7 +4,7 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { /// /// Provides context information to middleware providers. diff --git a/Owin.Security.Providers/EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs b/src/Owin.Security.Providers.EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs similarity index 88% rename from Owin.Security.Providers/EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs rename to src/Owin.Security.Providers.EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs index 1355655..a23f59f 100644 --- a/Owin.Security.Providers/EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.EVEOnline/Provider/IEVEOnlineAuthenticationProvider.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; -namespace Owin.Security.Providers.EveOnline +namespace Owin.Security.Providers.EVEOnline { public interface IEveOnlineAuthenticationProvider { /// - /// Invoked whenever Battle.net succesfully authenticates a user + /// Invoked whenever Battle.net successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.EVEOnline/Resources.Designer.cs b/src/Owin.Security.Providers.EVEOnline/Resources.Designer.cs new file mode 100644 index 0000000..9650f0f --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.EVEOnline { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.EVEOnline.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.EVEOnline/Resources.resx b/src/Owin.Security.Providers.EVEOnline/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.EVEOnline/packages.config b/src/Owin.Security.Providers.EVEOnline/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.EVEOnline/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Fitbit/Constants.cs b/src/Owin.Security.Providers.Fitbit/Constants.cs similarity index 100% rename from Owin.Security.Providers/Fitbit/Constants.cs rename to src/Owin.Security.Providers.Fitbit/Constants.cs diff --git a/Owin.Security.Providers/Fitbit/FitbitAuthenticationExtensions.cs b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Fitbit/FitbitAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Fitbit/FitbitAuthenticationExtensions.cs index 633a8a5..1234bf7 100644 --- a/Owin.Security.Providers/Fitbit/FitbitAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Fitbit FitbitAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(FitbitAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationHandler.cs b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationHandler.cs new file mode 100644 index 0000000..765786e --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationHandler.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler.Encoder; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.Fitbit.Provider; + +namespace Owin.Security.Providers.Fitbit +{ + public class FitbitAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public FitbitAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("code", code), + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("redirect_uri", redirectUri) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", new Base64TextEncoder().Encode(Encoding.ASCII.GetBytes(Options.ClientId + ":" + Options.ClientSecret))); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + requestMessage.Content = new FormUrlEncodedContent(body); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var refreshToken = (string) response.refresh_token; + + // Get the user info + var userInfoRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserEndpoint); + userInfoRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + userInfoRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userInfoResponse = await _httpClient.SendAsync(userInfoRequest); + userInfoResponse.EnsureSuccessStatusCode(); + text = await userInfoResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new FitbitAuthenticatedContext(Context, user, accessToken, refreshToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&response_type=" + Uri.EscapeDataString("code") + + "&scope=" + Uri.EscapeDataString(scope) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&prompt=" + Uri.EscapeDataString(Options.Prompt) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new FitbitReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Fitbit/FitbitAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationMiddleware.cs similarity index 70% rename from Owin.Security.Providers/Fitbit/FitbitAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Fitbit/FitbitAuthenticationMiddleware.cs index e984b35..634a855 100644 --- a/Owin.Security.Providers/Fitbit/FitbitAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationMiddleware.cs @@ -8,49 +8,48 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.Fitbit.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.Fitbit { public class FitbitAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public FitbitAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, FitbitAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new FitbitAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (FitbitAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(new WebRequestHandler()) + _httpClient = new HttpClient(new WebRequestHandler()) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Fitbit middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Fitbit middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -63,7 +62,7 @@ namespace Owin.Security.Providers.Fitbit /// protected override AuthenticationHandler CreateHandler() { - return new FitbitAuthenticationHandler(httpClient, logger); + return new FitbitAuthenticationHandler(_httpClient, _logger); } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Fitbit/FitbitAuthenticationOptions.cs b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/Fitbit/FitbitAuthenticationOptions.cs rename to src/Owin.Security.Providers.Fitbit/FitbitAuthenticationOptions.cs index 1a04bba..6f61596 100644 --- a/Owin.Security.Providers/Fitbit/FitbitAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Fitbit/FitbitAuthenticationOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; using Owin.Security.Providers.Fitbit.Provider; diff --git a/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.csproj b/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.csproj new file mode 100644 index 0000000..afac0d5 --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {CA44D014-5A74-4749-A891-1F711FD3A266} + Library + Properties + Owin.Security.Providers.Fitbit + Owin.Security.Providers.Fitbit + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Fitbit.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.nuspec b/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.nuspec new file mode 100644 index 0000000..eeda445 --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/Owin.Security.Providers.Fitbit.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Fitbit + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Fitbit OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Fitbit + + + + + + + + + diff --git a/src/Owin.Security.Providers.Fitbit/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Fitbit/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0cac3f1 --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Fitbit")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Fitbit")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("fbb6c87e-d738-43db-beeb-acebb56ed053")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Fitbit/Provider/FitbitAuthenticatedContext.cs b/src/Owin.Security.Providers.Fitbit/Provider/FitbitAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/Fitbit/Provider/FitbitAuthenticatedContext.cs rename to src/Owin.Security.Providers.Fitbit/Provider/FitbitAuthenticatedContext.cs diff --git a/Owin.Security.Providers/Fitbit/Provider/FitbitAuthenticationProvider.cs b/src/Owin.Security.Providers.Fitbit/Provider/FitbitAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Fitbit/Provider/FitbitAuthenticationProvider.cs rename to src/Owin.Security.Providers.Fitbit/Provider/FitbitAuthenticationProvider.cs index 6cbcbaf..1abef03 100644 --- a/Owin.Security.Providers/Fitbit/Provider/FitbitAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Fitbit/Provider/FitbitAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Fitbit.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Fitbit succesfully authenticates a user + /// Invoked whenever Fitbit successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Fitbit/Provider/FitbitReturnEndpointContext.cs b/src/Owin.Security.Providers.Fitbit/Provider/FitbitReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Fitbit/Provider/FitbitReturnEndpointContext.cs rename to src/Owin.Security.Providers.Fitbit/Provider/FitbitReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Fitbit/Provider/IFitbitAuthenticationProvider.cs b/src/Owin.Security.Providers.Fitbit/Provider/IFitbitAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Fitbit/Provider/IFitbitAuthenticationProvider.cs rename to src/Owin.Security.Providers.Fitbit/Provider/IFitbitAuthenticationProvider.cs index 7d3eca2..e25ea7d 100644 --- a/Owin.Security.Providers/Fitbit/Provider/IFitbitAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Fitbit/Provider/IFitbitAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Fitbit.Provider public interface IFitbitAuthenticationProvider { /// - /// Invoked whenever Fitbit succesfully authenticates a user + /// Invoked whenever Fitbit successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Fitbit/Resources.Designer.cs b/src/Owin.Security.Providers.Fitbit/Resources.Designer.cs new file mode 100644 index 0000000..e29b86d --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Fitbit { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Fitbit.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Fitbit/Resources.resx b/src/Owin.Security.Providers.Fitbit/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Fitbit/packages.config b/src/Owin.Security.Providers.Fitbit/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Fitbit/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Flickr/Constants.cs b/src/Owin.Security.Providers.Flickr/Constants.cs similarity index 100% rename from Owin.Security.Providers/Flickr/Constants.cs rename to src/Owin.Security.Providers.Flickr/Constants.cs diff --git a/Owin.Security.Providers/Flickr/FlickrAuthenticationExtensions.cs b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Flickr/FlickrAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Flickr/FlickrAuthenticationExtensions.cs index b1fe1d8..65cacc9 100644 --- a/Owin.Security.Providers/Flickr/FlickrAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Flickr FlickrAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(FlickrAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Flickr/FlickrAuthenticationHandler.cs b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationHandler.cs similarity index 65% rename from Owin.Security.Providers/Flickr/FlickrAuthenticationHandler.cs rename to src/Owin.Security.Providers.Flickr/FlickrAuthenticationHandler.cs index fa920c4..460655b 100644 --- a/Owin.Security.Providers/Flickr/FlickrAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationHandler.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Net.Http; -using System.Net.Http.Headers; -using System.Runtime.InteropServices; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -15,7 +12,6 @@ using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; -using Newtonsoft.Json.Linq; using Owin.Security.Providers.Flickr.Messages; namespace Owin.Security.Providers.Flickr @@ -29,13 +25,13 @@ namespace Owin.Security.Providers.Flickr private const string AuthenticationEndpoint = "https://www.flickr.com/services/oauth/authorize?oauth_token="; private const string AccessTokenEndpoint = "https://www.flickr.com/services/oauth/access_token"; - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public FlickrAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } public override async Task InvokeAsync() @@ -52,58 +48,60 @@ namespace Owin.Security.Providers.Flickr AuthenticationProperties properties = null; try { - IReadableStringCollection query = Request.Query; - string protectedRequestToken = Request.Cookies[StateCookie]; + var query = Request.Query; + var protectedRequestToken = Request.Cookies[StateCookie]; - RequestToken requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); + var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); if (requestToken == null) { - logger.WriteWarning("Invalid state"); + _logger.WriteWarning("Invalid state"); return null; } properties = requestToken.Properties; - string returnedToken = query.Get("oauth_token"); + var returnedToken = query.Get("oauth_token"); if (string.IsNullOrWhiteSpace(returnedToken)) { - logger.WriteWarning("Missing oauth_token"); + _logger.WriteWarning("Missing oauth_token"); return new AuthenticationTicket(null, properties); } if (returnedToken != requestToken.Token) { - logger.WriteWarning("Unmatched token"); + _logger.WriteWarning("Unmatched token"); return new AuthenticationTicket(null, properties); } - string oauthVerifier = query.Get("oauth_verifier"); + var oauthVerifier = query.Get("oauth_verifier"); if (string.IsNullOrWhiteSpace(oauthVerifier)) { - logger.WriteWarning("Missing or blank oauth_verifier"); + _logger.WriteWarning("Missing or blank oauth_verifier"); return new AuthenticationTicket(null, properties); } - AccessToken accessToken = await ObtainAccessTokenAsync(Options.AppKey, Options.AppSecret, requestToken, oauthVerifier); + var accessToken = await ObtainAccessTokenAsync(Options.AppKey, Options.AppSecret, requestToken, oauthVerifier); - var context = new FlickrAuthenticatedContext(Context, accessToken); + var context = new FlickrAuthenticatedContext(Context, accessToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!String.IsNullOrEmpty(context.UserId)) + if (!string.IsNullOrEmpty(context.UserId)) { context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, XmlSchemaString, Options.AuthenticationType)); } - if(!String.IsNullOrEmpty(context.UserName)) + if(!string.IsNullOrEmpty(context.UserName)) { context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName, XmlSchemaString, Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.FullName)) + if (!string.IsNullOrEmpty(context.FullName)) { context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.FullName, XmlSchemaString, Options.AuthenticationType)); @@ -118,7 +116,7 @@ namespace Owin.Security.Providers.Flickr } catch (Exception ex) { - logger.WriteError("Authentication failed", ex); + _logger.WriteError("Authentication failed", ex); return new AuthenticationTicket(null, properties); } } @@ -130,24 +128,24 @@ namespace Owin.Security.Providers.Flickr return; } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge != null) { - string requestPrefix = Request.Scheme + "://" + Request.Host; - string callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; - AuthenticationProperties extra = challenge.Properties; + var extra = challenge.Properties; if (string.IsNullOrEmpty(extra.RedirectUri)) { extra.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; } - RequestToken requestToken = await ObtainRequestTokenAsync(Options.AppKey, Options.AppSecret, callBackUrl, extra); + var requestToken = await ObtainRequestTokenAsync(Options.AppKey, Options.AppSecret, callBackUrl, extra); if (requestToken.CallbackConfirmed) { - string FlickrAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token + "&perms=" + Options.Scope; + var flickrAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token + "&perms=" + Options.Scope; var cookieOptions = new CookieOptions { @@ -157,21 +155,21 @@ namespace Owin.Security.Providers.Flickr Response.StatusCode = 302; Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions); - Response.Headers.Set("Location", FlickrAuthenticationEndpoint); + Response.Headers.Set("Location", flickrAuthenticationEndpoint); } else { - logger.WriteError("requestToken CallbackConfirmed!=true"); + _logger.WriteError("requestToken CallbackConfirmed!=true"); } } } public async Task InvokeReturnPathAsync() { - AuthenticationTicket model = await AuthenticateAsync(); + var model = await AuthenticateAsync(); if (model == null) { - logger.WriteWarning("Invalid return state, unable to redirect."); + _logger.WriteWarning("Invalid return state, unable to redirect."); Response.StatusCode = 500; return true; } @@ -187,7 +185,7 @@ namespace Owin.Security.Providers.Flickr if (context.SignInAsAuthenticationType != null && context.Identity != null) { - ClaimsIdentity signInIdentity = context.Identity; + var signInIdentity = context.Identity; if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) { signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); @@ -195,30 +193,28 @@ namespace Owin.Security.Providers.Flickr Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } - private async Task ObtainRequestTokenAsync(string AppKey, string AppSecret, string callBackUri, AuthenticationProperties properties) + private async Task ObtainRequestTokenAsync(string appKey, string appSecret, string callBackUri, AuthenticationProperties properties) { - logger.WriteVerbose("ObtainRequestToken"); + _logger.WriteVerbose("ObtainRequestToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { { "oauth_callback", callBackUri }, - { "oauth_consumer_key", AppKey }, + { "oauth_consumer_key", appKey }, { "oauth_nonce", nonce }, { "oauth_signature_method", "HMAC-SHA1" }, { "oauth_timestamp", GenerateTimeStamp() }, @@ -231,16 +227,16 @@ namespace Owin.Security.Providers.Flickr parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(AppSecret, null, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(appSecret, null, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); //-- @@ -256,11 +252,11 @@ namespace Owin.Security.Providers.Flickr var request = new HttpRequestMessage(HttpMethod.Post, RequestTokenEndpoint); request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); - HttpResponseMessage response = await httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); response.EnsureSuccessStatusCode(); - string responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseParameters = WebHelpers.ParseForm(responseText); if (string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.InvariantCulture)) { return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties }; @@ -269,15 +265,15 @@ namespace Owin.Security.Providers.Flickr return new RequestToken(); } - private async Task ObtainAccessTokenAsync(string AppKey, string AppSecret, RequestToken token, string verifier) + private async Task ObtainAccessTokenAsync(string appKey, string appSecret, RequestToken token, string verifier) { - logger.WriteVerbose("ObtainAccessToken"); + _logger.WriteVerbose("ObtainAccessToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { - { "oauth_consumer_key", AppKey }, + { "oauth_consumer_key", appKey }, { "oauth_nonce", nonce }, { "oauth_signature_method", "HMAC-SHA1" }, { "oauth_token", token.Token }, @@ -292,16 +288,16 @@ namespace Owin.Security.Providers.Flickr parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(AppSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(appSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -323,16 +319,16 @@ namespace Owin.Security.Providers.Flickr request.Content = new FormUrlEncodedContent(formPairs); - HttpResponseMessage response = await httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); if (!response.IsSuccessStatusCode) { - logger.WriteError("AccessToken request failed with a status code of " + response.StatusCode); + _logger.WriteError("AccessToken request failed with a status code of " + response.StatusCode); response.EnsureSuccessStatusCode(); // throw } - string responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseText = await response.Content.ReadAsStringAsync(); + var responseParameters = WebHelpers.ParseForm(responseText); return new AccessToken { @@ -346,20 +342,20 @@ namespace Owin.Security.Providers.Flickr private static string GenerateTimeStamp() { - TimeSpan secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; - return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + var secondsSinceUnixEpochStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpochStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } - private static string ComputeSignature(string AppSecret, string tokenSecret, string signatureData) + private static string ComputeSignature(string appSecret, string tokenSecret, string signatureData) { using (var algorithm = new HMACSHA1()) { algorithm.Key = Encoding.ASCII.GetBytes( string.Format(CultureInfo.InvariantCulture, "{0}&{1}", - Uri.EscapeDataString(AppSecret), + Uri.EscapeDataString(appSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret))); - byte[] hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); + var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); return Convert.ToBase64String(hash); } } diff --git a/Owin.Security.Providers/Flickr/FlickrAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationMiddleware.cs similarity index 63% rename from Owin.Security.Providers/Flickr/FlickrAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Flickr/FlickrAuthenticationMiddleware.cs index 3eb4be0..558d9d0 100644 --- a/Owin.Security.Providers/Flickr/FlickrAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationMiddleware.cs @@ -7,7 +7,6 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.Flickr.Messages; using Microsoft.Owin.Security.DataHandler.Encoder; @@ -15,28 +14,28 @@ namespace Owin.Security.Providers.Flickr { public class FlickrAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public FlickrAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, FlickrAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.AppKey)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppKey)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppKey")); - if (String.IsNullOrWhiteSpace(Options.AppSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new FlickrAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(FlickrAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new SecureDataFormat( @@ -45,10 +44,10 @@ namespace Owin.Security.Providers.Flickr TextEncodings.Base64Url); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -65,24 +64,22 @@ namespace Owin.Security.Providers.Flickr /// protected override AuthenticationHandler CreateHandler() { - return new FlickrAuthenticationHandler(httpClient, logger); + return new FlickrAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(FlickrAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(FlickrAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Flickr/FlickrAuthenticationOptions.cs b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/Flickr/FlickrAuthenticationOptions.cs rename to src/Owin.Security.Providers.Flickr/FlickrAuthenticationOptions.cs index 9dff4c5..dac263a 100644 --- a/Owin.Security.Providers/Flickr/FlickrAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Flickr/FlickrAuthenticationOptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; diff --git a/Owin.Security.Providers/Flickr/Messages/AccessToken.cs b/src/Owin.Security.Providers.Flickr/Messages/AccessToken.cs similarity index 100% rename from Owin.Security.Providers/Flickr/Messages/AccessToken.cs rename to src/Owin.Security.Providers.Flickr/Messages/AccessToken.cs diff --git a/Owin.Security.Providers/Flickr/Messages/RequestToken.cs b/src/Owin.Security.Providers.Flickr/Messages/RequestToken.cs similarity index 100% rename from Owin.Security.Providers/Flickr/Messages/RequestToken.cs rename to src/Owin.Security.Providers.Flickr/Messages/RequestToken.cs diff --git a/Owin.Security.Providers/Flickr/Messages/RequestTokenSerializer.cs b/src/Owin.Security.Providers.Flickr/Messages/RequestTokenSerializer.cs similarity index 88% rename from Owin.Security.Providers/Flickr/Messages/RequestTokenSerializer.cs rename to src/Owin.Security.Providers.Flickr/Messages/RequestTokenSerializer.cs index 56b0253..f84ab38 100644 --- a/Owin.Security.Providers/Flickr/Messages/RequestTokenSerializer.cs +++ b/src/Owin.Security.Providers.Flickr/Messages/RequestTokenSerializer.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler.Serializer; namespace Owin.Security.Providers.Flickr.Messages @@ -60,11 +59,11 @@ namespace Owin.Security.Providers.Flickr.Messages { if (writer == null) { - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); } if (token == null) { - throw new ArgumentNullException("token"); + throw new ArgumentNullException(nameof(token)); } writer.Write(FormatVersion); @@ -83,7 +82,7 @@ namespace Owin.Security.Providers.Flickr.Messages { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (reader.ReadInt32() != FormatVersion) @@ -91,10 +90,10 @@ namespace Owin.Security.Providers.Flickr.Messages return null; } - string token = reader.ReadString(); - string tokenSecret = reader.ReadString(); - bool callbackConfirmed = reader.ReadBoolean(); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var token = reader.ReadString(); + var tokenSecret = reader.ReadString(); + var callbackConfirmed = reader.ReadBoolean(); + var properties = PropertiesSerializer.Read(reader); if (properties == null) { return null; diff --git a/Owin.Security.Providers/Flickr/Messages/Serializers.cs b/src/Owin.Security.Providers.Flickr/Messages/Serializers.cs similarity index 100% rename from Owin.Security.Providers/Flickr/Messages/Serializers.cs rename to src/Owin.Security.Providers.Flickr/Messages/Serializers.cs diff --git a/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.csproj b/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.csproj new file mode 100644 index 0000000..635f2ae --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {AF6CBEB8-5638-43D4-839E-C81F305960BE} + Library + Properties + Owin.Security.Providers.Flickr + Owin.Security.Providers.Flickr + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Flickr.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.nuspec b/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.nuspec new file mode 100644 index 0000000..79f6ffa --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/Owin.Security.Providers.Flickr.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Flickr + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Flickr OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Flickr + + + + + + + + + diff --git a/src/Owin.Security.Providers.Flickr/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Flickr/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..76398c7 --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Flickr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Flickr")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("6b7aa818-2207-49ca-b332-9bdd0d883a9a")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Flickr/Provider/FlickrAuthenticatedContext.cs b/src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticatedContext.cs similarity index 94% rename from Owin.Security.Providers/Flickr/Provider/FlickrAuthenticatedContext.cs rename to src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticatedContext.cs index d8bba84..8a85649 100644 --- a/Owin.Security.Providers/Flickr/Provider/FlickrAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticatedContext.cs @@ -1,11 +1,7 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Claims; +using System.Security.Claims; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; -using Newtonsoft.Json.Linq; using Owin.Security.Providers.Flickr.Messages; namespace Owin.Security.Providers.Flickr { diff --git a/Owin.Security.Providers/Flickr/Provider/FlickrAuthenticationProvider.cs b/src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Flickr/Provider/FlickrAuthenticationProvider.cs rename to src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticationProvider.cs index c043f44..acefc25 100644 --- a/Owin.Security.Providers/Flickr/Provider/FlickrAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Flickr/Provider/FlickrAuthenticationProvider.cs @@ -25,7 +25,7 @@ namespace Owin.Security.Providers.Flickr { public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Flickr succesfully authenticates a user + /// Invoked whenever Flickr successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Flickr/Provider/FlickrReturnEndpointContext.cs b/src/Owin.Security.Providers.Flickr/Provider/FlickrReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Flickr/Provider/FlickrReturnEndpointContext.cs rename to src/Owin.Security.Providers.Flickr/Provider/FlickrReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Flickr/Provider/IFlickrAuthenticationProvider.cs b/src/Owin.Security.Providers.Flickr/Provider/IFlickrAuthenticationProvider.cs similarity index 85% rename from Owin.Security.Providers/Flickr/Provider/IFlickrAuthenticationProvider.cs rename to src/Owin.Security.Providers.Flickr/Provider/IFlickrAuthenticationProvider.cs index 96d54d7..9d08cee 100644 --- a/Owin.Security.Providers/Flickr/Provider/IFlickrAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Flickr/Provider/IFlickrAuthenticationProvider.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Owin.Security.Providers.Flickr { /// @@ -10,7 +6,7 @@ namespace Owin.Security.Providers.Flickr { /// public interface IFlickrAuthenticationProvider { /// - /// Invoked whenever Flickr succesfully authenticates a user + /// Invoked whenever Flickr successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Flickr/Resources.Designer.cs b/src/Owin.Security.Providers.Flickr/Resources.Designer.cs new file mode 100644 index 0000000..306336c --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Flickr { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Flickr.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Flickr/Resources.resx b/src/Owin.Security.Providers.Flickr/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Flickr/packages.config b/src/Owin.Security.Providers.Flickr/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Flickr/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Foursquare/Constants.cs b/src/Owin.Security.Providers.Foursquare/Constants.cs similarity index 100% rename from Owin.Security.Providers/Foursquare/Constants.cs rename to src/Owin.Security.Providers.Foursquare/Constants.cs diff --git a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationExtensions.cs b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/Foursquare/FoursquareAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationExtensions.cs index f92372a..0c7fa73 100644 --- a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationExtensions.cs @@ -8,12 +8,12 @@ namespace Owin.Security.Providers.Foursquare { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } return app.Use(typeof(FoursquareAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationHandler.cs b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationHandler.cs similarity index 56% rename from Owin.Security.Providers/Foursquare/FoursquareAuthenticationHandler.cs rename to src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationHandler.cs index cfe1140..3227198 100644 --- a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationHandler.cs @@ -27,15 +27,15 @@ namespace Owin.Security.Providers.Foursquare public FoursquareAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this._httpClient = httpClient; - this._logger = logger; + _httpClient = httpClient; + _logger = logger; } public override async Task InvokeAsync() { - if ((string.IsNullOrEmpty(this.Options.CallbackPath) == false) && (this.Options.CallbackPath == this.Request.Path.ToString())) + if ((string.IsNullOrEmpty(Options.CallbackPath) == false) && (Options.CallbackPath == Request.Path.ToString())) { - return await this.InvokeReturnPathAsync(); + return await InvokeReturnPathAsync(); } return false; @@ -43,7 +43,7 @@ namespace Owin.Security.Providers.Foursquare protected override async Task AuthenticateCoreAsync() { - this._logger.WriteVerbose("AuthenticateCore"); + _logger.WriteVerbose("AuthenticateCore"); AuthenticationProperties properties = null; @@ -52,7 +52,7 @@ namespace Owin.Security.Providers.Foursquare string code = null; string state = null; - var query = this.Request.Query; + var query = Request.Query; var values = query.GetValues("code"); if ((values != null) && (values.Count == 1)) @@ -67,7 +67,7 @@ namespace Owin.Security.Providers.Foursquare state = values[0]; } - properties = this.Options.StateDataFormat.Unprotect(state); + properties = Options.StateDataFormat.Unprotect(state); if (properties == null) { @@ -75,23 +75,23 @@ namespace Owin.Security.Providers.Foursquare } // OAuth2 10.12 CSRF - if (this.ValidateCorrelationId(properties, this._logger) == false) + if (ValidateCorrelationId(properties, _logger) == false) { return new AuthenticationTicket(null, properties); } var tokenRequestParameters = new List>() { - new KeyValuePair("client_id", this.Options.ClientId), - new KeyValuePair("client_secret", this.Options.ClientSecret), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret), new KeyValuePair("grant_type", "authorization_code"), - new KeyValuePair("redirect_uri", this.GenerateRedirectUri()), + new KeyValuePair("redirect_uri", GenerateRedirectUri()), new KeyValuePair("code", code), }; var requestContent = new FormUrlEncodedContent(tokenRequestParameters); - var response = await this._httpClient.PostAsync(TokenEndpoint, requestContent, this.Request.CallCancelled); + var response = await _httpClient.PostAsync(TokenEndpoint, requestContent, Request.CallCancelled); response.EnsureSuccessStatusCode(); var oauthTokenResponse = await response.Content.ReadAsStringAsync(); @@ -99,44 +99,45 @@ namespace Owin.Security.Providers.Foursquare var oauth2Token = JObject.Parse(oauthTokenResponse); var accessToken = oauth2Token["access_token"].Value(); - if (string.IsNullOrWhiteSpace(accessToken) == true) + if (string.IsNullOrWhiteSpace(accessToken)) { - this._logger.WriteWarning("Access token was not found"); + _logger.WriteWarning("Access token was not found"); return new AuthenticationTicket(null, properties); } - var graphResponse = await this._httpClient.GetAsync(GraphApiEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken) + "&m=foursquare&v=" + VersionDate.ToString("yyyyyMMdd"), this.Request.CallCancelled); + // ReSharper disable once StringLiteralTypo + var graphResponse = await _httpClient.GetAsync(GraphApiEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken) + "&m=foursquare&v=" + VersionDate.ToString("yyyyyMMdd"), Request.CallCancelled); graphResponse.EnsureSuccessStatusCode(); var accountstring = await graphResponse.Content.ReadAsStringAsync(); var accountInformation = JObject.Parse(accountstring); var user = (JObject)accountInformation["response"]["user"]; - var context = new FoursquareAuthenticatedContext(this.Context, user, accessToken); + var context = new FoursquareAuthenticatedContext(Context, user, accessToken); context.Identity = new ClaimsIdentity( new[] { - new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, this.Options.AuthenticationType), - new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, this.Options.AuthenticationType), - new Claim("urn:foursquare:id", context.Id, XmlSchemaString, this.Options.AuthenticationType), - new Claim("urn:foursquare:name", context.Name, XmlSchemaString, this.Options.AuthenticationType), + new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType), + new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, Options.AuthenticationType), + new Claim("urn:foursquare:id", context.Id, XmlSchemaString, Options.AuthenticationType), + new Claim("urn:foursquare:name", context.Name, XmlSchemaString, Options.AuthenticationType), }, - this.Options.AuthenticationType, + Options.AuthenticationType, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); if (string.IsNullOrWhiteSpace(context.Email) == false) { - context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, this.Options.AuthenticationType)); + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); } if (string.IsNullOrWhiteSpace(context.Twitter) == false) { - context.Identity.AddClaim(new Claim("urn:foursquare:twitter", context.Twitter, XmlSchemaString, this.Options.AuthenticationType)); + context.Identity.AddClaim(new Claim("urn:foursquare:twitter", context.Twitter, XmlSchemaString, Options.AuthenticationType)); } - await this.Options.Provider.Authenticated(context); + await Options.Provider.Authenticated(context); context.Properties = properties; @@ -144,65 +145,65 @@ namespace Owin.Security.Providers.Foursquare } catch (Exception ex) { - this._logger.WriteWarning("Authentication failed", ex); + _logger.WriteWarning("Authentication failed", ex); return new AuthenticationTicket(null, properties); } } protected override Task ApplyResponseChallengeAsync() { - this._logger.WriteVerbose("ApplyResponseChallenge"); + _logger.WriteVerbose("ApplyResponseChallenge"); - if (this.Response.StatusCode != (int)HttpStatusCode.Unauthorized) + if (Response.StatusCode != (int)HttpStatusCode.Unauthorized) { - return Task.FromResult(null); + return Task.FromResult(null); } - var challenge = Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = Request.Scheme + Uri.SchemeDelimiter + Request.Host + Request.PathBase; + var currentUri = baseUri + Request.Path + Request.QueryString; + var redirectUri = baseUri + Options.CallbackPath; + + var extra = challenge.Properties; + + if (string.IsNullOrEmpty(extra.RedirectUri)) { - var baseUri = this.Request.Scheme + Uri.SchemeDelimiter + this.Request.Host + this.Request.PathBase; - var currentUri = baseUri + this.Request.Path + this.Request.QueryString; - var redirectUri = baseUri + this.Options.CallbackPath; - - var extra = challenge.Properties; - - if (string.IsNullOrEmpty(extra.RedirectUri) == true) - { - extra.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - this.GenerateCorrelationId(extra); - - var state = this.Options.StateDataFormat.Protect(extra); - - var authorizationEndpoint = AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(this.Options.ClientId) + - "&response_type=code" + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&state=" + Uri.EscapeDataString(state); - - this.Response.Redirect(authorizationEndpoint); + extra.RedirectUri = currentUri; } - return Task.FromResult(null); + // OAuth2 10.12 CSRF + GenerateCorrelationId(extra); + + var state = Options.StateDataFormat.Protect(extra); + + var authorizationEndpoint = AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&response_type=code" + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + + return Task.FromResult(null); } public async Task InvokeReturnPathAsync() { - this._logger.WriteVerbose("InvokeReturnPath"); + _logger.WriteVerbose("InvokeReturnPath"); - var model = await this.AuthenticateAsync(); + var model = await AuthenticateAsync(); - var context = new FoursquareReturnEndpointContext(Context, model); - context.SignInAsAuthenticationType = this.Options.SignInAsAuthenticationType; - context.RedirectUri = model.Properties.RedirectUri; + var context = new FoursquareReturnEndpointContext(Context, model) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = model.Properties.RedirectUri + }; model.Properties.RedirectUri = null; - await this.Options.Provider.ReturnEndpoint(context); + await Options.Provider.ReturnEndpoint(context); if ((context.SignInAsAuthenticationType != null) && (context.Identity != null)) { @@ -213,28 +214,27 @@ namespace Owin.Security.Providers.Foursquare signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); } - this.Context.Authentication.SignIn(context.Properties, signInIdentity); + Context.Authentication.SignIn(context.Properties, signInIdentity); } - if ((context.IsRequestCompleted == false) && (context.RedirectUri != null)) + if (context.IsRequestCompleted || (context.RedirectUri == null)) + return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - - this.Response.Redirect(context.RedirectUri); - - context.RequestCompleted(); + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + + context.RequestCompleted(); + return context.IsRequestCompleted; } private string GenerateRedirectUri() { - var requestPrefix = this.Request.Scheme + "://" + this.Request.Host; - var redirectUri = requestPrefix + this.RequestPathBase + this.Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath; return redirectUri; } } diff --git a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationMiddleware.cs similarity index 57% rename from Owin.Security.Providers/Foursquare/FoursquareAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationMiddleware.cs index 300e5f2..b9732ae 100644 --- a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationMiddleware.cs @@ -18,37 +18,40 @@ namespace Owin.Security.Providers.Foursquare public FoursquareAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, FoursquareAuthenticationOptions options) : base(next, options) { - if (string.IsNullOrWhiteSpace(this.Options.ClientId) == true) + if (string.IsNullOrWhiteSpace(Options.ClientId)) { throw new ArgumentException("The 'ClientId' must be provided."); } - if (string.IsNullOrWhiteSpace(this.Options.ClientSecret) == true) + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) { throw new ArgumentException("The 'ClientSecret' option must be provided."); } - this._logger = app.CreateLogger(); + _logger = app.CreateLogger(); - if (this.Options.Provider == null) + if (Options.Provider == null) { - this.Options.Provider = new FoursquareAuthenticationProvider(); + Options.Provider = new FoursquareAuthenticationProvider(); } - if (this.Options.StateDataFormat == null) + if (Options.StateDataFormat == null) { - var dataProtector = app.CreateDataProtector(typeof(FoursquareAuthenticationMiddleware).FullName, this.Options.AuthenticationType, "v1"); - this.Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + var dataProtector = app.CreateDataProtector(typeof(FoursquareAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (string.IsNullOrEmpty(this.Options.SignInAsAuthenticationType) == true) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) { - this.Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } - this._httpClient = new HttpClient(ResolveHttpMessageHandler(this.Options)); - this._httpClient.Timeout = this.Options.BackchannelTimeout; - this._httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + // 10 MB } /// @@ -61,7 +64,7 @@ namespace Owin.Security.Providers.Foursquare /// protected override AuthenticationHandler CreateHandler() { - return new FoursquareAuthenticationHandler(this._httpClient, this._logger); + return new FoursquareAuthenticationHandler(_httpClient, _logger); } private static HttpMessageHandler ResolveHttpMessageHandler(FoursquareAuthenticationOptions options) @@ -69,19 +72,17 @@ namespace Owin.Security.Providers.Foursquare var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - - if (webRequestHandler == null) - { - throw new InvalidOperationException("Validator Handler Mismatch"); - } - - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException("Validator Handler Mismatch"); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + return handler; } diff --git a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationOptions.cs b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationOptions.cs similarity index 89% rename from Owin.Security.Providers/Foursquare/FoursquareAuthenticationOptions.cs rename to src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationOptions.cs index 41d8ab9..e21f615 100644 --- a/Owin.Security.Providers/Foursquare/FoursquareAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Foursquare/FoursquareAuthenticationOptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using Microsoft.Owin.Security; using Owin.Security.Providers.Foursquare.Provider; @@ -14,10 +13,10 @@ namespace Owin.Security.Providers.Foursquare public FoursquareAuthenticationOptions() : base(Constants.DefaultAuthenticationType) { - this.Caption = Constants.DefaultAuthenticationType; - this.CallbackPath = "/signin-foursquare"; - this.AuthenticationMode = AuthenticationMode.Passive; - this.BackchannelTimeout = TimeSpan.FromSeconds(60); + Caption = Constants.DefaultAuthenticationType; + CallbackPath = "/signin-foursquare"; + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); } /// @@ -86,8 +85,8 @@ namespace Owin.Security.Providers.Foursquare /// public string Caption { - get { return this.Description.Caption; } - set { this.Description.Caption = value; } + get { return Description.Caption; } + set { Description.Caption = value; } } } } \ No newline at end of file diff --git a/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.csproj b/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.csproj new file mode 100644 index 0000000..2f6e613 --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {8ACD9194-1EFE-4128-AC42-856D856332A4} + Library + Properties + Owin.Security.Providers.Foursquare + Owin.Security.Providers.Foursquare + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Foursquare.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.nuspec b/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.nuspec new file mode 100644 index 0000000..e5d03e5 --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/Owin.Security.Providers.Foursquare.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Foursquare + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Foursquare OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Foursquare + + + + + + + + + diff --git a/src/Owin.Security.Providers.Foursquare/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Foursquare/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b7180ff --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Foursquare")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Foursquare")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("61d0bc8f-17c5-42a2-80e5-adb643241c5b")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticatedContext.cs b/src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticatedContext.cs similarity index 74% rename from Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticatedContext.cs rename to src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticatedContext.cs index 04b9a28..34009f7 100644 --- a/Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticatedContext.cs @@ -23,39 +23,39 @@ namespace Owin.Security.Providers.Foursquare.Provider { if (user == null) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException(nameof(user)); } - this.User = user; - this.AccessToken = accessToken; + User = user; + AccessToken = accessToken; - var userId = this.User["id"]; + var userId = User["id"]; if (userId == null) { - throw new ArgumentException("The user does not have an id.", "user"); + throw new ArgumentException("The user does not have an id.", nameof(user)); } - this.Id = TryGetValue(user, "id"); - this.FirstName = TryGetValue(user, "firstName"); - this.LastName = TryGetValue(user, "lastName"); - this.Name = this.FirstName + " " + this.LastName; - this.Gender = TryGetValue(user, "gender"); - this.Photo = (JObject)user["photo"]; - this.Friends = TryGetValue(user, "friends"); - this.HomeCity = TryGetValue(user, "homeCity"); - this.Bio = TryGetValue(user, "bio"); - this.Contact = (JObject)user["contact"]; - this.Phone = TryGetValue(Contact, "phone"); - this.Email = TryGetValue(Contact, "email"); - this.Twitter = TryGetValue(Contact, "twitter"); - this.Facebook = TryGetValue(Contact, "facebook"); - this.Badges = TryGetValue(user, "badges"); - this.Mayorships = TryGetValue(user, "mayorships"); - this.Checkins = TryGetValue(user, "checkins"); - this.Photos = TryGetValue(user, "photos"); - this.Scores = TryGetValue(user, "scores"); - this.Link = "https://foursquare.com/user/" + this.Id; + Id = TryGetValue(user, "id"); + FirstName = TryGetValue(user, "firstName"); + LastName = TryGetValue(user, "lastName"); + Name = FirstName + " " + LastName; + Gender = TryGetValue(user, "gender"); + Photo = (JObject)user["photo"]; + Friends = TryGetValue(user, "friends"); + HomeCity = TryGetValue(user, "homeCity"); + Bio = TryGetValue(user, "bio"); + Contact = (JObject)user["contact"]; + Phone = TryGetValue(Contact, "phone"); + Email = TryGetValue(Contact, "email"); + Twitter = TryGetValue(Contact, "twitter"); + Facebook = TryGetValue(Contact, "facebook"); + Badges = TryGetValue(user, "badges"); + Mayorships = TryGetValue(user, "mayorships"); + Checkins = TryGetValue(user, "checkins"); + Photos = TryGetValue(user, "photos"); + Scores = TryGetValue(user, "scores"); + Link = "https://foursquare.com/user/" + Id; } /// @@ -64,7 +64,7 @@ namespace Owin.Security.Providers.Foursquare.Provider /// /// Contains the Foursquare user obtained from the User Info endpoint https://api.foursquare.com/v2/users/self /// - public JObject User { get; private set; } + public JObject User { get; } /// /// Gets the Foursquare access token /// @@ -72,15 +72,15 @@ namespace Owin.Security.Providers.Foursquare.Provider /// /// Gets the Foursquare user ID /// - public string Id { get; private set; } + public string Id { get; } /// /// Gets the user's first name /// - public string FirstName { get; private set; } + public string FirstName { get; } /// /// Gets the user's last name /// - public string LastName { get; private set; } + public string LastName { get; } /// /// Gets the user's full name /// @@ -108,7 +108,7 @@ namespace Owin.Security.Providers.Foursquare.Provider /// /// Gets the user's contact /// - public JObject Contact { get; private set; } + public JObject Contact { get; } /// /// Gets the user's phone /// diff --git a/Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticationProvider.cs b/src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticationProvider.cs similarity index 84% rename from Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticationProvider.cs rename to src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticationProvider.cs index 841cf2e..17365a9 100644 --- a/Owin.Security.Providers/Foursquare/Provider/FoursquareAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Foursquare/Provider/FoursquareAuthenticationProvider.cs @@ -13,8 +13,8 @@ namespace Owin.Security.Providers.Foursquare.Provider /// public FoursquareAuthenticationProvider() { - this.OnAuthenticated = context => Task.FromResult(null); - this.OnReturnEndpoint = context => Task.FromResult(null); + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); } /// @@ -28,13 +28,13 @@ namespace Owin.Security.Providers.Foursquare.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Foursquare succesfully authenticates a user + /// Invoked whenever Foursquare successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. public virtual Task Authenticated(FoursquareAuthenticatedContext context) { - return this.OnAuthenticated(context); + return OnAuthenticated(context); } /// @@ -44,7 +44,7 @@ namespace Owin.Security.Providers.Foursquare.Provider /// A representing the completed operation. public virtual Task ReturnEndpoint(FoursquareReturnEndpointContext context) { - return this.OnReturnEndpoint(context); + return OnReturnEndpoint(context); } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Foursquare/Provider/FoursquareReturnEndpointContext.cs b/src/Owin.Security.Providers.Foursquare/Provider/FoursquareReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Foursquare/Provider/FoursquareReturnEndpointContext.cs rename to src/Owin.Security.Providers.Foursquare/Provider/FoursquareReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Foursquare/Provider/IFoursquareAuthenticationProvider.cs b/src/Owin.Security.Providers.Foursquare/Provider/IFoursquareAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/Foursquare/Provider/IFoursquareAuthenticationProvider.cs rename to src/Owin.Security.Providers.Foursquare/Provider/IFoursquareAuthenticationProvider.cs index c46a554..e8924b6 100644 --- a/Owin.Security.Providers/Foursquare/Provider/IFoursquareAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Foursquare/Provider/IFoursquareAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Foursquare.Provider public interface IFoursquareAuthenticationProvider { /// - /// Invoked whenever Foursquare succesfully authenticates a user + /// Invoked whenever Foursquare successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Foursquare/Resources.Designer.cs b/src/Owin.Security.Providers.Foursquare/Resources.Designer.cs new file mode 100644 index 0000000..c43d979 --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Foursquare { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Foursquare.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Foursquare/Resources.resx b/src/Owin.Security.Providers.Foursquare/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Foursquare/packages.config b/src/Owin.Security.Providers.Foursquare/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Foursquare/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/GitHub/Constants.cs b/src/Owin.Security.Providers.GitHub/Constants.cs similarity index 100% rename from Owin.Security.Providers/GitHub/Constants.cs rename to src/Owin.Security.Providers.GitHub/Constants.cs diff --git a/Owin.Security.Providers/GitHub/GitHubAuthenticationExtensions.cs b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/GitHub/GitHubAuthenticationExtensions.cs rename to src/Owin.Security.Providers.GitHub/GitHubAuthenticationExtensions.cs index 8331dd9..b9e1d5c 100644 --- a/Owin.Security.Providers/GitHub/GitHubAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.GitHub GitHubAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(GitHubAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.GitHub/GitHubAuthenticationHandler.cs b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationHandler.cs new file mode 100644 index 0000000..b619b28 --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationHandler.cs @@ -0,0 +1,255 @@ +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.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.GitHub +{ + public class GitHubAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public GitHubAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + requestMessage.Content = new FormUrlEncodedContent(body); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the GitHub user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new GitHubAuthenticatedContext(Context, user, accessToken) + { + 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)); + } + else if (Options.Scope.Any(x=> x == "user" || x == "user:email")) + { + var userRequest2 = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "/emails" + "?access_token=" + Uri.EscapeDataString(accessToken)); + userRequest2.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse2 = await _httpClient.SendAsync(userRequest2, Request.CallCancelled); + userResponse2.EnsureSuccessStatusCode(); + text = await userResponse2.Content.ReadAsStringAsync(); + var emails = JsonConvert.DeserializeObject>(text); + if (emails.Any()) + { + var primaryEmail = emails.FirstOrDefault(x => x.Primary && x.Verified); + if (primaryEmail != null) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, primaryEmail.Email, XmlSchemaString, + Options.AuthenticationType)); + } + } + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim("urn:github:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:github: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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new GitHubReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/GitHub/GitHubAuthenticationMiddleware.cs b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/GitHub/GitHubAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.GitHub/GitHubAuthenticationMiddleware.cs index a0788f9..8ec1db8 100644 --- a/Owin.Security.Providers/GitHub/GitHubAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.GitHub { public class GitHubAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public GitHubAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, GitHubAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new GitHubAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (GitHubAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin GitHub middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin GitHub middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.GitHub /// protected override AuthenticationHandler CreateHandler() { - return new GitHubAuthenticationHandler(httpClient, logger); + return new GitHubAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(GitHubAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(GitHubAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/GitHub/GitHubAuthenticationOptions.cs b/src/Owin.Security.Providers.GitHub/GitHubAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/GitHub/GitHubAuthenticationOptions.cs rename to src/Owin.Security.Providers.GitHub/GitHubAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.csproj b/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.csproj new file mode 100644 index 0000000..4f8f33c --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {803F9EB7-029C-45AC-AB81-135E60D5BEAE} + Library + Properties + Owin.Security.Providers.GitHub + Owin.Security.Providers.GitHub + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.GitHub.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.nuspec b/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.nuspec new file mode 100644 index 0000000..5f72e64 --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/Owin.Security.Providers.GitHub.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.GitHub + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a GitHub OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth GitHub + + + + + + + + + diff --git a/src/Owin.Security.Providers.GitHub/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.GitHub/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bea72e4 --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.GitHub")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.GitHub")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("158ce2ed-e474-4a96-945c-41a83101988a")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/GitHub/Provider/GitHubAuthenticatedContext.cs b/src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/GitHub/Provider/GitHubAuthenticatedContext.cs rename to src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticatedContext.cs index c4ccea8..4b96523 100644 --- a/Owin.Security.Providers/GitHub/Provider/GitHubAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/GitHub/Provider/GitHubAuthenticationProvider.cs b/src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/GitHub/Provider/GitHubAuthenticationProvider.cs rename to src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticationProvider.cs index 49f9bc6..732224f 100644 --- a/Owin.Security.Providers/GitHub/Provider/GitHubAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.GitHub/Provider/GitHubAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.GitHub public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever GitHub succesfully authenticates a user + /// Invoked whenever GitHub successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/GitHub/Provider/GitHubReturnEndpointContext.cs b/src/Owin.Security.Providers.GitHub/Provider/GitHubReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/GitHub/Provider/GitHubReturnEndpointContext.cs rename to src/Owin.Security.Providers.GitHub/Provider/GitHubReturnEndpointContext.cs diff --git a/Owin.Security.Providers/GitHub/Provider/IGitHubAuthenticationProvider.cs b/src/Owin.Security.Providers.GitHub/Provider/IGitHubAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/GitHub/Provider/IGitHubAuthenticationProvider.cs rename to src/Owin.Security.Providers.GitHub/Provider/IGitHubAuthenticationProvider.cs index 93f71ba..0407e93 100644 --- a/Owin.Security.Providers/GitHub/Provider/IGitHubAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.GitHub/Provider/IGitHubAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.GitHub public interface IGitHubAuthenticationProvider { /// - /// Invoked whenever GitHub succesfully authenticates a user + /// Invoked whenever GitHub successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.GitHub/Resources.Designer.cs b/src/Owin.Security.Providers.GitHub/Resources.Designer.cs new file mode 100644 index 0000000..b43d6d5 --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.GitHub { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.GitHub.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.GitHub/Resources.resx b/src/Owin.Security.Providers.GitHub/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.GitHub/UserEmail.cs b/src/Owin.Security.Providers.GitHub/UserEmail.cs new file mode 100644 index 0000000..308b415 --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/UserEmail.cs @@ -0,0 +1,11 @@ +namespace Owin.Security.Providers.GitHub +{ + public class UserEmail + { + public string Email { get; set; } + + public bool Primary { get; set; } + + public bool Verified { get; set; } + } +} diff --git a/src/Owin.Security.Providers.GitHub/packages.config b/src/Owin.Security.Providers.GitHub/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.GitHub/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Gitter/Constants.cs b/src/Owin.Security.Providers.Gitter/Constants.cs similarity index 100% rename from Owin.Security.Providers/Gitter/Constants.cs rename to src/Owin.Security.Providers.Gitter/Constants.cs diff --git a/Owin.Security.Providers/Gitter/GitterAuthenticationExtensions.cs b/src/Owin.Security.Providers.Gitter/GitterAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Gitter/GitterAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Gitter/GitterAuthenticationExtensions.cs index 68f823c..ab241c8 100644 --- a/Owin.Security.Providers/Gitter/GitterAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Gitter/GitterAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Gitter GitterAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(GitterAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Gitter/GitterAuthenticationHandler.cs b/src/Owin.Security.Providers.Gitter/GitterAuthenticationHandler.cs similarity index 50% rename from Owin.Security.Providers/Gitter/GitterAuthenticationHandler.cs rename to src/Owin.Security.Providers.Gitter/GitterAuthenticationHandler.cs index 561130e..9569c0d 100644 --- a/Owin.Security.Providers/Gitter/GitterAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Gitter/GitterAuthenticationHandler.cs @@ -4,7 +4,6 @@ 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; @@ -23,13 +22,13 @@ namespace Owin.Security.Providers.Gitter private const string AuthorizeEndpoint = "https://gitter.im/login/oauth/authorize"; private const string Host = "api.gitter.im"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public GitterAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -41,8 +40,8 @@ namespace Owin.Security.Providers.Gitter string code = null; string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = values[0]; @@ -59,45 +58,47 @@ namespace Owin.Security.Providers.Gitter return null; } - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Build up the body for the token request - var body = new List>(); - body.Add(new KeyValuePair("grant_type", "authorization_code")); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; // Request the token - HttpResponseMessage tokenResponse = await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + var tokenResponse = await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string token_type = (string)response.token_type; + var accessToken = (string)response.access_token; + var tokenType = (string)response.token_type; // Get the Gitter user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "?token=" + Uri.EscapeDataString(accessToken)); + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "?token=" + Uri.EscapeDataString(accessToken)); userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - userRequest.Headers.Authorization = new AuthenticationHeaderValue(token_type, accessToken); + userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, accessToken); userRequest.Headers.Host = Host; - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); userResponse.EnsureSuccessStatusCode(); text = await userResponse.Content.ReadAsStringAsync(); - JArray userArray = JArray.Parse(text); - JObject user = JObject.Parse(userArray[0].ToString()); + var userArray = JArray.Parse(text); + var user = JObject.Parse(userArray[0].ToString()); - var context = new GitterAuthenticatedContext(Context, user, accessToken, token_type) + var context = new GitterAuthenticatedContext(Context, user, accessToken, tokenType) { Identity = new ClaimsIdentity( Options.AuthenticationType, @@ -117,9 +118,9 @@ namespace Owin.Security.Providers.Gitter { context.Identity.AddClaim(new Claim("urn:gitter:displayName", context.UserDisplayName, XmlSchemaString, Options.AuthenticationType)); } - if (!string.IsNullOrEmpty(context.UserGV)) + if (!string.IsNullOrEmpty(context.UserGv)) { - context.Identity.AddClaim(new Claim("urn:gitter:gv", context.UserGV, XmlSchemaString, Options.AuthenticationType)); + context.Identity.AddClaim(new Claim("urn:gitter:gv", context.UserGv, XmlSchemaString, Options.AuthenticationType)); } if (!string.IsNullOrEmpty(context.UserUrl)) { @@ -143,7 +144,7 @@ namespace Owin.Security.Providers.Gitter } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); @@ -156,50 +157,48 @@ namespace Owin.Security.Providers.Gitter return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - 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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - AuthorizeEndpoint + - "?response_type=code" + - "&client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + AuthorizeEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -210,51 +209,45 @@ namespace Owin.Security.Providers.Gitter private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + var ticket = await AuthenticateAsync(); + if (ticket == null) { - - AuthenticationTicket ticket = await AuthenticateAsync(); - if (ticket == null) - { - logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new GitterReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new GitterReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Gitter/GitterAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Gitter/GitterAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/Gitter/GitterAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Gitter/GitterAuthenticationMiddleware.cs index 4dcb9b5..e4f6731 100644 --- a/Owin.Security.Providers/Gitter/GitterAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Gitter/GitterAuthenticationMiddleware.cs @@ -8,64 +8,61 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.Gitter.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.Gitter { public class GitterAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public GitterAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, GitterAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new GitterAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(GitterAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 }; } - private HttpMessageHandler ResolveHttpMessageHandler(GitterAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(GitterAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } @@ -80,7 +77,7 @@ namespace Owin.Security.Providers.Gitter /// protected override AuthenticationHandler CreateHandler() { - return new GitterAuthenticationHandler(httpClient, logger); + return new GitterAuthenticationHandler(_httpClient, _logger); } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Gitter/GitterAuthenticationOptions.cs b/src/Owin.Security.Providers.Gitter/GitterAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Gitter/GitterAuthenticationOptions.cs rename to src/Owin.Security.Providers.Gitter/GitterAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.csproj b/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.csproj new file mode 100644 index 0000000..275648a --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {42EC50EB-0C51-460C-93A4-1E007BF1F323} + Library + Properties + Owin.Security.Providers.Gitter + Owin.Security.Providers.Gitter + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Gitter.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.nuspec b/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.nuspec new file mode 100644 index 0000000..62a826d --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/Owin.Security.Providers.Gitter.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Gitter + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Gitter OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Gitter + + + + + + + + + diff --git a/src/Owin.Security.Providers.Gitter/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Gitter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..523334d --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Gitter")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Gitter")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("7d68ce15-c657-45a9-bab1-9cf105f38730")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Gitter/Provider/GitterAuthenticatedContext.cs b/src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticatedContext.cs similarity index 91% rename from Owin.Security.Providers/Gitter/Provider/GitterAuthenticatedContext.cs rename to src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticatedContext.cs index e153ce1..32846c9 100644 --- a/Owin.Security.Providers/Gitter/Provider/GitterAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticatedContext.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Security.Claims; using Microsoft.Owin; using Microsoft.Owin.Security; @@ -20,13 +19,13 @@ namespace Owin.Security.Providers.Gitter.Provider /// The OWIN environment /// The JSON-serialized user /// Gitter Access token - /// Indicates access level of application - public GitterAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string token_type) + /// Indicates access level of application + public GitterAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string tokenType) : base(context) { User = user; AccessToken = accessToken; - TokenType = token_type; + TokenType = tokenType; UserId = TryGetValue(user, "id"); Username = TryGetValue(user, "username"); @@ -34,13 +33,13 @@ namespace Owin.Security.Providers.Gitter.Provider UserUrl = TryGetValue(user, "url"); UserAvatarUrlSmall = TryGetValue(user, "avatarUrlSmall"); UserAvatarUrlMedium = TryGetValue(user, "avatarUrlMedium"); - UserGV = TryGetValue(user, "gv"); + UserGv = TryGetValue(user, "gv"); } /// /// The user GV /// - public string UserGV { get; set; } + public string UserGv { get; set; } /// /// URL to the medium size avatar diff --git a/Owin.Security.Providers/Gitter/Provider/GitterAuthenticationProvider.cs b/src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Gitter/Provider/GitterAuthenticationProvider.cs rename to src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticationProvider.cs index ef443fe..020fc86 100644 --- a/Owin.Security.Providers/Gitter/Provider/GitterAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Gitter/Provider/GitterAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Gitter.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Gitter/Provider/GitterReturnEndpointContext.cs b/src/Owin.Security.Providers.Gitter/Provider/GitterReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Gitter/Provider/GitterReturnEndpointContext.cs rename to src/Owin.Security.Providers.Gitter/Provider/GitterReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Gitter/Provider/IGitterAuthenticationProvider.cs b/src/Owin.Security.Providers.Gitter/Provider/IGitterAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Gitter/Provider/IGitterAuthenticationProvider.cs rename to src/Owin.Security.Providers.Gitter/Provider/IGitterAuthenticationProvider.cs index f9e8a83..79ba274 100644 --- a/Owin.Security.Providers/Gitter/Provider/IGitterAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Gitter/Provider/IGitterAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Gitter.Provider public interface IGitterAuthenticationProvider { /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.Gitter/Resources.Designer.cs b/src/Owin.Security.Providers.Gitter/Resources.Designer.cs new file mode 100644 index 0000000..23220be --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Gitter { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Gitter.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Gitter/Resources.resx b/src/Owin.Security.Providers.Gitter/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Gitter/packages.config b/src/Owin.Security.Providers.Gitter/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Gitter/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/GooglePlus/Constants.cs b/src/Owin.Security.Providers.GooglePlus/Constants.cs similarity index 100% rename from Owin.Security.Providers/GooglePlus/Constants.cs rename to src/Owin.Security.Providers.GooglePlus/Constants.cs diff --git a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationExtensions.cs b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationExtensions.cs rename to src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationExtensions.cs index d31a885..4ab09e3 100644 --- a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.GooglePlus GooglePlusAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(GooglePlusAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationHandler.cs b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationHandler.cs new file mode 100644 index 0000000..94a42d8 --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationHandler.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.GooglePlus.Provider; + +namespace Owin.Security.Providers.GooglePlus +{ + public class GooglePlusAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token"; + private const string UserInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"; + private const string GooglePlusUserEndpoint = "https://www.googleapis.com/plus/v1/people/me"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public GooglePlusAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string) response.expires_in; + string refreshToken = null; + if (response.refresh_token != null) + refreshToken = (string) response.refresh_token; + + // Get the Google user + var graphResponse = await _httpClient.GetAsync( + UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + // Get the Google+ Person Info + graphResponse = await _httpClient.GetAsync( + GooglePlusUserEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken), Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var person = JObject.Parse(text); + + var context = new GooglePlusAuthenticatedContext(Context, user, person, accessToken, expires, refreshToken) + { + 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:googleplus:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:googleplus: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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://accounts.google.com/o/oauth2/auth" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + // Check if offline access was requested + if (Options.RequestOfflineAccess) + authorizationEndpoint += "&access_type=offline"; + + // Request the moment types + if (Options.MomentTypes.Count > 0) + authorizationEndpoint += $"&request_visible_actions={string.Join(" ", Options.MomentTypes)}"; + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new GooglePlusReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationMiddleware.cs b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationMiddleware.cs index cf5b128..d00d41f 100644 --- a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationMiddleware.cs @@ -8,43 +8,42 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.GooglePlus.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.GooglePlus { public class GooglePlusAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public GooglePlusAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, GooglePlusAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new GooglePlusAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (GooglePlusAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -61,24 +60,22 @@ namespace Owin.Security.Providers.GooglePlus /// protected override AuthenticationHandler CreateHandler() { - return new GooglePlusAuthenticationHandler(httpClient, logger); + return new GooglePlusAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(GooglePlusAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(GooglePlusAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationOptions.cs b/src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/GooglePlus/GooglePlusAuthenticationOptions.cs rename to src/Owin.Security.Providers.GooglePlus/GooglePlusAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.csproj b/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.csproj new file mode 100644 index 0000000..47d21a9 --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {D3FEF959-0E0E-4F50-954C-F123A0B629DC} + Library + Properties + Owin.Security.Providers.GooglePlus + Owin.Security.Providers.GooglePlus + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.GooglePlus.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.nuspec b/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.nuspec new file mode 100644 index 0000000..3056e81 --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/Owin.Security.Providers.GooglePlus.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.GooglePlus + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a GooglePlus OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth GooglePlus + + + + + + + + + diff --git a/src/Owin.Security.Providers.GooglePlus/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.GooglePlus/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5674b60 --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.GooglePlus")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.GooglePlus")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("319d029a-cdc3-49ff-a3dd-518d3cd957be")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticatedContext.cs b/src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticatedContext.cs similarity index 96% rename from Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticatedContext.cs rename to src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticatedContext.cs index ae84558..c1f09b0 100644 --- a/Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticatedContext.cs @@ -24,6 +24,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider /// /// Google+ Access token /// Seconds until expiration + /// public GooglePlusAuthenticatedContext(IOwinContext context, JObject user, JObject person, string accessToken, string expires, string refreshToken) : base(context) { @@ -33,7 +34,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider RefreshToken = refreshToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticationProvider.cs b/src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticationProvider.cs rename to src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticationProvider.cs index a76dc72..f59ed04 100644 --- a/Owin.Security.Providers/GooglePlus/Provider/GooglePlusAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/GooglePlus/Provider/GooglePlusReturnEndpointContext.cs b/src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/GooglePlus/Provider/GooglePlusReturnEndpointContext.cs rename to src/Owin.Security.Providers.GooglePlus/Provider/GooglePlusReturnEndpointContext.cs diff --git a/Owin.Security.Providers/GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs b/src/Owin.Security.Providers.GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs rename to src/Owin.Security.Providers.GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs index b16fadb..fead223 100644 --- a/Owin.Security.Providers/GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.GooglePlus/Provider/IGooglePlusAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider public interface IGooglePlusAuthenticationProvider { /// - /// Invoked whenever Google+ succesfully authenticates a user + /// Invoked whenever Google+ successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/src/Owin.Security.Providers.GooglePlus/Resources.Designer.cs b/src/Owin.Security.Providers.GooglePlus/Resources.Designer.cs new file mode 100644 index 0000000..38f1040 --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.GooglePlus { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.GooglePlus.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.GooglePlus/Resources.resx b/src/Owin.Security.Providers.GooglePlus/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.GooglePlus/packages.config b/src/Owin.Security.Providers.GooglePlus/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.GooglePlus/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/HealthGraph/Constants.cs b/src/Owin.Security.Providers.HealthGraph/Constants.cs similarity index 100% rename from Owin.Security.Providers/HealthGraph/Constants.cs rename to src/Owin.Security.Providers.HealthGraph/Constants.cs diff --git a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationExtensions.cs b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationExtensions.cs similarity index 87% rename from Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationExtensions.cs rename to src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationExtensions.cs index 5942962..ebd308b 100644 --- a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationExtensions.cs @@ -9,9 +9,9 @@ namespace Owin.Security.Providers.HealthGraph HealthGraphAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(HealthGraphAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationHandler.cs b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationHandler.cs new file mode 100644 index 0000000..4e5a291 --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationHandler.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.HealthGraph.Provider; + +namespace Owin.Security.Providers.HealthGraph +{ + public class HealthGraphAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public HealthGraphAuthenticationHandler(HttpClient httpClient,ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + requestMessage.Content = new FormUrlEncodedContent(body); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the RunKeeper user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + var userText = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(userText); + + // Get the RunKeeper Profile + var profileRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.ProfileInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + var profileResponse = await _httpClient.SendAsync(profileRequest, Request.CallCancelled); + profileResponse.EnsureSuccessStatusCode(); + var profileText = await profileResponse.Content.ReadAsStringAsync(); + var profile = JObject.Parse(profileText); + + var context = new HealthGraphAuthenticatedContext(Context, user, profile, accessToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + + if (!string.IsNullOrEmpty(context.UserId)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&response_type=code" + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new HealthGraphReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationMiddleware.cs b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationMiddleware.cs similarity index 53% rename from Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationMiddleware.cs index 434d47e..0f15d16 100644 --- a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationMiddleware.cs @@ -8,73 +8,70 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.HealthGraph.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.HealthGraph { public class HealthGraphAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public HealthGraphAuthenticationMiddleware( OwinMiddleware next, IAppBuilder app, HealthGraphAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new HealthGraphAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(HealthGraphAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin HealthGraph middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin HealthGraph middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } protected override AuthenticationHandler CreateHandler() { - return new HealthGraphAuthenticationHandler(httpClient, logger); + return new HealthGraphAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(HealthGraphAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(HealthGraphAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationOptions.cs b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationOptions.cs rename to src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationOptions.cs index 89d2b3d..27324d3 100644 --- a/Owin.Security.Providers/HealthGraph/HealthGraphAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.HealthGraph/HealthGraphAuthenticationOptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Security; diff --git a/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.csproj b/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.csproj new file mode 100644 index 0000000..31960a7 --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {157BB715-29B2-4202-8A59-CCBACFCBEDD3} + Library + Properties + Owin.Security.Providers.HealthGraph + Owin.Security.Providers.HealthGraph + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.HealthGraph.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.nuspec b/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.nuspec new file mode 100644 index 0000000..1dde581 --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/Owin.Security.Providers.HealthGraph.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.HealthGraph + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a HealthGraph OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth HealthGraph + + + + + + + + + diff --git a/src/Owin.Security.Providers.HealthGraph/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.HealthGraph/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4b110ff --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.HealthGraph")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.HealthGraph")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("99d5baa0-a558-4587-91b2-2efc366eb19c")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/HealthGraph/Provider/HealthGraphAuthenticatedContext.cs b/src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/HealthGraph/Provider/HealthGraphAuthenticatedContext.cs rename to src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphAuthenticatedContext.cs diff --git a/Owin.Security.Providers/HealthGraph/Provider/HealthGraphAuthenticationProvider.cs b/src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/HealthGraph/Provider/HealthGraphAuthenticationProvider.cs rename to src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphAuthenticationProvider.cs diff --git a/Owin.Security.Providers/HealthGraph/Provider/HealthGraphReturnEndpointContext.cs b/src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/HealthGraph/Provider/HealthGraphReturnEndpointContext.cs rename to src/Owin.Security.Providers.HealthGraph/Provider/HealthGraphReturnEndpointContext.cs diff --git a/Owin.Security.Providers/HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs b/src/Owin.Security.Providers.HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs similarity index 88% rename from Owin.Security.Providers/HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs rename to src/Owin.Security.Providers.HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs index 0cfb125..13237a2 100644 --- a/Owin.Security.Providers/HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.HealthGraph/Provider/IHealthGraphAuthenticationProvider.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using Owin.Security.Providers.GitHub; namespace Owin.Security.Providers.HealthGraph.Provider { diff --git a/src/Owin.Security.Providers.HealthGraph/Resources.Designer.cs b/src/Owin.Security.Providers.HealthGraph/Resources.Designer.cs new file mode 100644 index 0000000..4bc403b --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.HealthGraph { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.HealthGraph.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.HealthGraph/Resources.resx b/src/Owin.Security.Providers.HealthGraph/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.HealthGraph/packages.config b/src/Owin.Security.Providers.HealthGraph/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.HealthGraph/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Imgur/ImgurAuthenticationDefaults.cs b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationDefaults.cs similarity index 97% rename from Owin.Security.Providers/Imgur/ImgurAuthenticationDefaults.cs rename to src/Owin.Security.Providers.Imgur/ImgurAuthenticationDefaults.cs index 8aaa4ba..b9cd768 100644 --- a/Owin.Security.Providers/Imgur/ImgurAuthenticationDefaults.cs +++ b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationDefaults.cs @@ -54,7 +54,7 @@ namespace Owin.Security.Providers.Imgur /// The name of the grant type parameter. internal const string GrantTypeParameter = "grant_type"; - /// The format to use to stringify s. + /// The format to use to stringify s. internal const string Int32Format = "D"; /// The message for the invalid authentication ticket error. diff --git a/Owin.Security.Providers/Imgur/ImgurAuthenticationExtensions.cs b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationExtensions.cs similarity index 100% rename from Owin.Security.Providers/Imgur/ImgurAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Imgur/ImgurAuthenticationExtensions.cs diff --git a/Owin.Security.Providers/Imgur/ImgurAuthenticationHandler.cs b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationHandler.cs similarity index 79% rename from Owin.Security.Providers/Imgur/ImgurAuthenticationHandler.cs rename to src/Owin.Security.Providers.Imgur/ImgurAuthenticationHandler.cs index dd9f289..7820420 100644 --- a/Owin.Security.Providers/Imgur/ImgurAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationHandler.cs @@ -16,13 +16,13 @@ using Newtonsoft.Json; - using Owin.Security.Providers.Imgur.Provider; + using Provider; /// public class ImgurAuthenticationHandler : AuthenticationHandler { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; /// Creates a new . /// The to be used for back channel calls. @@ -31,44 +31,44 @@ { if (httpClient == null) { - throw new ArgumentNullException("httpClient"); + throw new ArgumentNullException(nameof(httpClient)); } if (logger == null) { - throw new ArgumentNullException("logger"); + throw new ArgumentNullException(nameof(logger)); } - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } /// Is called once by common code after initialization. /// Return true if the request is handled by this , returns false if the request should be passed to the next . public override async Task InvokeAsync() { - if (!this.Options.CallbackPath.HasValue) + if (!Options.CallbackPath.HasValue) { return false; } - if (!this.Options.CallbackPath.Value.Equals(this.Request.Path.Value, StringComparison.OrdinalIgnoreCase)) + if (!Options.CallbackPath.Value.Equals(Request.Path.Value, StringComparison.OrdinalIgnoreCase)) { return false; } - var ticket = await this.AuthenticateAsync(); + var ticket = await AuthenticateAsync(); if (ticket == null) { throw new Exception(ImgurAuthenticationDefaults.InvalidAuthenticationTicketMessage); } - var context = this.GetImgurReturnEndpointContext(ticket); + var context = GetImgurReturnEndpointContext(ticket); - await this.Options.Provider.ReturnEndpoint(context); + await Options.Provider.ReturnEndpoint(context); - this.SignIn(context); + SignIn(context); if (context.IsRequestCompleted || context.RedirectUri == null) { @@ -77,7 +77,7 @@ var location = GetRedirectLocation(context); - this.Response.Redirect(location); + Response.Redirect(location); context.RequestCompleted(); @@ -88,12 +88,12 @@ /// A representing the completed operation. protected override Task ApplyResponseChallengeAsync() { - if (this.Response.StatusCode != 401) + if (Response.StatusCode != 401) { return Task.FromResult(null); } - var challenge = this.Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge == null) { @@ -102,15 +102,15 @@ if (string.IsNullOrWhiteSpace(challenge.Properties.RedirectUri)) { - challenge.Properties.RedirectUri = this.Request.Uri.AbsoluteUri; + challenge.Properties.RedirectUri = Request.Uri.AbsoluteUri; } - this.GenerateCorrelationId(challenge.Properties); + GenerateCorrelationId(challenge.Properties); - var state = this.Options.StateDataFormat.Protect(challenge.Properties); - var authorizationUri = this.GetAuthorizationUri(state); + var state = Options.StateDataFormat.Protect(challenge.Properties); + var authorizationUri = GetAuthorizationUri(state); - this.Response.Redirect(authorizationUri); + Response.Redirect(authorizationUri); return Task.FromResult(null); } @@ -120,36 +120,36 @@ /// Will be invoked at most once per request. Do not call directly, call the wrapping Authenticate method instead. protected override async Task AuthenticateCoreAsync() { - if (this.Request.Query.Get(ImgurAuthenticationDefaults.ErrorParameter) != null) + if (Request.Query.Get(ImgurAuthenticationDefaults.ErrorParameter) != null) { return new AuthenticationTicket(null, null); } - var code = this.Request.Query.Get(ImgurAuthenticationDefaults.CodeParameter); - var state = this.Request.Query.Get(ImgurAuthenticationDefaults.StateParameter); - var properties = this.Options.StateDataFormat.Unprotect(state); + var code = Request.Query.Get(ImgurAuthenticationDefaults.CodeParameter); + var state = Request.Query.Get(ImgurAuthenticationDefaults.StateParameter); + var properties = Options.StateDataFormat.Unprotect(state); if (properties == null) { return new AuthenticationTicket(null, null); } - if (!this.ValidateCorrelationId(properties, this.logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - var authenticationResponse = await this.GetAuthenticationResponseAsync(code); + var authenticationResponse = await GetAuthenticationResponseAsync(code); if (authenticationResponse == null) { throw new Exception(ImgurAuthenticationDefaults.DeserializationFailureMessage); } - var identity = this.GetIdentity(authenticationResponse); - var context = this.GetImgurAuthenticatedContext(authenticationResponse, identity, properties); + var identity = GetIdentity(authenticationResponse); + var context = GetImgurAuthenticatedContext(authenticationResponse, identity, properties); - await this.Options.Provider.Authenticated(context); + await Options.Provider.Authenticated(context); return new AuthenticationTicket(context.Identity, context.Properties); } @@ -165,11 +165,11 @@ { new KeyValuePair( ImgurAuthenticationDefaults.ClientIdParameter, - this.Options.ClientId), + Options.ClientId), new KeyValuePair( ImgurAuthenticationDefaults.ClientSecretParameter, - this.Options.ClientSecret), + Options.ClientSecret), new KeyValuePair( ImgurAuthenticationDefaults.GrantTypeParameter, @@ -188,9 +188,9 @@ { using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, ImgurAuthenticationDefaults.TokenUrl)) { - httpRequestMessage.Content = this.GetAuthenticationRequestContent(code); + httpRequestMessage.Content = GetAuthenticationRequestContent(code); - using (var httpResponseMessage = await this.httpClient.SendAsync(httpRequestMessage, this.Request.CallCancelled)) + using (var httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage, Request.CallCancelled)) { if (!httpResponseMessage.IsSuccessStatusCode) { @@ -224,7 +224,7 @@ WebUtilities.AddQueryString( authorizationUri, ImgurAuthenticationDefaults.ClientIdParameter, - Uri.EscapeDataString(this.Options.ClientId)); + Uri.EscapeDataString(Options.ClientId)); authorizationUri = WebUtilities.AddQueryString( @@ -248,7 +248,7 @@ { var identity = new ClaimsIdentity( - this.Options.AuthenticationType, + Options.AuthenticationType, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); @@ -257,21 +257,21 @@ ClaimTypes.Name, authenticationResponse.AccountUsername, ImgurAuthenticationDefaults.XmlSchemaString, - this.Options.AuthenticationType)); + Options.AuthenticationType)); identity.AddClaim( new Claim( ClaimTypes.NameIdentifier, authenticationResponse.AccountId.ToString(ImgurAuthenticationDefaults.Int32Format, CultureInfo.InvariantCulture), ImgurAuthenticationDefaults.XmlSchemaString, - this.Options.AuthenticationType)); + Options.AuthenticationType)); identity.AddClaim( new Claim( ClaimsIdentity.DefaultNameClaimType, authenticationResponse.AccountUsername, ImgurAuthenticationDefaults.XmlSchemaString, - this.Options.AuthenticationType)); + Options.AuthenticationType)); return identity; } @@ -283,16 +283,18 @@ /// The for the current authentication session. private ImgurAuthenticatedContext GetImgurAuthenticatedContext(AuthenticationResponse authenticationResponse, ClaimsIdentity identity, AuthenticationProperties properties) { - var context = new ImgurAuthenticatedContext(this.Context, this.Options); - context.AccessToken = authenticationResponse.AccessToken; - context.AccountId = authenticationResponse.AccountId; - context.AccountUsername = authenticationResponse.AccountUsername; - context.ExpiresIn = authenticationResponse.ExpiresIn; - context.Identity = identity; - context.Properties = properties; - context.RefreshToken = authenticationResponse.RefreshToken; - context.Scope = authenticationResponse.Scope; - context.TokenType = authenticationResponse.TokenType; + var context = new ImgurAuthenticatedContext(Context, Options) + { + AccessToken = authenticationResponse.AccessToken, + AccountId = authenticationResponse.AccountId, + AccountUsername = authenticationResponse.AccountUsername, + ExpiresIn = authenticationResponse.ExpiresIn, + Identity = identity, + Properties = properties, + RefreshToken = authenticationResponse.RefreshToken, + Scope = authenticationResponse.Scope, + TokenType = authenticationResponse.TokenType + }; return context; } @@ -302,9 +304,11 @@ /// The for the current authentication session. private ImgurReturnEndpointContext GetImgurReturnEndpointContext(AuthenticationTicket ticket) { - var context = new ImgurReturnEndpointContext(this.Context, ticket); - context.SignInAsAuthenticationType = this.Options.SignInAsAuthenticationType; - context.RedirectUri = ticket.Properties.RedirectUri; + var context = new ImgurReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; return context; } @@ -330,7 +334,7 @@ identity.RoleClaimType); } - this.Context.Authentication.SignIn(context.Properties, identity); + Context.Authentication.SignIn(context.Properties, identity); } /// Gets the URL where the user should be redirect to. diff --git a/Owin.Security.Providers/Imgur/ImgurAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationMiddleware.cs similarity index 68% rename from Owin.Security.Providers/Imgur/ImgurAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Imgur/ImgurAuthenticationMiddleware.cs index 94c89f4..812d60e 100644 --- a/Owin.Security.Providers/Imgur/ImgurAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationMiddleware.cs @@ -11,16 +11,15 @@ using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; - using Owin.Security.Providers.Imgur.Provider; - using Owin.Security.Providers.Properties; + using Provider; /// OWIN authentication middleware for imgur. public class ImgurAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; - private readonly static string TypeFullName = typeof(ImgurAuthenticationMiddleware).FullName; + private static readonly string TypeFullName = typeof(ImgurAuthenticationMiddleware).FullName; /// Creates a new . /// The next in the configuration chain. @@ -31,95 +30,87 @@ { if (appBuilder == null) { - throw new ArgumentNullException("appBuilder"); + throw new ArgumentNullException(nameof(appBuilder)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } - this.CheckClientId(); - this.CheckClientSecret(); - this.SetProvider(); - this.SetSignInAsAuthenticationType(appBuilder); - this.SetStateDataFormat(appBuilder); + CheckClientId(); + CheckClientSecret(); + SetProvider(); + SetSignInAsAuthenticationType(appBuilder); + SetStateDataFormat(appBuilder); - var httpMessageHandler = ResolveHttpMessageHandler(this.Options); + var httpMessageHandler = ResolveHttpMessageHandler(Options); - this.httpClient = new HttpClient(httpMessageHandler); - this.logger = appBuilder.CreateLogger(); + _httpClient = new HttpClient(httpMessageHandler); + _logger = appBuilder.CreateLogger(); } /// Creates the to be used by the . /// The to be used by the . protected override AuthenticationHandler CreateHandler() { - return new ImgurAuthenticationHandler(this.httpClient, this.logger); + return new ImgurAuthenticationHandler(_httpClient, _logger); } /// Checks that the imgur application client id has been set. private void CheckClientId() { - if (!string.IsNullOrWhiteSpace(this.Options.ClientId)) + if (!string.IsNullOrWhiteSpace(Options.ClientId)) { return; } - var message = - string.Format( - CultureInfo.InvariantCulture, - Resources.Exception_OptionMustBeProvided, - "ClientId"); + var message = string.Format(CultureInfo.InvariantCulture, Resources.Exception_OptionMustBeProvided, "ClientId"); - throw new ArgumentException(message, "options"); + throw new ArgumentException(message); } /// Checks that the imgur application client secret has been set. private void CheckClientSecret() { - if (!string.IsNullOrWhiteSpace(this.Options.ClientSecret)) + if (!string.IsNullOrWhiteSpace(Options.ClientSecret)) { return; } - var message = - string.Format( - CultureInfo.InvariantCulture, - Resources.Exception_OptionMustBeProvided, - "ClientSecret"); + var message = string.Format(CultureInfo.InvariantCulture,Resources.Exception_OptionMustBeProvided,"ClientSecret"); - throw new ArgumentException(message, "options"); + throw new ArgumentException(message); } /// Sets the provider to if it hasn't been set. private void SetProvider() { - if (this.Options.Provider != null) + if (Options.Provider != null) { return; } - this.Options.Provider = new ImgurAuthenticationProvider(); + Options.Provider = new ImgurAuthenticationProvider(); } /// Sets the name authentication middleware responsible for signing in the user if it hasn't been set. /// The OWIN being configured. private void SetSignInAsAuthenticationType(IAppBuilder appBuilder) { - if (!string.IsNullOrWhiteSpace(this.Options.SignInAsAuthenticationType)) + if (!string.IsNullOrWhiteSpace(Options.SignInAsAuthenticationType)) { return; } - this.Options.SignInAsAuthenticationType = appBuilder.GetDefaultSignInAsAuthenticationType(); + Options.SignInAsAuthenticationType = appBuilder.GetDefaultSignInAsAuthenticationType(); } /// Sets the data protector to if it hasn't been set. /// The OWIN being configured. private void SetStateDataFormat(IAppBuilder appBuilder) { - if (this.Options.StateDataFormat != null) + if (Options.StateDataFormat != null) { return; } @@ -127,10 +118,10 @@ var dataProtector = appBuilder.CreateDataProtector( TypeFullName, - this.Options.AuthenticationType, + Options.AuthenticationType, ImgurAuthenticationDefaults.Version); - this.Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } /// Gets the to be used for the back channel calls. diff --git a/Owin.Security.Providers/Imgur/ImgurAuthenticationOptions.cs b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationOptions.cs similarity index 86% rename from Owin.Security.Providers/Imgur/ImgurAuthenticationOptions.cs rename to src/Owin.Security.Providers.Imgur/ImgurAuthenticationOptions.cs index dbace7b..c7ae6fa 100644 --- a/Owin.Security.Providers/Imgur/ImgurAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Imgur/ImgurAuthenticationOptions.cs @@ -6,7 +6,7 @@ using Microsoft.Owin; using Microsoft.Owin.Security; - using Owin.Security.Providers.Imgur.Provider; + using Provider; /// Configuration options for the . public class ImgurAuthenticationOptions : AuthenticationOptions @@ -15,10 +15,10 @@ public ImgurAuthenticationOptions() : base(ImgurAuthenticationDefaults.AuthenticationType) { - this.AuthenticationMode = AuthenticationMode.Passive; - this.BackchannelTimeout = TimeSpan.FromSeconds(60); - this.CallbackPath = new PathString(ImgurAuthenticationDefaults.CallbackPath); - this.Caption = ImgurAuthenticationDefaults.AuthenticationType; + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + CallbackPath = new PathString(ImgurAuthenticationDefaults.CallbackPath); + Caption = ImgurAuthenticationDefaults.AuthenticationType; } /// Gets or sets the a pinned certificate validator to use to validate the endpoints used in back channel communications belong to StackExchange. @@ -41,12 +41,12 @@ { get { - return this.Description.Caption; + return Description.Caption; } set { - this.Description.Caption = value; + Description.Caption = value; } } diff --git a/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.csproj b/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.csproj new file mode 100644 index 0000000..d8654d2 --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {101841D3-645E-4A44-AF8B-8AAA85CEEA4E} + Library + Properties + Owin.Security.Providers.Imgur + Owin.Security.Providers.Imgur + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Imgur.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.nuspec b/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.nuspec new file mode 100644 index 0000000..8fac2ac --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/Owin.Security.Providers.Imgur.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Imgur + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Imgur OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Imgur + + + + + + + + + diff --git a/src/Owin.Security.Providers.Imgur/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Imgur/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..301a81a --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Imgur")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Imgur")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("e2a3441b-9efe-45e4-b9f2-2e1c6b95db80")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Imgur/Provider/IImgurAuthenticationProvider.cs b/src/Owin.Security.Providers.Imgur/Provider/IImgurAuthenticationProvider.cs similarity index 92% rename from Owin.Security.Providers/Imgur/Provider/IImgurAuthenticationProvider.cs rename to src/Owin.Security.Providers.Imgur/Provider/IImgurAuthenticationProvider.cs index 6a79d2f..c137dec 100644 --- a/Owin.Security.Providers/Imgur/Provider/IImgurAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Imgur/Provider/IImgurAuthenticationProvider.cs @@ -5,7 +5,7 @@ /// Specifies callback methods which the invokes to enable developers control over the authentication process. public interface IImgurAuthenticationProvider { - /// Invoked whenever imgur succesfully authenticates a user. + /// Invoked whenever imgur successfully authenticates a user. /// The that contains information about the login session and the user's . /// A representing the completed operation. Task Authenticated(ImgurAuthenticatedContext context); diff --git a/Owin.Security.Providers/Imgur/Provider/ImgurAuthenticatedContext.cs b/src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticatedContext.cs similarity index 95% rename from Owin.Security.Providers/Imgur/Provider/ImgurAuthenticatedContext.cs rename to src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticatedContext.cs index 38c8edd..f176a79 100644 --- a/Owin.Security.Providers/Imgur/Provider/ImgurAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticatedContext.cs @@ -10,7 +10,7 @@ public class ImgurAuthenticatedContext : BaseContext { /// Creates a new . - /// The OWIN context of the autentication request. + /// The OWIN context of the authentication request. /// The used to set up the . public ImgurAuthenticatedContext(IOwinContext context, ImgurAuthenticationOptions options) : base(context, options) diff --git a/Owin.Security.Providers/Imgur/Provider/ImgurAuthenticationProvider.cs b/src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticationProvider.cs similarity index 83% rename from Owin.Security.Providers/Imgur/Provider/ImgurAuthenticationProvider.cs rename to src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticationProvider.cs index ff41b26..e0df839 100644 --- a/Owin.Security.Providers/Imgur/Provider/ImgurAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Imgur/Provider/ImgurAuthenticationProvider.cs @@ -9,8 +9,8 @@ /// Creates a new . public ImgurAuthenticationProvider() { - this.OnAuthenticated = context => Task.FromResult(null); - this.OnReturnEndpoint = context => Task.FromResult(null); + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); } /// Gets or sets the function that is invoked when the Authenticated method is invoked. @@ -19,12 +19,12 @@ /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. public Func OnReturnEndpoint { get; set; } - /// Invoked whenever imgur succesfully authenticates a user. + /// Invoked whenever imgur successfully authenticates a user. /// The that contains information about the login session and the user's . /// A representing the completed operation. public Task Authenticated(ImgurAuthenticatedContext context) { - return this.OnAuthenticated(context); + return OnAuthenticated(context); } /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. @@ -32,7 +32,7 @@ /// A representing the completed operation. public Task ReturnEndpoint(ImgurReturnEndpointContext context) { - return this.OnReturnEndpoint(context); + return OnReturnEndpoint(context); } } } diff --git a/Owin.Security.Providers/Imgur/Provider/ImgurReturnEndpointContext.cs b/src/Owin.Security.Providers.Imgur/Provider/ImgurReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Imgur/Provider/ImgurReturnEndpointContext.cs rename to src/Owin.Security.Providers.Imgur/Provider/ImgurReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Imgur/Resources.Designer.cs b/src/Owin.Security.Providers.Imgur/Resources.Designer.cs new file mode 100644 index 0000000..8127b61 --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Imgur { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Imgur.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Imgur/Resources.resx b/src/Owin.Security.Providers.Imgur/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Imgur/packages.config b/src/Owin.Security.Providers.Imgur/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Imgur/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Instagram/Constants.cs b/src/Owin.Security.Providers.Instagram/Constants.cs old mode 100755 new mode 100644 similarity index 100% rename from Owin.Security.Providers/Instagram/Constants.cs rename to src/Owin.Security.Providers.Instagram/Constants.cs diff --git a/Owin.Security.Providers/Instagram/InstagramAuthenticationExtensions.cs b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationExtensions.cs old mode 100755 new mode 100644 similarity index 86% rename from Owin.Security.Providers/Instagram/InstagramAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Instagram/InstagramAuthenticationExtensions.cs index b027b23..75525f2 --- a/Owin.Security.Providers/Instagram/InstagramAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Instagram InstagramAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(InstagramAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Instagram/InstagramAuthenticationHandler.cs b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationHandler.cs new file mode 100644 index 0000000..85b175f --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationHandler.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.Instagram.Provider; + +namespace Owin.Security.Providers.Instagram +{ + public class InstagramAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.instagram.com/oauth/access_token"; + + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public InstagramAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var user = (JObject) response.user; + + var context = new InstagramAuthenticatedContext(Context, user, accessToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim("urn:instagram:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.ProfilePicture)) + { + context.Identity.AddClaim(new Claim("urn:instagram:profilepicture", context.ProfilePicture, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + context.Identity.AddClaim(new Claim("urn:instagram:accesstoken", context.AccessToken, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // plus separated (do not URL encode) + var scope = string.Join("+", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://api.instagram.com/oauth/authorize/" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + scope + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new InstagramReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Instagram/InstagramAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationMiddleware.cs old mode 100755 new mode 100644 similarity index 61% rename from Owin.Security.Providers/Instagram/InstagramAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Instagram/InstagramAuthenticationMiddleware.cs index c51c143..23df74c --- a/Owin.Security.Providers/Instagram/InstagramAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationMiddleware.cs @@ -8,43 +8,42 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.Instagram.Provider; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.Instagram { public class InstagramAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public InstagramAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, InstagramAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new InstagramAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (InstagramAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -61,24 +60,22 @@ namespace Owin.Security.Providers.Instagram /// protected override AuthenticationHandler CreateHandler() { - return new InstagramAuthenticationHandler(httpClient, logger); + return new InstagramAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(InstagramAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(InstagramAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Instagram/InstagramAuthenticationOptions.cs b/src/Owin.Security.Providers.Instagram/InstagramAuthenticationOptions.cs old mode 100755 new mode 100644 similarity index 100% rename from Owin.Security.Providers/Instagram/InstagramAuthenticationOptions.cs rename to src/Owin.Security.Providers.Instagram/InstagramAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.csproj b/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.csproj new file mode 100644 index 0000000..e1a596f --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {041178C4-6131-4D68-9896-CE33124D83A0} + Library + Properties + Owin.Security.Providers.Instagram + Owin.Security.Providers.Instagram + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Instagram.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.nuspec b/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.nuspec new file mode 100644 index 0000000..a143fe3 --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/Owin.Security.Providers.Instagram.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Instagram + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Instagram OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Instagram + + + + + + + + + diff --git a/src/Owin.Security.Providers.Instagram/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Instagram/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..22a2dd6 --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Instagram")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Instagram")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("e620ee49-a4b6-4576-b08a-8c3c74f4b900")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Instagram/Provider/IInstagramAuthenticationProvider.cs b/src/Owin.Security.Providers.Instagram/Provider/IInstagramAuthenticationProvider.cs old mode 100755 new mode 100644 similarity index 94% rename from Owin.Security.Providers/Instagram/Provider/IInstagramAuthenticationProvider.cs rename to src/Owin.Security.Providers.Instagram/Provider/IInstagramAuthenticationProvider.cs index e84d8b0..f094ae7 --- a/Owin.Security.Providers/Instagram/Provider/IInstagramAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Instagram/Provider/IInstagramAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Instagram.Provider public interface IInstagramAuthenticationProvider { /// - /// Invoked whenever Instagram succesfully authenticates a user + /// Invoked whenever Instagram successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Instagram/Provider/InstagramAuthenticatedContext.cs b/src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticatedContext.cs old mode 100755 new mode 100644 similarity index 99% rename from Owin.Security.Providers/Instagram/Provider/InstagramAuthenticatedContext.cs rename to src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticatedContext.cs index 3cc5cf0..4ccc40a --- a/Owin.Security.Providers/Instagram/Provider/InstagramAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticatedContext.cs @@ -7,8 +7,6 @@ namespace Owin.Security.Providers.Instagram.Provider { public class InstagramAuthenticatedContext { - - public InstagramAuthenticatedContext(IOwinContext context, JObject user, string accessToken) { User = user; diff --git a/Owin.Security.Providers/Instagram/Provider/InstagramAuthenticationProvider.cs b/src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticationProvider.cs old mode 100755 new mode 100644 similarity index 96% rename from Owin.Security.Providers/Instagram/Provider/InstagramAuthenticationProvider.cs rename to src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticationProvider.cs index b3ddac3..69514fc --- a/Owin.Security.Providers/Instagram/Provider/InstagramAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Instagram/Provider/InstagramAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Instagram.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Instagram succesfully authenticates a user + /// Invoked whenever Instagram successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Instagram/Provider/InstagramReturnEndpointContext.cs b/src/Owin.Security.Providers.Instagram/Provider/InstagramReturnEndpointContext.cs old mode 100755 new mode 100644 similarity index 100% rename from Owin.Security.Providers/Instagram/Provider/InstagramReturnEndpointContext.cs rename to src/Owin.Security.Providers.Instagram/Provider/InstagramReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Instagram/Resources.Designer.cs b/src/Owin.Security.Providers.Instagram/Resources.Designer.cs new file mode 100644 index 0000000..f40816c --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Instagram { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Instagram.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Instagram/Resources.resx b/src/Owin.Security.Providers.Instagram/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Instagram/packages.config b/src/Owin.Security.Providers.Instagram/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Instagram/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/LinkedIn/Constants.cs b/src/Owin.Security.Providers.LinkedIn/Constants.cs similarity index 100% rename from Owin.Security.Providers/LinkedIn/Constants.cs rename to src/Owin.Security.Providers.LinkedIn/Constants.cs diff --git a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationExtensions.cs b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/LinkedIn/LinkedInAuthenticationExtensions.cs rename to src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationExtensions.cs index 17e7f84..ab8c303 100644 --- a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.LinkedIn LinkedInAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(LinkedInAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationHandler.cs b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationHandler.cs new file mode 100644 index 0000000..e6a2191 --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationHandler.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +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.LinkedIn +{ + public class LinkedInAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://www.linkedin.com/uas/oauth2/accessToken"; + private const string UserInfoEndpoint = "https://api.linkedin.com/v1/people/"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public LinkedInAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string) response.expires_in; + + // Get the LinkedIn user + var userInfoEndpoint = UserInfoEndpoint + + "~:("+ string.Join(",", Options.ProfileFields.Distinct().ToArray()) +")" + + "?oauth2_access_token=" + Uri.EscapeDataString(accessToken); + var userRequest = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint); + userRequest.Headers.Add("x-li-format", "json"); + var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new LinkedInAuthenticatedContext(Context, user, accessToken, expires) + { + 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.Email)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.GivenName)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.FamilyName)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, XmlSchemaString, Options.AuthenticationType)); + context.Identity.AddClaim(new Claim("urn:linkedin:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:linkedin:url", context.Link, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + context.Identity.AddClaim(new Claim("urn:linkedin:accesstoken", context.AccessToken, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + // allow scopes to be specified via the authentication properties for this request, when specified they will already be comma separated + if (properties.Dictionary.ContainsKey("scope")) + { + scope = properties.Dictionary["scope"]; + } + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://www.linkedin.com/uas/oauth2/authorization" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new LinkedInReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationMiddleware.cs b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationMiddleware.cs similarity index 60% rename from Owin.Security.Providers/LinkedIn/LinkedInAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationMiddleware.cs index 3ba9bcd..cbc3cee 100644 --- a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationMiddleware.cs @@ -7,50 +7,49 @@ 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.LinkedIn { public class LinkedInAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public LinkedInAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, LinkedInAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new LinkedInAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (LinkedInAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 }; // Fix for LinkedIn Expect: 100- continue issue - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -63,24 +62,22 @@ namespace Owin.Security.Providers.LinkedIn /// protected override AuthenticationHandler CreateHandler() { - return new LinkedInAuthenticationHandler(httpClient, logger); + return new LinkedInAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(LinkedInAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(LinkedInAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/LinkedIn/LinkedInAuthenticationOptions.cs b/src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/LinkedIn/LinkedInAuthenticationOptions.cs rename to src/Owin.Security.Providers.LinkedIn/LinkedInAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.csproj b/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.csproj new file mode 100644 index 0000000..31da521 --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {9FA87825-30E9-48D7-AC4A-39E8F0C2777C} + Library + Properties + Owin.Security.Providers.LinkedIn + Owin.Security.Providers.LinkedIn + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.LinkedIn.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.nuspec b/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.nuspec new file mode 100644 index 0000000..6d27c3d --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/Owin.Security.Providers.LinkedIn.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.LinkedIn + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a LinkedIn OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth LinkedIn + + + + + + + + + diff --git a/src/Owin.Security.Providers.LinkedIn/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.LinkedIn/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a8a2593 --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.LinkedIn")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.LinkedIn")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("5f4b991a-881e-4e26-9f27-89ef33c95811")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/LinkedIn/Provider/ILinkedInAuthenticationProvider.cs b/src/Owin.Security.Providers.LinkedIn/Provider/ILinkedInAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/LinkedIn/Provider/ILinkedInAuthenticationProvider.cs rename to src/Owin.Security.Providers.LinkedIn/Provider/ILinkedInAuthenticationProvider.cs index 2af48c4..52f0e03 100644 --- a/Owin.Security.Providers/LinkedIn/Provider/ILinkedInAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.LinkedIn/Provider/ILinkedInAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.LinkedIn public interface ILinkedInAuthenticationProvider { /// - /// Invoked whenever LinkedIn succesfully authenticates a user + /// Invoked whenever LinkedIn successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticatedContext.cs b/src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticatedContext.cs similarity index 97% rename from Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticatedContext.cs rename to src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticatedContext.cs index ce0d2cd..437ce61 100644 --- a/Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticatedContext.cs @@ -29,7 +29,7 @@ namespace Owin.Security.Providers.LinkedIn AccessToken = accessToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } diff --git a/Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticationProvider.cs b/src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticationProvider.cs rename to src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticationProvider.cs index 67ace73..fe921c6 100644 --- a/Owin.Security.Providers/LinkedIn/Provider/LinkedInAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.LinkedIn/Provider/LinkedInAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.LinkedIn public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever LinkedIn succesfully authenticates a user + /// Invoked whenever LinkedIn successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/LinkedIn/Provider/LinkedInReturnEndpointContext.cs b/src/Owin.Security.Providers.LinkedIn/Provider/LinkedInReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/LinkedIn/Provider/LinkedInReturnEndpointContext.cs rename to src/Owin.Security.Providers.LinkedIn/Provider/LinkedInReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.LinkedIn/Resources.Designer.cs b/src/Owin.Security.Providers.LinkedIn/Resources.Designer.cs new file mode 100644 index 0000000..8505146 --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.LinkedIn { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.LinkedIn.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.LinkedIn/Resources.resx b/src/Owin.Security.Providers.LinkedIn/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.LinkedIn/packages.config b/src/Owin.Security.Providers.LinkedIn/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.LinkedIn/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Onshape/Constants.cs b/src/Owin.Security.Providers.Onshape/Constants.cs similarity index 100% rename from Owin.Security.Providers/Onshape/Constants.cs rename to src/Owin.Security.Providers.Onshape/Constants.cs diff --git a/Owin.Security.Providers/Onshape/OnshapeAuthenticationExtensions.cs b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationExtensions.cs similarity index 83% rename from Owin.Security.Providers/Onshape/OnshapeAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Onshape/OnshapeAuthenticationExtensions.cs index 0784023..d7880d0 100644 --- a/Owin.Security.Providers/Onshape/OnshapeAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationExtensions.cs @@ -1,5 +1,4 @@ -using Microsoft.Owin; -using System; +using System; namespace Owin.Security.Providers.Onshape { @@ -9,9 +8,9 @@ namespace Owin.Security.Providers.Onshape OnshapeAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(OnshapeAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationHandler.cs b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationHandler.cs new file mode 100644 index 0000000..6ca72bb --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationHandler.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net.Http.Headers; + +namespace Owin.Security.Providers.Onshape +{ + public class OnshapeAuthenticationHandler : AuthenticationHandler + { + private const string StateCookie = "_OnshapeState"; + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string AuthorizationEndpoint = "/oauth/authorize"; + private const string TokenEndpoint = "/oauth/token"; + private const string UserInfoEndpoint = "/api/users/session"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public OnshapeAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + + 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); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.AppKey), + new KeyValuePair("client_secret", Options.AppSecret) + }; + + // Get token + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://" + Options.Hostname + TokenEndpoint); + tokenRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + tokenRequest.Content = new FormUrlEncodedContent(body); + + var tokenResponse = await _httpClient.SendAsync(tokenRequest, Request.CallCancelled); + + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + var tokenType = (string)response.token_type; + + var userRequest = new HttpRequestMessage(HttpMethod.Get, "https://" + Options.Hostname + UserInfoEndpoint); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + userRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, accessToken); + var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new OnshapeAuthenticatedContext(Context, user, accessToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://" + Options.Hostname + AuthorizationEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.AppKey) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + Response.StatusCode = 302; + Response.Headers.Set("Location", 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new OnshapeReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Onshape/OnshapeAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationMiddleware.cs similarity index 63% rename from Owin.Security.Providers/Onshape/OnshapeAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Onshape/OnshapeAuthenticationMiddleware.cs index 9ab356d..79b0f68 100644 --- a/Owin.Security.Providers/Onshape/OnshapeAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationMiddleware.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Net.Http; using Microsoft.Owin; using Microsoft.Owin.Logging; @@ -12,35 +11,35 @@ namespace Owin.Security.Providers.Onshape { public class OnshapeAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public OnshapeAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OnshapeAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.AppKey)) + if (string.IsNullOrWhiteSpace(Options.AppKey)) throw new ArgumentException("AppKey must be provided"); - if (String.IsNullOrWhiteSpace(Options.AppSecret)) + if (string.IsNullOrWhiteSpace(Options.AppSecret)) throw new ArgumentException("AppSecret must be provided"); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new OnshapeAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (OnshapeAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -57,24 +56,22 @@ namespace Owin.Security.Providers.Onshape /// protected override AuthenticationHandler CreateHandler() { - return new OnshapeAuthenticationHandler(httpClient, logger); + return new OnshapeAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(OnshapeAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(OnshapeAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException("Validator Handler Mismatch"); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException("Validator Handler Mismatch"); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Onshape/OnshapeAuthenticationOptions.cs b/src/Owin.Security.Providers.Onshape/OnshapeAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Onshape/OnshapeAuthenticationOptions.cs rename to src/Owin.Security.Providers.Onshape/OnshapeAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.csproj b/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.csproj new file mode 100644 index 0000000..4138a5f --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {9FEC99F8-6F45-40A2-8200-85381434C79A} + Library + Properties + Owin.Security.Providers.Onshape + Owin.Security.Providers.Onshape + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Onshape.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.nuspec b/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.nuspec new file mode 100644 index 0000000..dd67c34 --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/Owin.Security.Providers.Onshape.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Onshape + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Onshape OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Onshape + + + + + + + + + diff --git a/src/Owin.Security.Providers.Onshape/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Onshape/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f94517b --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Onshape")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Onshape")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("dbb58ae3-c09f-41dd-82e8-2ccbb53601c4")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Onshape/Provider/IOnshapeAuthenticationProvider.cs b/src/Owin.Security.Providers.Onshape/Provider/IOnshapeAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Onshape/Provider/IOnshapeAuthenticationProvider.cs rename to src/Owin.Security.Providers.Onshape/Provider/IOnshapeAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Onshape/Provider/OnshapeAuthenticatedContext.cs b/src/Owin.Security.Providers.Onshape/Provider/OnshapeAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/Onshape/Provider/OnshapeAuthenticatedContext.cs rename to src/Owin.Security.Providers.Onshape/Provider/OnshapeAuthenticatedContext.cs index a046187..b2b6b9f 100644 --- a/Owin.Security.Providers/Onshape/Provider/OnshapeAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Onshape/Provider/OnshapeAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/Onshape/Provider/OnshapeAuthenticationProvider.cs b/src/Owin.Security.Providers.Onshape/Provider/OnshapeAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Onshape/Provider/OnshapeAuthenticationProvider.cs rename to src/Owin.Security.Providers.Onshape/Provider/OnshapeAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Onshape/Provider/OnshapeReturnEndpointContext.cs b/src/Owin.Security.Providers.Onshape/Provider/OnshapeReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Onshape/Provider/OnshapeReturnEndpointContext.cs rename to src/Owin.Security.Providers.Onshape/Provider/OnshapeReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Onshape/Resources.Designer.cs b/src/Owin.Security.Providers.Onshape/Resources.Designer.cs new file mode 100644 index 0000000..df37f48 --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Onshape { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Onshape.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Onshape/Resources.resx b/src/Owin.Security.Providers.Onshape/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Onshape/packages.config b/src/Owin.Security.Providers.Onshape/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Onshape/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/OpenID/OpenIDAuthenticationExtensions.cs b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationExtensions.cs similarity index 94% rename from Owin.Security.Providers/OpenID/OpenIDAuthenticationExtensions.cs rename to src/Owin.Security.Providers.OpenID/OpenIDAuthenticationExtensions.cs index c94ef22..1ac5e73 100644 --- a/Owin.Security.Providers/OpenID/OpenIDAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Owin; using System; +using Owin.Security.Providers.OpenIDBase; namespace Owin.Security.Providers.OpenID { @@ -18,11 +19,11 @@ namespace Owin.Security.Providers.OpenID { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } app.Use(typeof(OpenIDAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationHandler.cs b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationHandler.cs new file mode 100644 index 0000000..da4d609 --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationHandler.cs @@ -0,0 +1,13 @@ +using Microsoft.Owin.Logging; +using System.Net.Http; +using Owin.Security.Providers.OpenIDBase; + +namespace Owin.Security.Providers.OpenID +{ + internal class OpenIDAuthenticationHandler : OpenIDAuthenticationHandlerBase + { + public OpenIDAuthenticationHandler(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { } + } +} diff --git a/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationMiddleware.cs b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationMiddleware.cs new file mode 100644 index 0000000..d7f939a --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/OpenIDAuthenticationMiddleware.cs @@ -0,0 +1,27 @@ +using Microsoft.Owin; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.OpenIDBase; + +namespace Owin.Security.Providers.OpenID +{ + /// + /// OWIN middleware for authenticating users using an OpenID provider + /// + public class OpenIDAuthenticationMiddleware : OpenIDAuthenticationMiddlewareBase + { + /// + /// Initializes a + /// + /// The next middleware in the OWIN pipeline to invoke + /// The OWIN application + /// Configuration options for the middleware + public OpenIDAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIDAuthenticationOptions options) + : base(next, app, options) + { } + + protected override AuthenticationHandler CreateSpecificHandler() + { + return new OpenIDAuthenticationHandler(HTTPClient, Logger); + } + } +} diff --git a/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.csproj b/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.csproj new file mode 100644 index 0000000..9292544 --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.csproj @@ -0,0 +1,108 @@ + + + + + Debug + AnyCPU + {90C152D7-9C66-4949-9998-C7CE48B593DE} + Library + Properties + Owin.Security.Providers.OpenID + Owin.Security.Providers.OpenID + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {4fd7b873-1994-4990-aa40-c37060121494} + Owin.Security.Providers.OpenIDBase + + + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.OpenID.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.nuspec b/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.nuspec new file mode 100644 index 0000000..c1084c1 --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/Owin.Security.Providers.OpenID.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.OpenID + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a OpenID OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth OpenID + + + + + + + + + diff --git a/src/Owin.Security.Providers.OpenID/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.OpenID/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..01c0325 --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.OpenID")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.OpenID")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("a6488ad5-5b2a-4a11-b66e-9b022eb82faf")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Owin.Security.Providers.OpenID/Resources.Designer.cs b/src/Owin.Security.Providers.OpenID/Resources.Designer.cs new file mode 100644 index 0000000..822140c --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.OpenID { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.OpenID.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.OpenID/Resources.resx b/src/Owin.Security.Providers.OpenID/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenID/packages - Copy.config b/src/Owin.Security.Providers.OpenID/packages - Copy.config new file mode 100644 index 0000000..e84c791 --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/packages - Copy.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenID/packages.config b/src/Owin.Security.Providers.OpenID/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.OpenID/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenIDBase/Constants.cs b/src/Owin.Security.Providers.OpenIDBase/Constants.cs new file mode 100644 index 0000000..a6f9975 --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/Constants.cs @@ -0,0 +1,8 @@ + +namespace Owin.Security.Providers.OpenIDBase +{ + public static class Constants + { + public const string DefaultAuthenticationType = "OpenID"; + } +} diff --git a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs similarity index 85% rename from Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs rename to src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs index 1001d11..4fe2fa4 100644 --- a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationAuthenticationContextExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Owin.Security.Providers.OpenID.Extensions +namespace Owin.Security.Providers.OpenIDBase.Extensions { /// /// Contains an extension method that makes reading the SREG fields easier. diff --git a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationExtension.cs b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationExtension.cs similarity index 88% rename from Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationExtension.cs rename to src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationExtension.cs index 26cfc6e..75b542a 100644 --- a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationExtension.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationExtension.cs @@ -4,15 +4,14 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -namespace Owin.Security.Providers.OpenID.Extensions +namespace Owin.Security.Providers.OpenIDBase.Extensions { /// /// Implements the OpenID Simple Registration Extension http://openid.net/specs/openid-simple-registration-extension-1_0.html /// public class OpenIDSimpleRegistrationExtension : IOpenIDProtocolExtension { - - private static readonly Dictionary claimsMap = new Dictionary() + private static readonly Dictionary ClaimsMap = new Dictionary() { { OpenIDSimpleRegistrationField.NickName, "nickname" }, { OpenIDSimpleRegistrationField.FullName, "fullname" }, @@ -25,18 +24,18 @@ namespace Owin.Security.Providers.OpenID.Extensions { OpenIDSimpleRegistrationField.Timezone, "timezone" } }; - private const string sregNamespace = "http://openid.net/extensions/sreg/1.1"; + private const string SimpleRegistrationNamespace = "http://openid.net/extensions/sreg/1.1"; /// /// Gets or sets a list of comma-separated SREG fields that are required. /// - public HashSet RequiredFields { get; private set; } + public HashSet RequiredFields { get; } /// /// Gets or sets a list of comma-separated SREG fields that are optional. /// - public HashSet OptionalFields { get; private set; } + public HashSet OptionalFields { get; } /// /// Gets or sets the SREG policy URL. @@ -60,14 +59,14 @@ namespace Owin.Security.Providers.OpenID.Extensions /// public Task OnChallengeAsync(Microsoft.Owin.Security.AuthenticationResponseChallenge challenge, OpenIDAuthorizationEndpointInfo endpoint) { - endpoint.Url += "&openid.ns.sreg=" + Uri.EscapeDataString(sregNamespace); + endpoint.Url += "&openid.ns.sreg=" + Uri.EscapeDataString(SimpleRegistrationNamespace); - var requiredClaims = string.Join(",", RequiredFields.Select(f => claimsMap[f])); + var requiredClaims = string.Join(",", RequiredFields.Select(f => ClaimsMap[f])); endpoint.Url += "&openid.sreg.required=" + Uri.EscapeDataString(requiredClaims); if (OptionalFields.Any()) { - var optionalClaims = string.Join(",", OptionalFields.Select(f => claimsMap[f])); + var optionalClaims = string.Join(",", OptionalFields.Select(f => ClaimsMap[f])); endpoint.Url += "&openid.sreg.optional=" + Uri.EscapeDataString(optionalClaims); } @@ -94,10 +93,10 @@ namespace Owin.Security.Providers.OpenID.Extensions public Task OnExtractResultsAsync(ClaimsIdentity identity, string claimedId, Infrastructure.Message message) { var result = new OpenIDSimpleRegistrationResult(); - foreach (var claim in claimsMap) + foreach (var claim in ClaimsMap) { string value; - if (message.TryGetValue(claim.Value + "." + sregNamespace, out value)) + if (message.TryGetValue(claim.Value + "." + SimpleRegistrationNamespace, out value)) { result.Values.Add(claim.Key, value); } diff --git a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationField.cs b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationField.cs similarity index 63% rename from Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationField.cs rename to src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationField.cs index daf1817..e7efd99 100644 --- a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationField.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationField.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Owin.Security.Providers.OpenID.Extensions +namespace Owin.Security.Providers.OpenIDBase.Extensions { public enum OpenIDSimpleRegistrationField { diff --git a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationResult.cs b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationResult.cs similarity index 87% rename from Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationResult.cs rename to src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationResult.cs index 5aa7256..1c5b441 100644 --- a/Owin.Security.Providers/OpenID/Extensions/OpenIDSimpleRegistrationResult.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Extensions/OpenIDSimpleRegistrationResult.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; -namespace Owin.Security.Providers.OpenID.Extensions +namespace Owin.Security.Providers.OpenIDBase.Extensions { /// /// Contains values of OpenID Simple Registration Extension fields. diff --git a/Owin.Security.Providers/OpenID/IOpenIDProtocolExtension.cs b/src/Owin.Security.Providers.OpenIDBase/IOpenIDProtocolExtension.cs similarity index 85% rename from Owin.Security.Providers/OpenID/IOpenIDProtocolExtension.cs rename to src/Owin.Security.Providers.OpenIDBase/IOpenIDProtocolExtension.cs index abd753b..f13028c 100644 --- a/Owin.Security.Providers/OpenID/IOpenIDProtocolExtension.cs +++ b/src/Owin.Security.Providers.OpenIDBase/IOpenIDProtocolExtension.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Owin.Security; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { public interface IOpenIDProtocolExtension { diff --git a/Owin.Security.Providers/OpenID/Infrastructure/Message.cs b/src/Owin.Security.Providers.OpenIDBase/Infrastructure/Message.cs similarity index 59% rename from Owin.Security.Providers/OpenID/Infrastructure/Message.cs rename to src/Owin.Security.Providers.OpenIDBase/Infrastructure/Message.cs index bc6b265..13035be 100644 --- a/Owin.Security.Providers/OpenID/Infrastructure/Message.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Infrastructure/Message.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Owin.Security.Providers.OpenID.Infrastructure +namespace Owin.Security.Providers.OpenIDBase.Infrastructure { public class Message { @@ -14,8 +14,8 @@ namespace Owin.Security.Providers.OpenID.Infrastructure Add(parameters, strict); } - public Dictionary Namespaces { get; private set; } - public Dictionary Properties { get; private set; } + public Dictionary Namespaces { get; } + public Dictionary Properties { get; } /// /// Adds the openid parameters from querystring or form body into Namespaces and Properties collections. @@ -32,7 +32,7 @@ namespace Owin.Security.Providers.OpenID.Infrastructure // strict is true if keys that are not signed should be strict if (strict) { - IList signed = parameters.GetValues("openid.signed"); + var signed = parameters.GetValues("openid.signed"); if (signed == null || signed.Count != 1) { @@ -60,7 +60,7 @@ namespace Owin.Security.Providers.OpenID.Infrastructure // Key is the raw key name. The Name starts of being equal to Key with a // trailing dot appended. The Value is the query or form value, with a comma delimiter // inserted between multiply occuring values. - Property[] addingProperties = addingParameters.Select(kv => new Property + var addingProperties = addingParameters.Select(kv => new Property { Key = kv.Key, Name = kv.Key + ".", @@ -70,21 +70,17 @@ namespace Owin.Security.Providers.OpenID.Infrastructure // first, recognize which parameters are namespace declarations var namespacePrefixes = new Dictionary(StringComparer.Ordinal); - foreach (var item in addingProperties) + foreach (var item in addingProperties.Where(item => item.Name.StartsWith("openid.ns.", StringComparison.Ordinal))) { - // namespaces appear as with "openid.ns" or "openid.ns.alias" - if (item.Name.StartsWith("openid.ns.", StringComparison.Ordinal)) - { - // the value of the parameter is the uri of the namespace - item.Namespace = item.Value; - item.Name = "openid." + item.Name.Substring("openid.ns.".Length); + // the value of the parameter is the uri of the namespace + item.Namespace = item.Value; + item.Name = "openid." + item.Name.Substring("openid.ns.".Length); - // the namespaces collection is keyed by the ns uri - Namespaces.Add(item.Namespace, item); + // the namespaces collection is keyed by the ns uri + Namespaces.Add(item.Namespace, item); - // and the prefixes collection is keyed by "openid.alias." - namespacePrefixes.Add(item.Name, item); - } + // and the prefixes collection is keyed by "openid.alias." + namespacePrefixes.Add(item.Name, item); } // second, recognize which parameters are property values @@ -92,41 +88,39 @@ namespace Owin.Security.Providers.OpenID.Infrastructure foreach (var item in addingProperties) { // anything with a namespace was already added to Namespaces - if (item.Namespace == null) + if (item.Namespace != null) continue; + // look for the namespace match for this property. + Property match = null; + + // try finding where openid.alias.arg2 matches openid.ns.alies namespace + if (item.Name.StartsWith("openid.", StringComparison.Ordinal)) { - // look for the namespace match for this property. - Property match = null; - - // try finding where openid.alias.arg2 matches openid.ns.alies namespace - if (item.Name.StartsWith("openid.", StringComparison.Ordinal)) + var dotIndex = item.Name.IndexOf('.', "openid.".Length); + if (dotIndex != -1) { - int dotIndex = item.Name.IndexOf('.', "openid.".Length); - if (dotIndex != -1) - { - string namespacePrefix = item.Name.Substring(0, dotIndex + 1); - namespacePrefixes.TryGetValue(namespacePrefix, out match); - } + var namespacePrefix = item.Name.Substring(0, dotIndex + 1); + namespacePrefixes.TryGetValue(namespacePrefix, out match); } - - // then try finding where openid.arg1 should match openid.ns namespace - if (match == null) - { - namespacePrefixes.TryGetValue("openid.", out match); - } - - // when a namespace is found - if (match != null) - { - // the property's namespace is defined, and the namespace's prefix is removed - item.Namespace = match.Namespace; - item.Name = item.Name.Substring(match.Name.Length); - } - - // the resulting property key is keyed by the local name and namespace - // so "openid.arg1" becomes "arg1.namespace-uri-of-openid" - // and "openid.alias.arg2" becomes "arg2.namespace-uri-of-alias" - Properties.Add(item.Name + item.Namespace, item); } + + // then try finding where openid.arg1 should match openid.ns namespace + if (match == null) + { + namespacePrefixes.TryGetValue("openid.", out match); + } + + // when a namespace is found + if (match != null) + { + // the property's namespace is defined, and the namespace's prefix is removed + item.Namespace = match.Namespace; + item.Name = item.Name.Substring(match.Name.Length); + } + + // the resulting property key is keyed by the local name and namespace + // so "openid.arg1" becomes "arg1.namespace-uri-of-openid" + // and "openid.alias.arg2" becomes "arg2.namespace-uri-of-alias" + Properties.Add(item.Name + item.Namespace, item); } } diff --git a/Owin.Security.Providers/OpenID/Infrastructure/Property.cs b/src/Owin.Security.Providers.OpenIDBase/Infrastructure/Property.cs similarity index 78% rename from Owin.Security.Providers/OpenID/Infrastructure/Property.cs rename to src/Owin.Security.Providers.OpenIDBase/Infrastructure/Property.cs index 5729cd8..f543293 100644 --- a/Owin.Security.Providers/OpenID/Infrastructure/Property.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Infrastructure/Property.cs @@ -1,5 +1,5 @@  -namespace Owin.Security.Providers.OpenID.Infrastructure +namespace Owin.Security.Providers.OpenIDBase.Infrastructure { public class Property { diff --git a/Owin.Security.Providers/OpenID/OpenIDAuthenticationHandler.cs b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationHandlerBase.cs similarity index 59% rename from Owin.Security.Providers/OpenID/OpenIDAuthenticationHandler.cs rename to src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationHandlerBase.cs index 9c87633..d46a36e 100644 --- a/Owin.Security.Providers/OpenID/OpenIDAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationHandlerBase.cs @@ -1,9 +1,3 @@ -using Microsoft.Owin; -using Microsoft.Owin.Infrastructure; -using Microsoft.Owin.Logging; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.OpenID.Infrastructure; using System; using System.Collections.Generic; using System.Linq; @@ -15,32 +9,31 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.OpenIDBase.Infrastructure; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { - internal class OpenIDAuthenticationHandler : OpenIDAuthenticationHandlerBase + public abstract class OpenIDAuthenticationHandlerBase : AuthenticationHandler where T : OpenIDAuthenticationOptions { - public OpenIDAuthenticationHandler(HttpClient httpClient, ILogger logger) - : base(httpClient, logger) - { } - } + private const string ContenttypeXrds = "application/xrds+xml"; + private const string ContenttypeHTML = "text/html"; + private const string ContenttypeXhtml = "application/xhtml+xml"; + private const string ContenttypeXml = "text/xml"; + private const string XrdsLocationheader = "X-XRDS-Location"; + private const string XrdNamespace = "xri://$xrd*($v*2.0)"; - internal abstract class OpenIDAuthenticationHandlerBase : AuthenticationHandler where T : OpenIDAuthenticationOptions - { - private const string CONTENTTYPE_XRDS = "application/xrds+xml"; - private const string CONTENTTYPE_HTML = "text/html"; - private const string CONTENTTYPE_XHTML = "application/xhtml+xml"; - private const string CONTENTTYPE_XML = "text/xml"; - private const string XRDS_LOCATIONHEADER = "X-XRDS-Location"; - private const string XRD_NAMESPACE = "xri://$xrd*($v*2.0)"; + protected readonly ILogger Logger; + protected readonly HttpClient HTTPClient; - protected readonly ILogger _logger; - protected readonly HttpClient _httpClient; - - public OpenIDAuthenticationHandlerBase(HttpClient httpClient, ILogger logger) + protected OpenIDAuthenticationHandlerBase(HttpClient httpClient, ILogger logger) { - _httpClient = httpClient; - _logger = logger; + HTTPClient = httpClient; + Logger = logger; } public override async Task InvokeAsync() @@ -58,35 +51,35 @@ namespace Owin.Security.Providers.OpenID try { - IReadableStringCollection query = Request.Query; + var query = Request.Query; properties = UnpackStateParameter(query); if (properties == null) { - _logger.WriteWarning("Invalid return state"); + Logger.WriteWarning("Invalid return state"); return null; } // Anti-CSRF - if (!ValidateCorrelationId(properties, _logger)) + if (!ValidateCorrelationId(properties, Logger)) { return new AuthenticationTicket(null, properties); } - Message message = await ParseRequestMessageAsync(query); + var message = await ParseRequestMessageAsync(query); - bool messageValidated = false; + var messageValidated = false; Property mode; if (!message.Properties.TryGetValue("mode.http://specs.openid.net/auth/2.0", out mode)) { - _logger.WriteWarning("Missing mode parameter"); + Logger.WriteWarning("Missing mode parameter"); return new AuthenticationTicket(null, properties); } if (string.Equals("cancel", mode.Value, StringComparison.Ordinal)) { - _logger.WriteWarning("User cancelled signin request"); + Logger.WriteWarning("User cancelled signin request"); return new AuthenticationTicket(null, properties); } @@ -96,31 +89,24 @@ namespace Owin.Security.Providers.OpenID var requestBody = new FormUrlEncodedContent(message.ToFormValues()); - HttpResponseMessage response = await _httpClient.PostAsync(Options.ProviderLoginUri, requestBody, Request.CallCancelled); + var response = await HTTPClient.PostAsync(Options.ProviderLoginUri, requestBody, Request.CallCancelled); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); + var responseBody = await response.Content.ReadAsStringAsync(); var verifyBody = new Dictionary(); foreach (var line in responseBody.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)) { - int delimiter = line.IndexOf(':'); + var delimiter = line.IndexOf(':'); if (delimiter != -1) { verifyBody.Add("openid." + line.Substring(0, delimiter), new[] { line.Substring(delimiter + 1) }); } } - var verifyMessage = new Message(new ReadableStringCollection(verifyBody), strict: false); + var verifyMessage = new Message(new ReadableStringCollection(verifyBody), false); Property isValid; if (verifyMessage.Properties.TryGetValue("is_valid.http://specs.openid.net/auth/2.0", out isValid)) { - if (string.Equals("true", isValid.Value, StringComparison.Ordinal)) - { - messageValidated = true; - } - else - { - messageValidated = false; - } + messageValidated = string.Equals("true", isValid.Value, StringComparison.Ordinal); } } @@ -135,7 +121,7 @@ namespace Owin.Security.Providers.OpenID string actualReturnTo; if (!message.TryGetValue("return_to.http://specs.openid.net/auth/2.0", out actualReturnTo)) { - _logger.WriteWarning("openid.return_to parameter missing at return address"); + Logger.WriteWarning("openid.return_to parameter missing at return address"); messageValidated = false; } else @@ -143,11 +129,11 @@ namespace Owin.Security.Providers.OpenID // create the expected return_to parameter based on the URL that is processing // the assertion, plus exactly and only the the query string parameter (state) // that this RP must have received - string expectedReturnTo = BuildReturnTo(GetStateParameter(query)); + var expectedReturnTo = BuildReturnTo(GetStateParameter(query)); if (!string.Equals(actualReturnTo, expectedReturnTo, StringComparison.Ordinal)) { - _logger.WriteWarning("openid.return_to parameter not equal to expected value based on return address"); + Logger.WriteWarning("openid.return_to parameter not equal to expected value based on return address"); messageValidated = false; } } @@ -162,20 +148,17 @@ namespace Owin.Security.Providers.OpenID } } - if (messageValidated) + if (!messageValidated) return new AuthenticationTicket(null, properties); { IDictionary attributeExchangeProperties = new Dictionary(); foreach (var typeProperty in message.Properties.Values) { - if (typeProperty.Namespace == "http://openid.net/srv/ax/1.0" && - typeProperty.Name.StartsWith("type.")) + if (typeProperty.Namespace != "http://openid.net/srv/ax/1.0" || !typeProperty.Name.StartsWith("type.")) continue; + var name = "value." + typeProperty.Name.Substring("type.".Length) + "http://openid.net/srv/ax/1.0"; + Property valueProperty; + if (message.Properties.TryGetValue(name, out valueProperty)) { - string qname = "value." + typeProperty.Name.Substring("type.".Length) + "http://openid.net/srv/ax/1.0"; - Property valueProperty; - if (message.Properties.TryGetValue(qname, out valueProperty)) - { - attributeExchangeProperties.Add(typeProperty.Value, valueProperty.Value); - } + attributeExchangeProperties.Add(typeProperty.Value, valueProperty.Value); } } @@ -185,14 +168,14 @@ namespace Owin.Security.Providers.OpenID new XAttribute(XNamespace.Xmlns + "openid.ax", "http://openid.net/srv/ax/1.0") }; - IEnumerable responseProperties = message.Properties - .Where(p => p.Value.Namespace != null) - .Select(p => (object)new XElement(XName.Get(p.Value.Name.Substring(0, p.Value.Name.Length - 1), p.Value.Namespace), p.Value.Value)); + var responseProperties = message.Properties + .Where(p => p.Value.Namespace != null) + .Select(p => (object)new XElement(XName.Get(p.Value.Name.Substring(0, p.Value.Name.Length - 1), p.Value.Namespace), p.Value.Value)); var responseMessage = new XElement("response", responseNamespaces.Concat(responseProperties).ToArray()); var identity = new ClaimsIdentity(Options.AuthenticationType); - XElement claimedId = responseMessage.Element(XName.Get("claimed_id", "http://specs.openid.net/auth/2.0")); + var claimedId = responseMessage.Element(XName.Get("claimed_id", "http://specs.openid.net/auth/2.0")); if (claimedId != null) { identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, claimedId.Value, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); @@ -219,12 +202,10 @@ namespace Owin.Security.Providers.OpenID return new AuthenticationTicket(context.Identity, context.Properties); } - - return new AuthenticationTicket(null, properties); } catch (Exception ex) { - _logger.WriteError("Authentication failed", ex); + Logger.WriteError("Authentication failed", ex); return new AuthenticationTicket(null, properties); } } @@ -280,7 +261,7 @@ namespace Owin.Security.Providers.OpenID private static string GetStateParameter(IReadableStringCollection query) { - IList values = query.GetValues("state"); + var values = query.GetValues("state"); if (values != null && values.Count == 1) { return values[0]; @@ -290,7 +271,7 @@ namespace Owin.Security.Providers.OpenID private AuthenticationProperties UnpackStateParameter(IReadableStringCollection query) { - string state = GetStateParameter(query); + var state = GetStateParameter(query); if (state != null) { return Options.StateDataFormat.Unprotect(state); @@ -301,18 +282,15 @@ namespace Owin.Security.Providers.OpenID private string BuildReturnTo(string state) { return Request.Scheme + "://" + Request.Host + - RequestPathBase + Options.CallbackPath + - "?state=" + Uri.EscapeDataString(state); + RequestPathBase + Options.CallbackPath + + "?state=" + Uri.EscapeDataString(state); } private async Task ParseRequestMessageAsync(IReadableStringCollection query) { - if (Request.Method == "POST") - { - IFormCollection form = await Request.ReadFormAsync(); - return new Message(form, strict: true); - } - return new Message(query, strict: true); + if (Request.Method != "POST") return new Message(query, true); + var form = await Request.ReadFormAsync(); + return new Message(form, true); } protected override async Task ApplyResponseChallengeAsync() @@ -322,7 +300,7 @@ namespace Owin.Security.Providers.OpenID return; } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge != null) { @@ -333,10 +311,10 @@ namespace Owin.Security.Providers.OpenID if (!string.IsNullOrEmpty(Options.ProviderLoginUri)) { - string requestPrefix = Request.Scheme + Uri.SchemeDelimiter + Request.Host; + var requestPrefix = Request.Scheme + Uri.SchemeDelimiter + Request.Host; var state = challenge.Properties; - if (String.IsNullOrEmpty(state.RedirectUri)) + if (string.IsNullOrEmpty(state.RedirectUri)) { state.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; } @@ -344,31 +322,31 @@ namespace Owin.Security.Providers.OpenID // Anti-CSRF GenerateCorrelationId(state); - string returnTo = BuildReturnTo(Options.StateDataFormat.Protect(state)); + var returnTo = BuildReturnTo(Options.StateDataFormat.Protect(state)); - string authorizationEndpoint = + var authorizationEndpoint = Options.ProviderLoginUri + - "?openid.ns=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0") + - "&openid.mode=" + Uri.EscapeDataString("checkid_setup") + - "&openid.claimed_id=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") + - "&openid.identity=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") + - "&openid.return_to=" + Uri.EscapeDataString(returnTo) + - "&openid.realm=" + Uri.EscapeDataString(requestPrefix) + + "?openid.ns=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0") + + "&openid.mode=" + Uri.EscapeDataString("checkid_setup") + + "&openid.claimed_id=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") + + "&openid.identity=" + Uri.EscapeDataString("http://specs.openid.net/auth/2.0/identifier_select") + + "&openid.return_to=" + Uri.EscapeDataString(returnTo) + + "&openid.realm=" + Uri.EscapeDataString(requestPrefix) + - "&openid.ns.ax=" + Uri.EscapeDataString("http://openid.net/srv/ax/1.0") + - "&openid.ax.mode=" + Uri.EscapeDataString("fetch_request") + + "&openid.ns.ax=" + Uri.EscapeDataString("http://openid.net/srv/ax/1.0") + + "&openid.ax.mode=" + Uri.EscapeDataString("fetch_request") + - "&openid.ax.type.email=" + Uri.EscapeDataString("http://axschema.org/contact/email") + - "&openid.ax.type.name=" + Uri.EscapeDataString("http://axschema.org/namePerson") + - "&openid.ax.type.first=" + Uri.EscapeDataString("http://axschema.org/namePerson/first") + - "&openid.ax.type.last=" + Uri.EscapeDataString("http://axschema.org/namePerson/last") + + "&openid.ax.type.email=" + Uri.EscapeDataString("http://axschema.org/contact/email") + + "&openid.ax.type.name=" + Uri.EscapeDataString("http://axschema.org/namePerson") + + "&openid.ax.type.first=" + Uri.EscapeDataString("http://axschema.org/namePerson/first") + + "&openid.ax.type.last=" + Uri.EscapeDataString("http://axschema.org/namePerson/last") + - "&openid.ax.type.email2=" + Uri.EscapeDataString("http://schema.openid.net/contact/email") + - "&openid.ax.type.name2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson") + - "&openid.ax.type.first2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/first") + - "&openid.ax.type.last2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/last") + + "&openid.ax.type.email2=" + Uri.EscapeDataString("http://schema.openid.net/contact/email") + + "&openid.ax.type.name2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson") + + "&openid.ax.type.first2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/first") + + "&openid.ax.type.last2=" + Uri.EscapeDataString("http://schema.openid.net/namePerson/last") + - "&openid.ax.required=" + Uri.EscapeDataString("email,name,first,last,email2,name2,first2,last2"); + "&openid.ax.required=" + Uri.EscapeDataString("email,name,first,last,email2,name2,first2,last2"); // allow protocol extensions to add their own attributes to the endpoint URL var endpoint = new OpenIDAuthorizationEndpointInfo() @@ -388,22 +366,23 @@ namespace Owin.Security.Providers.OpenID private async Task DoYadisDiscoveryAsync() { - // 1° request - HttpResponseMessage httpResponse = await SendRequestAsync(Options.ProviderDiscoveryUri, CONTENTTYPE_XRDS, CONTENTTYPE_HTML, CONTENTTYPE_XHTML); + // 1° request + var httpResponse = await SendRequestAsync(Options.ProviderDiscoveryUri, ContenttypeXrds, ContenttypeHTML, ContenttypeXhtml); if (httpResponse.StatusCode != HttpStatusCode.OK) { - _logger.WriteError(string.Format("HTTP error {0} ({1}) while performing discovery on {2}.", (int)httpResponse.StatusCode, httpResponse.StatusCode, Options.ProviderDiscoveryUri)); + Logger.WriteError( + $"HTTP error {(int) httpResponse.StatusCode} ({httpResponse.StatusCode}) while performing discovery on {Options.ProviderDiscoveryUri}."); return; } await httpResponse.Content.LoadIntoBufferAsync(); - // 2° request (if necessary) + // 2° request (if necessary) if (!await IsXrdsDocumentAsync(httpResponse)) { IEnumerable uriStrings; string uriString = null; - if (httpResponse.Headers.TryGetValues(XRDS_LOCATIONHEADER, out uriStrings)) + if (httpResponse.Headers.TryGetValues(XrdsLocationheader, out uriStrings)) { uriString = uriStrings.FirstOrDefault(); } @@ -415,76 +394,64 @@ namespace Owin.Security.Providers.OpenID } var contentType = httpResponse.Content.Headers.ContentType; - if (url == null && contentType != null && (contentType.MediaType == CONTENTTYPE_HTML || contentType.MediaType == CONTENTTYPE_XHTML)) + if (url == null && contentType != null && (contentType.MediaType == ContenttypeHTML || contentType.MediaType == ContenttypeXhtml)) { url = FindYadisDocumentLocationInHtmlMetaTags(await httpResponse.Content.ReadAsStringAsync()); } if (url == null) { - _logger.WriteError(string.Format("The uri {0} doesn't return an XRDS document.", Options.ProviderDiscoveryUri)); + Logger.WriteError($"The uri {Options.ProviderDiscoveryUri} doesn't return an XRDS document."); return; } else { - httpResponse = await SendRequestAsync(url.AbsoluteUri, CONTENTTYPE_XRDS); + httpResponse = await SendRequestAsync(url.AbsoluteUri, ContenttypeXrds); if (httpResponse.StatusCode != HttpStatusCode.OK) { - _logger.WriteError(string.Format("HTTP error {0} {1} while performing discovery on {2}.", (int)httpResponse.StatusCode, httpResponse.StatusCode, url.AbsoluteUri)); + Logger.WriteError( + $"HTTP error {(int) httpResponse.StatusCode} {httpResponse.StatusCode} while performing discovery on {url.AbsoluteUri}."); return; } if (!await IsXrdsDocumentAsync(httpResponse)) { - _logger.WriteError(string.Format("The uri {0} doesn't return an XRDS document.", url.AbsoluteUri)); + Logger.WriteError($"The uri {url.AbsoluteUri} doesn't return an XRDS document."); return; } } } // Get provider url from XRDS document - XDocument xrdsDoc = XDocument.Parse(await httpResponse.Content.ReadAsStringAsync()); + var xrdsDoc = XDocument.Parse(await httpResponse.Content.ReadAsStringAsync()); Options.ProviderLoginUri = xrdsDoc.Root.Element(XName.Get("XRD", "xri://$xrd*($v*2.0)")) .Descendants(XName.Get("Service", "xri://$xrd*($v*2.0)")) .Where(service => service.Descendants(XName.Get("Type", "xri://$xrd*($v*2.0)")).Any(type => type.Value == "http://specs.openid.net/auth/2.0/server")) .OrderBy(service => { var priorityAttribute = service.Attribute("priority"); - if (priorityAttribute == null) - return null; - return priorityAttribute.Value; + return priorityAttribute?.Value; }) .Select(service => service.Element(XName.Get("URI", "xri://$xrd*($v*2.0)")).Value) .FirstOrDefault(); } - // FIXME use an HTTP parser - private static readonly Regex MetaTagXRDSLocationRegex = new Regex(@"", RegexOptions.Compiled); - private static Uri FindYadisDocumentLocationInHtmlMetaTags(string html) { - var match = MetaTagXRDSLocationRegex.Match(html); - if (match.Success) - { - Uri uri; - if (Uri.TryCreate(match.Groups[1].Value, UriKind.Absolute, out uri)) - { - return uri; - } - } - return null; + var match = new Regex(@"", RegexOptions.Compiled).Match(html); + if (!match.Success) return null; + Uri uri; + return Uri.TryCreate(match.Groups[1].Value, UriKind.Absolute, out uri) ? uri : null; } private async Task SendRequestAsync(string uri, params string[] acceptTypes) { - HttpRequestMessage httprequest = new HttpRequestMessage(HttpMethod.Get, uri); - if (acceptTypes != null) + var httprequest = new HttpRequestMessage(HttpMethod.Get, uri); + if (acceptTypes == null) return await HTTPClient.SendAsync(httprequest); + foreach (var acceptType in acceptTypes) { - foreach (string acceptType in acceptTypes) - { - httprequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(acceptType)); - } + httprequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(acceptType)); } - return await _httpClient.SendAsync(httprequest); + return await HTTPClient.SendAsync(httprequest); } private static async Task IsXrdsDocumentAsync(HttpResponseMessage response) @@ -494,23 +461,21 @@ namespace Owin.Security.Providers.OpenID return false; } - if (response.Content.Headers.ContentType.MediaType == CONTENTTYPE_XRDS) + if (response.Content.Headers.ContentType.MediaType == ContenttypeXrds) { return true; } - if (response.Content.Headers.ContentType.MediaType == CONTENTTYPE_XML) + if (response.Content.Headers.ContentType.MediaType != ContenttypeXml) return false; + using (var responseStream = await response.Content.ReadAsStreamAsync()) { - using (var responseStream = await response.Content.ReadAsStreamAsync()) - { - XmlReader reader = XmlReader.Create(responseStream, new XmlReaderSettings { MaxCharactersFromEntities = 1024, XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit }); + var reader = XmlReader.Create(responseStream, new XmlReaderSettings { MaxCharactersFromEntities = 1024, XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit }); - while (await reader.ReadAsync() && reader.NodeType != XmlNodeType.Element) - { } - if (reader.NamespaceURI == XRD_NAMESPACE && reader.Name == "XRDS") - { - return true; - } + while (await reader.ReadAsync() && reader.NodeType != XmlNodeType.Element) + { } + if (reader.NamespaceURI == XrdNamespace && reader.Name == "XRDS") + { + return true; } } @@ -519,24 +484,26 @@ namespace Owin.Security.Providers.OpenID public async Task InvokeReturnPathAsync() { - AuthenticationTicket model = await AuthenticateAsync(); + var model = await AuthenticateAsync(); if (model == null) { - _logger.WriteWarning("Invalid return state, unable to redirect."); + Logger.WriteWarning("Invalid return state, unable to redirect."); Response.StatusCode = 500; return true; } - var context = new OpenIDReturnEndpointContext(Context, model); - context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType; - context.RedirectUri = model.Properties.RedirectUri; + var context = new OpenIDReturnEndpointContext(Context, model) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = model.Properties.RedirectUri + }; model.Properties.RedirectUri = null; await Options.Provider.ReturnEndpoint(context); if (context.SignInAsAuthenticationType != null && context.Identity != null) { - ClaimsIdentity signInIdentity = context.Identity; + var signInIdentity = context.Identity; if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) { signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); @@ -544,18 +511,16 @@ namespace Owin.Security.Providers.OpenID Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } } -} +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationMiddlewareBase.cs b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationMiddlewareBase.cs new file mode 100644 index 0000000..8b34e5c --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationMiddlewareBase.cs @@ -0,0 +1,91 @@ +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; + +namespace Owin.Security.Providers.OpenIDBase +{ + /// + /// OWIN middleware for authenticating users using an OpenID provider + /// + public abstract class OpenIDAuthenticationMiddlewareBase : AuthenticationMiddleware where T : OpenIDAuthenticationOptions + { + protected readonly ILogger Logger; + protected readonly HttpClient HTTPClient; + + /// + /// Initializes a + /// + /// The next middleware in the OWIN pipeline to invoke + /// The OWIN application + /// Configuration options for the middleware + protected OpenIDAuthenticationMiddlewareBase(OwinMiddleware next, IAppBuilder app, T options) + : base(next, options) + { + if (string.IsNullOrWhiteSpace(Options.ProviderDiscoveryUri) && string.IsNullOrWhiteSpace(Options.ProviderLoginUri) && Options.AuthenticationType != Constants.DefaultAuthenticationType) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ProviderDiscoveryUri")); + } + + Logger = app.CreateLogger>(); + + if (Options.Provider == null) Options.Provider = new OpenIDAuthenticationProvider(); + + + if (Options.StateDataFormat == null) + { + var dataProtecter = app.CreateDataProtector(typeof(OpenIDAuthenticationMiddlewareBase).FullName, Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtecter); + } + + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + { + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + } + + HTTPClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + // 10 MB + } + + /// + /// Provides the object for processing authentication-related requests. + /// + /// An configured with the supplied to the constructor. + protected override AuthenticationHandler CreateHandler() + { + return CreateSpecificHandler(); + } + + /// + /// Provides the object for processing authentication-related requests. + /// + /// An configured with the supplied to the constructor. + protected abstract AuthenticationHandler CreateSpecificHandler(); + + private static HttpMessageHandler ResolveHttpMessageHandler(OpenIDAuthenticationOptions options) + { + var 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/OpenID/OpenIDAuthenticationOptions.cs b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationOptions.cs similarity index 93% rename from Owin.Security.Providers/OpenID/OpenIDAuthenticationOptions.cs rename to src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationOptions.cs index 0954cec..9404832 100644 --- a/Owin.Security.Providers/OpenID/OpenIDAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthenticationOptions.cs @@ -4,11 +4,8 @@ using Microsoft.Owin.Security; using System; using System.Net.Http; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { - /// - /// Configuration options for - /// public class OpenIDAuthenticationOptions : AuthenticationOptions { /// @@ -87,8 +84,7 @@ namespace Owin.Security.Providers.OpenID /// /// Initializes a new /// - public OpenIDAuthenticationOptions() - : base(Constants.DefaultAuthenticationType) + public OpenIDAuthenticationOptions() : base(Constants.DefaultAuthenticationType) { Caption = Constants.DefaultAuthenticationType; CallbackPath = new PathString("/signin-openid"); diff --git a/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthorizationEndpointInfo.cs b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthorizationEndpointInfo.cs new file mode 100644 index 0000000..1e6e31c --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/OpenIDAuthorizationEndpointInfo.cs @@ -0,0 +1,9 @@ +namespace Owin.Security.Providers.OpenIDBase +{ + public class OpenIDAuthorizationEndpointInfo + { + + public string Url { get; set; } + + } +} \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenIDBase/Owin.Security.Providers.OpenIDBase.csproj b/src/Owin.Security.Providers.OpenIDBase/Owin.Security.Providers.OpenIDBase.csproj new file mode 100644 index 0000000..c42f6a6 --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/Owin.Security.Providers.OpenIDBase.csproj @@ -0,0 +1,100 @@ + + + + + Debug + AnyCPU + {4FD7B873-1994-4990-AA40-C37060121494} + Library + Properties + Owin.Security.Providers.OpenIDBase + Owin.Security.Providers.OpenIDBase + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenIDBase/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.OpenIDBase/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7691444 --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.OpenIDBaseBase")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.OpenIDBaseBase")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("4fd7b873-1994-4990-aa40-c37060121494")] +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/OpenID/Provider/IOpenIDAuthenticationProvider.cs b/src/Owin.Security.Providers.OpenIDBase/Provider/IOpenIDAuthenticationProvider.cs similarity index 76% rename from Owin.Security.Providers/OpenID/Provider/IOpenIDAuthenticationProvider.cs rename to src/Owin.Security.Providers.OpenIDBase/Provider/IOpenIDAuthenticationProvider.cs index 9012007..fa30a28 100644 --- a/Owin.Security.Providers/OpenID/Provider/IOpenIDAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Provider/IOpenIDAuthenticationProvider.cs @@ -1,14 +1,11 @@ using System.Threading.Tasks; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { - /// - /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> - /// public interface IOpenIDAuthenticationProvider { /// - /// Invoked whenever OpenID succesfully authenticates a user + /// Invoked whenever OpenID successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticatedContext.cs b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticatedContext.cs similarity index 97% rename from Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticatedContext.cs rename to src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticatedContext.cs index 4cd566c..0dfd7b8 100644 --- a/Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticatedContext.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Security.Claims; using System.Xml.Linq; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { /// /// Contains information about the login session as well as the user . diff --git a/Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticationProvider.cs b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticationProvider.cs rename to src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticationProvider.cs index 0b78c74..42a7a7a 100644 --- a/Owin.Security.Providers/OpenID/Provider/OpenIDAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDAuthenticationProvider.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { /// /// Default implementation. @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.OpenID public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever OpenID succesfully authenticates a user + /// Invoked whenever OpenID successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/OpenID/Provider/OpenIDReturnEndpointContext.cs b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDReturnEndpointContext.cs similarity index 93% rename from Owin.Security.Providers/OpenID/Provider/OpenIDReturnEndpointContext.cs rename to src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDReturnEndpointContext.cs index 027abe5..6bb6a21 100644 --- a/Owin.Security.Providers/OpenID/Provider/OpenIDReturnEndpointContext.cs +++ b/src/Owin.Security.Providers.OpenIDBase/Provider/OpenIDReturnEndpointContext.cs @@ -2,7 +2,7 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; -namespace Owin.Security.Providers.OpenID +namespace Owin.Security.Providers.OpenIDBase { /// /// Provides context information to middleware providers. diff --git a/src/Owin.Security.Providers.OpenIDBase/Resources.Designer.cs b/src/Owin.Security.Providers.OpenIDBase/Resources.Designer.cs new file mode 100644 index 0000000..2c6d569 --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.OpenIDBase { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.OpenIDBase.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.OpenIDBase/Resources.resx b/src/Owin.Security.Providers.OpenIDBase/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.OpenIDBase/packages.config b/src/Owin.Security.Providers.OpenIDBase/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.OpenIDBase/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/PayPal/Constants.cs b/src/Owin.Security.Providers.PayPal/Constants.cs similarity index 100% rename from Owin.Security.Providers/PayPal/Constants.cs rename to src/Owin.Security.Providers.PayPal/Constants.cs diff --git a/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.csproj b/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.csproj new file mode 100644 index 0000000..b7bdee8 --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {F7129064-3DB7-4B79-81D3-80130D664E45} + Library + Properties + Owin.Security.Providers.PayPal + Owin.Security.Providers.PayPal + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.PayPal.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.nuspec b/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.nuspec new file mode 100644 index 0000000..b650f87 --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/Owin.Security.Providers.PayPal.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.PayPal + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a PayPal OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth PayPal + + + + + + + + + diff --git a/Owin.Security.Providers/PayPal/PayPalAuthenticationExtensions.cs b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/PayPal/PayPalAuthenticationExtensions.cs rename to src/Owin.Security.Providers.PayPal/PayPalAuthenticationExtensions.cs index caeedbd..95fe9a3 100644 --- a/Owin.Security.Providers/PayPal/PayPalAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.PayPal PayPalAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(PayPalAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/PayPal/PayPalAuthenticationHandler.cs b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationHandler.cs similarity index 58% rename from Owin.Security.Providers/PayPal/PayPalAuthenticationHandler.cs rename to src/Owin.Security.Providers.PayPal/PayPalAuthenticationHandler.cs index fcc4985..944bb68 100644 --- a/Owin.Security.Providers/PayPal/PayPalAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationHandler.cs @@ -4,7 +4,6 @@ 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; @@ -24,21 +23,21 @@ namespace Owin.Security.Providers.PayPal public PayPalAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this._httpClient = httpClient; - this._logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() { AuthenticationProperties properties = null; - string error = "Unknown"; + var error = "Unknown"; try { string code = null; string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if(values != null && values.Count == 1) { @@ -69,13 +68,14 @@ namespace Owin.Security.Providers.PayPal return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Request the token var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", Options.ClientId, Options.ClientSecret)))); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes( + $"{Options.ClientId}:{Options.ClientSecret}"))); requestMessage.Content = new FormUrlEncodedContent(new List>{ new KeyValuePair("grant_type", "authorization_code"), new KeyValuePair("code", code), @@ -135,50 +135,48 @@ namespace Owin.Security.Providers.PayPal return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if(challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if(string.IsNullOrEmpty(properties.RedirectUri)) { - 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); - - // comma separated - string scope = string.Join(" ", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - Options.Endpoints.AuthorizationEndpoint + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&response_type=code" + - "&state=" + Uri.EscapeDataString(state); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&response_type=code" + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -189,50 +187,45 @@ namespace Owin.Security.Providers.PayPal private async Task InvokeReplyPathAsync() { - if(Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + var ticket = await AuthenticateAsync(); + if(ticket == null) { - AuthenticationTicket ticket = await AuthenticateAsync(); - if(ticket == null) - { - _logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new PayPalReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new PayPalReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if(context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/PayPal/PayPalAuthenticationMiddleware.cs b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/PayPal/PayPalAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.PayPal/PayPalAuthenticationMiddleware.cs index 9200e40..0d1a9c8 100644 --- a/Owin.Security.Providers/PayPal/PayPalAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.PayPal { public class PayPalAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public PayPalAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, PayPalAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new PayPalAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (PayPalAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin PayPal middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin PayPal middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.PayPal /// protected override AuthenticationHandler CreateHandler() { - return new PayPalAuthenticationHandler(httpClient, logger); + return new PayPalAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(PayPalAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(PayPalAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/PayPal/PayPalAuthenticationOptions.cs b/src/Owin.Security.Providers.PayPal/PayPalAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/PayPal/PayPalAuthenticationOptions.cs rename to src/Owin.Security.Providers.PayPal/PayPalAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.PayPal/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.PayPal/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..07035f6 --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.PayPal")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.PayPal")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("55281d25-4df4-4d29-bbf8-006d5a4fdd66")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/PayPal/Provider/IPayPalAuthenticationProvider.cs b/src/Owin.Security.Providers.PayPal/Provider/IPayPalAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/PayPal/Provider/IPayPalAuthenticationProvider.cs rename to src/Owin.Security.Providers.PayPal/Provider/IPayPalAuthenticationProvider.cs index a01d6d3..516ccbf 100644 --- a/Owin.Security.Providers/PayPal/Provider/IPayPalAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.PayPal/Provider/IPayPalAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.PayPal public interface IPayPalAuthenticationProvider { /// - /// Invoked whenever PayPal succesfully authenticates a user + /// Invoked whenever PayPal successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/PayPal/Provider/PayPalAuthenticatedContext.cs b/src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/PayPal/Provider/PayPalAuthenticatedContext.cs rename to src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticatedContext.cs index 05cf1dd..29d63a0 100644 --- a/Owin.Security.Providers/PayPal/Provider/PayPalAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/PayPal/Provider/PayPalAuthenticationProvider.cs b/src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/PayPal/Provider/PayPalAuthenticationProvider.cs rename to src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticationProvider.cs index 6d907c4..b244106 100644 --- a/Owin.Security.Providers/PayPal/Provider/PayPalAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.PayPal/Provider/PayPalAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.PayPal public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever PayPal succesfully authenticates a user + /// Invoked whenever PayPal successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/PayPal/Provider/PayPalReturnEndpointContext.cs b/src/Owin.Security.Providers.PayPal/Provider/PayPalReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/PayPal/Provider/PayPalReturnEndpointContext.cs rename to src/Owin.Security.Providers.PayPal/Provider/PayPalReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.PayPal/Resources.Designer.cs b/src/Owin.Security.Providers.PayPal/Resources.Designer.cs new file mode 100644 index 0000000..f32be88 --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.PayPal { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.PayPal.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.PayPal/Resources.resx b/src/Owin.Security.Providers.PayPal/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.PayPal/packages.config b/src/Owin.Security.Providers.PayPal/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.PayPal/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Reddit/Constants.cs b/src/Owin.Security.Providers.Reddit/Constants.cs similarity index 100% rename from Owin.Security.Providers/Reddit/Constants.cs rename to src/Owin.Security.Providers.Reddit/Constants.cs diff --git a/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.csproj b/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.csproj new file mode 100644 index 0000000..c73ea32 --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {D0CD86C8-A6F9-4C6C-9BF0-EAA461E7FBAD} + Library + Properties + Owin.Security.Providers.Reddit + Owin.Security.Providers.Reddit + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Reddit.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.nuspec b/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.nuspec new file mode 100644 index 0000000..09a7b6c --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/Owin.Security.Providers.Reddit.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Reddit + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Reddit OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Reddit + + + + + + + + + diff --git a/src/Owin.Security.Providers.Reddit/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Reddit/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4642a25 --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Reddit")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Reddit")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("5d0f8dfb-2057-48a2-8eef-8f09645e8b4e")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Reddit/Provider/IRedditAuthenticationProvider.cs b/src/Owin.Security.Providers.Reddit/Provider/IRedditAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Reddit/Provider/IRedditAuthenticationProvider.cs rename to src/Owin.Security.Providers.Reddit/Provider/IRedditAuthenticationProvider.cs index ae79a09..301d41c 100644 --- a/Owin.Security.Providers/Reddit/Provider/IRedditAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Reddit/Provider/IRedditAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Reddit.Provider public interface IRedditAuthenticationProvider { /// - /// Invoked whenever Reddit succesfully authenticates a user + /// Invoked whenever Reddit successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Reddit/Provider/RedditAuthenticatedContext.cs b/src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticatedContext.cs similarity index 85% rename from Owin.Security.Providers/Reddit/Provider/RedditAuthenticatedContext.cs rename to src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticatedContext.cs index 2e0c564..17a2667 100644 --- a/Owin.Security.Providers/Reddit/Provider/RedditAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticatedContext.cs @@ -22,6 +22,7 @@ namespace Owin.Security.Providers.Reddit.Provider /// The JSON-serialized user /// Reddit Access token /// Seconds until expiration + /// public RedditAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires, string refreshToken) : base(context) { @@ -29,29 +30,13 @@ namespace Owin.Security.Providers.Reddit.Provider AccessToken = accessToken; RefreshToken = refreshToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } - - /*{ - "has_mail": false, - "name": "************", - "created": 1313605620.0, - "created_utc": 1313602020.0, - "link_karma": 344, - "comment_karma": 1782, - "over_18": true, - "is_gold": false, - "is_mod": true, - "has_verified_email": true, - "id": "5omjg", - "has_mod_mail": false - }*/ Id = TryGetValue(user, "id"); UserName = TryGetValue(user, "name"); OverEighteen = bool.Parse(TryGetValue(user, "over_18")); - } public bool OverEighteen { get; set; } diff --git a/Owin.Security.Providers/Reddit/Provider/RedditAuthenticationProvider.cs b/src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Reddit/Provider/RedditAuthenticationProvider.cs rename to src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticationProvider.cs index 39f87c4..7cbd41c 100644 --- a/Owin.Security.Providers/Reddit/Provider/RedditAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Reddit/Provider/RedditAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Reddit.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Reddit succesfully authenticates a user + /// Invoked whenever Reddit successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Reddit/Provider/RedditReturnEndpointContext.cs b/src/Owin.Security.Providers.Reddit/Provider/RedditReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Reddit/Provider/RedditReturnEndpointContext.cs rename to src/Owin.Security.Providers.Reddit/Provider/RedditReturnEndpointContext.cs diff --git a/Owin.Security.Providers/Reddit/RedditAuthenticationExtensions.cs b/src/Owin.Security.Providers.Reddit/RedditAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Reddit/RedditAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Reddit/RedditAuthenticationExtensions.cs index 70f2f8a..70732fd 100644 --- a/Owin.Security.Providers/Reddit/RedditAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Reddit/RedditAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Reddit RedditAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(RedditAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Reddit/RedditAuthenticationHandler.cs b/src/Owin.Security.Providers.Reddit/RedditAuthenticationHandler.cs new file mode 100644 index 0000000..4d35578 --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/RedditAuthenticationHandler.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.Reddit.Provider; + +namespace Owin.Security.Providers.Reddit +{ + public class RedditAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://ssl.reddit.com/api/v1/access_token"; + private const string UserInfoEndpoint = "https://oauth.reddit.com/api/v1/me"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public RedditAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("state", state), + new KeyValuePair("scope", string.Join(",", Options.Scope)) + }; + var request = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); + request.Headers.Add("User-Agent", Options.UserAgent); + request.Content = new FormUrlEncodedContent(body); + + // Request the token + + var tokenResponse = + await _httpClient.SendAsync(request); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var expires = (string) response.expires_in; + var refreshToken = (string) response.refresh_token; + + // Get the Reddit user + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); + userRequest.Headers.Add("User-Agent", Options.UserAgent); + userRequest.Headers.Add("Authorization", "bearer " + Uri.EscapeDataString(accessToken) + ""); + var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new RedditAuthenticatedContext(Context, user, accessToken, expires, refreshToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + if (!string.IsNullOrEmpty(context.Id)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.UserName)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:reddit:url", context.Link, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + context.Identity.AddClaim(new Claim("urn:reddit:accesstoken", context.AccessToken, XmlSchemaString, Options.AuthenticationType)); + } + context.Identity.AddClaim(new Claim("urn:reddit:overeighteen", context.OverEighteen.ToString())); + 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://ssl.reddit.com/api/v1/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state) + + "&duration=permanent"; + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new RedditReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Reddit/RedditAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Reddit/RedditAuthenticationMiddleware.cs similarity index 77% rename from Owin.Security.Providers/Reddit/RedditAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Reddit/RedditAuthenticationMiddleware.cs index 6fb9dd8..00b7557 100644 --- a/Owin.Security.Providers/Reddit/RedditAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Reddit/RedditAuthenticationMiddleware.cs @@ -14,37 +14,37 @@ namespace Owin.Security.Providers.Reddit { public class RedditAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public RedditAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, RedditAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientId")); - if (String.IsNullOrWhiteSpace(Options.ClientSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new RedditAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (RedditAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler()) + _httpClient = new HttpClient(ResolveHttpMessageHandler()) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -61,7 +61,7 @@ namespace Owin.Security.Providers.Reddit /// protected override AuthenticationHandler CreateHandler() { - return new RedditAuthenticationHandler(httpClient, logger); + return new RedditAuthenticationHandler(_httpClient, _logger); } private HttpClientHandler ResolveHttpMessageHandler() diff --git a/Owin.Security.Providers/Reddit/RedditAuthenticationOptions.cs b/src/Owin.Security.Providers.Reddit/RedditAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Reddit/RedditAuthenticationOptions.cs rename to src/Owin.Security.Providers.Reddit/RedditAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Reddit/Resources.Designer.cs b/src/Owin.Security.Providers.Reddit/Resources.Designer.cs new file mode 100644 index 0000000..0e961a9 --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Reddit { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Reddit.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Reddit/Resources.resx b/src/Owin.Security.Providers.Reddit/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Reddit/packages.config b/src/Owin.Security.Providers.Reddit/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Reddit/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Salesforce/Constants.cs b/src/Owin.Security.Providers.Salesforce/Constants.cs similarity index 100% rename from Owin.Security.Providers/Salesforce/Constants.cs rename to src/Owin.Security.Providers.Salesforce/Constants.cs diff --git a/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.csproj b/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.csproj new file mode 100644 index 0000000..f3dbdb8 --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {827A9D68-0DD4-4C5E-B763-8302FAEEDECC} + Library + Properties + Owin.Security.Providers.Salesforce + Owin.Security.Providers.Salesforce + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Salesforce.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.nuspec b/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.nuspec new file mode 100644 index 0000000..5a2e335 --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/Owin.Security.Providers.Salesforce.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Salesforce + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Salesforce OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Salesforce + + + + + + + + + diff --git a/src/Owin.Security.Providers.Salesforce/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Salesforce/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..33c383a --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Salesforce")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Salesforce")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("b6a4a4cb-d365-41a0-b4c9-966e7ef19de0")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Salesforce/Provider/ISalesforceAuthenticationProvider.cs b/src/Owin.Security.Providers.Salesforce/Provider/ISalesforceAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/Salesforce/Provider/ISalesforceAuthenticationProvider.cs rename to src/Owin.Security.Providers.Salesforce/Provider/ISalesforceAuthenticationProvider.cs index ae0159a..c4a13aa 100644 --- a/Owin.Security.Providers/Salesforce/Provider/ISalesforceAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Salesforce/Provider/ISalesforceAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Salesforce public interface ISalesforceAuthenticationProvider { /// - /// Invoked whenever Salesforce succesfully authenticates a user + /// Invoked whenever Salesforce successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Salesforce/Provider/SalesforceAuthenticatedContext.cs b/src/Owin.Security.Providers.Salesforce/Provider/SalesforceAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/Salesforce/Provider/SalesforceAuthenticatedContext.cs rename to src/Owin.Security.Providers.Salesforce/Provider/SalesforceAuthenticatedContext.cs diff --git a/Owin.Security.Providers/Salesforce/Provider/SalesforceAuthenticationProvider.cs b/src/Owin.Security.Providers.Salesforce/Provider/SalesforceAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Salesforce/Provider/SalesforceAuthenticationProvider.cs rename to src/Owin.Security.Providers.Salesforce/Provider/SalesforceAuthenticationProvider.cs index ae19d46..0bf27d9 100644 --- a/Owin.Security.Providers/Salesforce/Provider/SalesforceAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Salesforce/Provider/SalesforceAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Salesforce public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Salesforce succesfully authenticates a user + /// Invoked whenever Salesforce successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Salesforce/Provider/SalesforceReturnEndpointContext.cs b/src/Owin.Security.Providers.Salesforce/Provider/SalesforceReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Salesforce/Provider/SalesforceReturnEndpointContext.cs rename to src/Owin.Security.Providers.Salesforce/Provider/SalesforceReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Salesforce/Resources.Designer.cs b/src/Owin.Security.Providers.Salesforce/Resources.Designer.cs new file mode 100644 index 0000000..aecd8b4 --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Salesforce { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Salesforce.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Salesforce/Resources.resx b/src/Owin.Security.Providers.Salesforce/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationExtensions.cs b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/Salesforce/SalesforceAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationExtensions.cs index 02caf1a..05f1881 100644 --- a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Salesforce SalesforceAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(SalesforceAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationHandler.cs b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationHandler.cs similarity index 51% rename from Owin.Security.Providers/Salesforce/SalesforceAuthenticationHandler.cs rename to src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationHandler.cs index a4dbe07..499f929 100644 --- a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationHandler.cs @@ -5,7 +5,6 @@ using System.Net.Http.Headers; using System.Security.Claims; using System.Threading.Tasks; using System.Web; -using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; @@ -19,26 +18,26 @@ namespace Owin.Security.Providers.Salesforce { private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public SalesforceAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() { - AuthenticationProperties properties = new AuthenticationProperties(); + var properties = new AuthenticationProperties(); try { string code = null; string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = values[0]; @@ -56,13 +55,13 @@ namespace Owin.Security.Providers.Salesforce } // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Build up the body for the token request var body = new List> @@ -78,29 +77,31 @@ namespace Owin.Security.Providers.Salesforce var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); requestMessage.Content = new FormUrlEncodedContent(body); - HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); + var tokenResponse = await _httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response dynamic response = JsonConvert.DeserializeObject(text); - string accessToken = (string)response.access_token; - string refreshToken = (string)response.refresh_token; - string instanceUrl = (string)response.instance_url; + var accessToken = (string)response.access_token; + var refreshToken = (string)response.refresh_token; + var instanceUrl = (string)response.instance_url; // Get the Salesforce user using the user info endpoint, which is part of the token - response.id - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, (string)response.id + "?access_token=" + Uri.EscapeDataString(accessToken)); + var userRequest = new HttpRequestMessage(HttpMethod.Get, (string)response.id + "?access_token=" + Uri.EscapeDataString(accessToken)); userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); userResponse.EnsureSuccessStatusCode(); text = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); + var user = JObject.Parse(text); - var context = new SalesforceAuthenticatedContext(Context, user, accessToken, refreshToken, instanceUrl); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); + var context = new SalesforceAuthenticatedContext(Context, user, accessToken, refreshToken, instanceUrl) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; if (!string.IsNullOrEmpty(context.UserId)) { @@ -144,7 +145,7 @@ namespace Owin.Security.Providers.Salesforce } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); } @@ -156,56 +157,45 @@ namespace Owin.Security.Providers.Salesforce return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - 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 state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = string.Format( - "{0}?response_type={1}&client_id={2}&redirect_uri={3}&display={4}&immediate={5}&state={6}&scope={7}", - Options.Endpoints.AuthorizationEndpoint, - "code", - Options.ClientId, - HttpUtility.UrlEncode(redirectUri), - "page", - false, - Uri.EscapeDataString(state), - "" - ); - - if (Options.Prompt != null) - { - authorizationEndpoint += string.Format("&prompt={0}", Options.Prompt); - } - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + $"{Options.Endpoints.AuthorizationEndpoint}?response_type={"code"}&client_id={Options.ClientId}&redirect_uri={HttpUtility.UrlEncode(redirectUri)}&display={"page"}&immediate={false}&state={Uri.EscapeDataString(state)}&scope={""}"; + + if (Options.Prompt != null) + { + authorizationEndpoint += $"&prompt={Options.Prompt}"; + } + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -216,52 +206,47 @@ namespace Owin.Security.Providers.Salesforce private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // 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 SalesforceReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new SalesforceReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } diff --git a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/Salesforce/SalesforceAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationMiddleware.cs index bfb032d..56c5da7 100644 --- a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.Salesforce { public class SalesforceAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public SalesforceAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SalesforceAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new SalesforceAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(SalesforceAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Salesforce middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Salesforce middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.Salesforce /// protected override AuthenticationHandler CreateHandler() { - return new SalesforceAuthenticationHandler(httpClient, logger); + return new SalesforceAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(SalesforceAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(SalesforceAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Salesforce/SalesforceAuthenticationOptions.cs b/src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Salesforce/SalesforceAuthenticationOptions.cs rename to src/Owin.Security.Providers.Salesforce/SalesforceAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Salesforce/packages.config b/src/Owin.Security.Providers.Salesforce/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Salesforce/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Shopify/Constants.cs b/src/Owin.Security.Providers.Shopify/Constants.cs similarity index 100% rename from Owin.Security.Providers/Shopify/Constants.cs rename to src/Owin.Security.Providers.Shopify/Constants.cs diff --git a/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.csproj b/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.csproj new file mode 100644 index 0000000..24d5242 --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {67F12BFB-EB3A-4A86-B5DC-F4C066FDF792} + Library + Properties + Owin.Security.Providers.Shopify + Owin.Security.Providers.Shopify + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Shopify.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.nuspec b/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.nuspec new file mode 100644 index 0000000..12659f2 --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/Owin.Security.Providers.Shopify.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Shopify + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Shopify OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Shopify + + + + + + + + + diff --git a/src/Owin.Security.Providers.Shopify/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Shopify/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7f1abad --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Shopify")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Shopify")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("6a1c64b3-1c63-4592-916d-7544f19c55d0")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Shopify/Provider/IShopifyAuthenticationProvider.cs b/src/Owin.Security.Providers.Shopify/Provider/IShopifyAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Shopify/Provider/IShopifyAuthenticationProvider.cs rename to src/Owin.Security.Providers.Shopify/Provider/IShopifyAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Shopify/Provider/ShopifyAuthenticatedContext.cs b/src/Owin.Security.Providers.Shopify/Provider/ShopifyAuthenticatedContext.cs similarity index 91% rename from Owin.Security.Providers/Shopify/Provider/ShopifyAuthenticatedContext.cs rename to src/Owin.Security.Providers.Shopify/Provider/ShopifyAuthenticatedContext.cs index a0d6025..efdf857 100644 --- a/Owin.Security.Providers/Shopify/Provider/ShopifyAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Shopify/Provider/ShopifyAuthenticatedContext.cs @@ -77,15 +77,9 @@ private static string TryGetValue(JToken shop, string propertyName) { - if (shop != null && shop.First != null && shop.First.First != null) - { - var propertyValue = shop.First.First[propertyName]; + var propertyValue = shop?.First?.First?[propertyName]; - if (propertyValue != null) - return propertyValue.ToString(); - } - - return null; + return propertyValue?.ToString(); } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Shopify/Provider/ShopifyAuthenticationProvider.cs b/src/Owin.Security.Providers.Shopify/Provider/ShopifyAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Shopify/Provider/ShopifyAuthenticationProvider.cs rename to src/Owin.Security.Providers.Shopify/Provider/ShopifyAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Shopify/Provider/ShopifyReturnEndpointContext.cs b/src/Owin.Security.Providers.Shopify/Provider/ShopifyReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Shopify/Provider/ShopifyReturnEndpointContext.cs rename to src/Owin.Security.Providers.Shopify/Provider/ShopifyReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Shopify/Resources.Designer.cs b/src/Owin.Security.Providers.Shopify/Resources.Designer.cs new file mode 100644 index 0000000..194206b --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Shopify { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Shopify.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Shopify/Resources.resx b/src/Owin.Security.Providers.Shopify/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Shopify/ShopifyAuthenticationExtensions.cs b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationExtensions.cs similarity index 87% rename from Owin.Security.Providers/Shopify/ShopifyAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Shopify/ShopifyAuthenticationExtensions.cs index fe74719..2919e51 100644 --- a/Owin.Security.Providers/Shopify/ShopifyAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationExtensions.cs @@ -8,18 +8,18 @@ /// Use Shopify Shop OAuth authentication. /// /// Instance of . - /// Shopify overrided authentication options. + /// Shopify overwritten authentication options. /// Returns instance of . public static IAppBuilder UseShopifyAuthentication(this IAppBuilder app, ShopifyAuthenticationOptions options) { if (null == app) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (null == options) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } app.Use(typeof(ShopifyAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Shopify/ShopifyAuthenticationHandler.cs b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationHandler.cs similarity index 94% rename from Owin.Security.Providers/Shopify/ShopifyAuthenticationHandler.cs rename to src/Owin.Security.Providers.Shopify/ShopifyAuthenticationHandler.cs index b00ba47..02d90f1 100644 --- a/Owin.Security.Providers/Shopify/ShopifyAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationHandler.cs @@ -17,13 +17,13 @@ public class ShopifyAuthenticationHandler : AuthenticationHandler { private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public ShopifyAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -54,7 +54,7 @@ } //// OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } @@ -81,7 +81,7 @@ var requestMessage = new HttpRequestMessage(HttpMethod.Post, string.Format(CultureInfo.CurrentCulture, Options.Endpoints.TokenEndpoint, currentShopifyShopName)); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); requestMessage.Content = new FormUrlEncodedContent(body); - var tokenResponse = await httpClient.SendAsync(requestMessage); + var tokenResponse = await _httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); var text = await tokenResponse.Content.ReadAsStringAsync(); @@ -92,7 +92,7 @@ //// Get the Shopify shop information var shopRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(CultureInfo.CurrentCulture, Options.Endpoints.ShopInfoEndpoint, currentShopifyShopName) + "?access_token=" + Uri.EscapeDataString(accessToken)); shopRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var shopResponse = await httpClient.SendAsync(shopRequest, Request.CallCancelled); + var shopResponse = await _httpClient.SendAsync(shopRequest, Request.CallCancelled); shopResponse.EnsureSuccessStatusCode(); text = await shopResponse.Content.ReadAsStringAsync(); var shopifyShop = JObject.Parse(text); @@ -127,7 +127,7 @@ } catch (Exception exception) { - logger.WriteError(exception.Message); + _logger.WriteError(exception.Message); } return new AuthenticationTicket(null, properties); @@ -187,7 +187,7 @@ var ticket = await AuthenticateAsync(); if (null == ticket) { - logger.WriteWarning("Invalid return state, unable to redirect."); + _logger.WriteWarning("Invalid return state, unable to redirect."); Response.StatusCode = 500; return true; } diff --git a/Owin.Security.Providers/Shopify/ShopifyAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationMiddleware.cs similarity index 86% rename from Owin.Security.Providers/Shopify/ShopifyAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Shopify/ShopifyAuthenticationMiddleware.cs index 51a537f..2ff0df8 100644 --- a/Owin.Security.Providers/Shopify/ShopifyAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationMiddleware.cs @@ -6,15 +6,14 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; - using Properties; using System; using System.Globalization; using System.Net.Http; public class ShopifyAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public ShopifyAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, ShopifyAuthenticationOptions options) : base(next, options) @@ -29,7 +28,7 @@ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ApiSecret")); } - this.logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (null == Options.Provider) { Options.Provider = new ShopifyAuthenticationProvider(); @@ -46,14 +45,14 @@ Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } - this.httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 }; - this.httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Shopify middleware"); - this.httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Shopify middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,7 +61,7 @@ /// An configured with the supplied to the constructor. protected override AuthenticationHandler CreateHandler() { - return new ShopifyAuthenticationHandler(httpClient, logger); + return new ShopifyAuthenticationHandler(_httpClient, _logger); } private static HttpMessageHandler ResolveHttpMessageHandler(ShopifyAuthenticationOptions options) diff --git a/Owin.Security.Providers/Shopify/ShopifyAuthenticationOptions.cs b/src/Owin.Security.Providers.Shopify/ShopifyAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Shopify/ShopifyAuthenticationOptions.cs rename to src/Owin.Security.Providers.Shopify/ShopifyAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Shopify/packages.config b/src/Owin.Security.Providers.Shopify/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Shopify/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Slack/Constants.cs b/src/Owin.Security.Providers.Slack/Constants.cs similarity index 100% rename from Owin.Security.Providers/Slack/Constants.cs rename to src/Owin.Security.Providers.Slack/Constants.cs diff --git a/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.csproj b/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.csproj new file mode 100644 index 0000000..97cae6d --- /dev/null +++ b/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {3E6F293D-8500-428D-BDC9-27440CC91E16} + Library + Properties + Owin.Security.Providers.Slack + Owin.Security.Providers.Slack + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Slack.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.nuspec b/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.nuspec new file mode 100644 index 0000000..5572f2b --- /dev/null +++ b/src/Owin.Security.Providers.Slack/Owin.Security.Providers.Slack.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Slack + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Slack OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Slack + + + + + + + + + diff --git a/src/Owin.Security.Providers.Slack/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Slack/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..272084e --- /dev/null +++ b/src/Owin.Security.Providers.Slack/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Slack")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Slack")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("8136a4f8-f690-49b9-ac45-0c974c8ecacc")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Slack/Provider/ISlackAuthenticationProvider.cs b/src/Owin.Security.Providers.Slack/Provider/ISlackAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/Slack/Provider/ISlackAuthenticationProvider.cs rename to src/Owin.Security.Providers.Slack/Provider/ISlackAuthenticationProvider.cs index e57556e..faa38eb 100644 --- a/Owin.Security.Providers/Slack/Provider/ISlackAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Slack/Provider/ISlackAuthenticationProvider.cs @@ -5,7 +5,7 @@ namespace Owin.Security.Providers.Slack.Provider public interface ISlackAuthenticationProvider { /// - /// Invoked whenever Slack succesfully authenticates a user + /// Invoked whenever Slack successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Slack/Provider/SlackAuthenticatedContext.cs b/src/Owin.Security.Providers.Slack/Provider/SlackAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/Slack/Provider/SlackAuthenticatedContext.cs rename to src/Owin.Security.Providers.Slack/Provider/SlackAuthenticatedContext.cs diff --git a/Owin.Security.Providers/Slack/Provider/SlackAuthenticationProvider.cs b/src/Owin.Security.Providers.Slack/Provider/SlackAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Slack/Provider/SlackAuthenticationProvider.cs rename to src/Owin.Security.Providers.Slack/Provider/SlackAuthenticationProvider.cs index 8e68b40..3cdc06a 100644 --- a/Owin.Security.Providers/Slack/Provider/SlackAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Slack/Provider/SlackAuthenticationProvider.cs @@ -25,7 +25,7 @@ namespace Owin.Security.Providers.Slack.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Slack succesfully authenticates a user + /// Invoked whenever Slack successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Slack/Provider/SlackReturnEndpointContext.cs b/src/Owin.Security.Providers.Slack/Provider/SlackReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Slack/Provider/SlackReturnEndpointContext.cs rename to src/Owin.Security.Providers.Slack/Provider/SlackReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Slack/Resources.Designer.cs b/src/Owin.Security.Providers.Slack/Resources.Designer.cs new file mode 100644 index 0000000..a6362b5 --- /dev/null +++ b/src/Owin.Security.Providers.Slack/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Slack { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Slack.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Slack/Resources.resx b/src/Owin.Security.Providers.Slack/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Slack/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Slack/SlackAuthenticationExtensions.cs b/src/Owin.Security.Providers.Slack/SlackAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Slack/SlackAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Slack/SlackAuthenticationExtensions.cs index 1a47871..14c4855 100644 --- a/Owin.Security.Providers/Slack/SlackAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Slack/SlackAuthenticationExtensions.cs @@ -7,9 +7,9 @@ namespace Owin.Security.Providers.Slack public static IAppBuilder UseSlackAuthentication(this IAppBuilder app, SlackAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(SlackAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Slack/SlackAuthenticationHandler.cs b/src/Owin.Security.Providers.Slack/SlackAuthenticationHandler.cs new file mode 100644 index 0000000..1b50b23 --- /dev/null +++ b/src/Owin.Security.Providers.Slack/SlackAuthenticationHandler.cs @@ -0,0 +1,250 @@ +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.Slack.Provider; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Slack +{ + public class SlackAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://slack.com/api/oauth.access"; + private const string UserInfoEndpoint = "https://slack.com/api/auth.test"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public SlackAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri) + }; + + var secret = Options.ClientId + ":" + Options.ClientSecret; + var secretBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret)); + + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); + tokenRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", secretBase64); + tokenRequest.Content = new FormUrlEncodedContent(body); + + // Request the token + var tokenResponse = await _httpClient.SendAsync(tokenRequest); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var scope = (string)response.scope; + + // Get the Slack user + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "?token=" + Uri.EscapeDataString(accessToken)); + + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new SlackAuthenticatedContext(Context, user, accessToken, scope) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + if (!string.IsNullOrEmpty(context.UserId)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.UserName)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.TeamId)) + { + context.Identity.AddClaim(new Claim("urn:slack:teamid", context.TeamId, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.TeamName)) + { + context.Identity.AddClaim(new Claim("urn:slack:teamname", context.TeamName, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.TeamUrl)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Webpage, context.TeamUrl, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://slack.com/oauth/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state) + + "&team=" + Uri.EscapeDataString(Options.TeamId); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new SlackReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} diff --git a/Owin.Security.Providers/Slack/SlackAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Slack/SlackAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/Slack/SlackAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Slack/SlackAuthenticationMiddleware.cs index 97517f7..8a07f9d 100644 --- a/Owin.Security.Providers/Slack/SlackAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Slack/SlackAuthenticationMiddleware.cs @@ -4,7 +4,6 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.Slack.Provider; using System; using System.Globalization; @@ -14,43 +13,43 @@ namespace Owin.Security.Providers.Slack { public class SlackAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public SlackAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SlackAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientId")); - if (String.IsNullOrWhiteSpace(Options.ClientSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new SlackAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(SlackAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Slack middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Slack middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } @@ -64,24 +63,22 @@ namespace Owin.Security.Providers.Slack /// protected override AuthenticationHandler CreateHandler() { - return new SlackAuthenticationHandler(httpClient, logger); + return new SlackAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(SlackAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(SlackAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Slack/SlackAuthenticationOptions.cs b/src/Owin.Security.Providers.Slack/SlackAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Slack/SlackAuthenticationOptions.cs rename to src/Owin.Security.Providers.Slack/SlackAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Slack/packages.config b/src/Owin.Security.Providers.Slack/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Slack/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/SoundCloud/Constants.cs b/src/Owin.Security.Providers.SoundCloud/Constants.cs similarity index 100% rename from Owin.Security.Providers/SoundCloud/Constants.cs rename to src/Owin.Security.Providers.SoundCloud/Constants.cs diff --git a/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.csproj b/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.csproj new file mode 100644 index 0000000..c98f0fd --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {2C959026-7058-4302-A6C4-DFD10A030585} + Library + Properties + Owin.Security.Providers.SoundCloud + Owin.Security.Providers.SoundCloud + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.SoundCloud.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.nuspec b/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.nuspec new file mode 100644 index 0000000..b89cfc1 --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/Owin.Security.Providers.SoundCloud.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.SoundCloud + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a SoundCloud OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth SoundCloud + + + + + + + + + diff --git a/src/Owin.Security.Providers.SoundCloud/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.SoundCloud/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e984c8a --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.SoundCloud")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.SoundCloud")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("cc398065-b8fd-484d-afc4-93782ee7f56a")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs b/src/Owin.Security.Providers.SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs rename to src/Owin.Security.Providers.SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs index 276a564..2e8f47e 100644 --- a/Owin.Security.Providers/SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.SoundCloud/Provider/ISoundCloudAuthenticationProvider.cs @@ -9,7 +9,7 @@ namespace Owin.Security.Providers.SoundCloud.Provider public interface ISoundCloudAuthenticationProvider { /// - /// Invoked whenever SoundCloud succesfully authenticates a user + /// Invoked whenever SoundCloud successfully authenticates a user /// /// /// Contains information about the login session as well as the user diff --git a/Owin.Security.Providers/SoundCloud/Provider/SoundCloudAuthenticatedContext.cs b/src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/SoundCloud/Provider/SoundCloudAuthenticatedContext.cs rename to src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudAuthenticatedContext.cs diff --git a/Owin.Security.Providers/SoundCloud/Provider/SoundCloudAuthenticationProvider.cs b/src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/SoundCloud/Provider/SoundCloudAuthenticationProvider.cs rename to src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudAuthenticationProvider.cs index 457ccd1..ce3129e 100644 --- a/Owin.Security.Providers/SoundCloud/Provider/SoundCloudAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.SoundCloud.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever SoundCloud succesfully authenticates a user + /// Invoked whenever SoundCloud successfully authenticates a user /// /// /// Contains information about the login session as well as the user diff --git a/Owin.Security.Providers/SoundCloud/Provider/SoundCloudReturnEndpointContext.cs b/src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/SoundCloud/Provider/SoundCloudReturnEndpointContext.cs rename to src/Owin.Security.Providers.SoundCloud/Provider/SoundCloudReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.SoundCloud/Resources.Designer.cs b/src/Owin.Security.Providers.SoundCloud/Resources.Designer.cs new file mode 100644 index 0000000..c420b8a --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.SoundCloud { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.SoundCloud.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.SoundCloud/Resources.resx b/src/Owin.Security.Providers.SoundCloud/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationExtensions.cs b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationExtensions.cs rename to src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationExtensions.cs index 3d01dac..56c1e51 100644 --- a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.SoundCloud SoundCloudAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof (SoundCloudAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationHandler.cs b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationHandler.cs similarity index 57% rename from Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationHandler.cs rename to src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationHandler.cs index de799d2..75e744c 100644 --- a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationHandler.cs @@ -19,13 +19,13 @@ namespace Owin.Security.Providers.SoundCloud private const string TokenEndpoint = "https://api.soundcloud.com/oauth2/token"; private const string ConnectEndpoint = "https://soundcloud.com/connect"; private const string UserInfoEndpoint = "https://api.soundcloud.com/me.json"; - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public SoundCloudAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -54,7 +54,7 @@ namespace Owin.Security.Providers.SoundCloud return null; } - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } @@ -71,7 +71,7 @@ namespace Owin.Security.Providers.SoundCloud new KeyValuePair("client_secret", Options.ClientSecret) }; - var tokenResponse = await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + var tokenResponse = await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); tokenResponse.EnsureSuccessStatusCode(); var text = await tokenResponse.Content.ReadAsStringAsync(); @@ -81,7 +81,7 @@ namespace Owin.Security.Providers.SoundCloud var userRequest = new HttpRequestMessage( HttpMethod.Get, UserInfoEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken)); - var userResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); userResponse.EnsureSuccessStatusCode(); text = await userResponse.Content.ReadAsStringAsync(); var user = JObject.Parse(text); @@ -117,7 +117,7 @@ namespace Owin.Security.Providers.SoundCloud } catch (Exception exception) { - logger.WriteError(exception.Message); + _logger.WriteError(exception.Message); } return new AuthenticationTicket(null, properties); } @@ -131,46 +131,44 @@ namespace Owin.Security.Providers.SoundCloud var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - var baseUri = - Request.Scheme + - Uri.SchemeDelimiter + - Request.Host + - Request.PathBase; - - var currentUri = - baseUri + - Request.Path + - Request.QueryString; - - var redirectUri = - baseUri + - Options.CallbackPath; - - var properties = challenge.Properties; - if (string.IsNullOrEmpty(properties.RedirectUri)) - { - properties.RedirectUri = currentUri; - } - - // OAuth2 10.12 CSRF - GenerateCorrelationId(properties); - - var state = Options.StateDataFormat.Protect(properties); - - var authorizationEndpoint = - ConnectEndpoint - + "?client_id=" + Uri.EscapeDataString(Options.ClientId) - + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) - + "&response_type=" + "code" - + "&scope=" + "non-expiring" - + "&display=" + "popup" - + "&state=" + Uri.EscapeDataString(state); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + ConnectEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&response_type=" + "code" + + "&scope=" + "non-expiring" + + "&display=" + "popup" + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -181,58 +179,53 @@ namespace Owin.Security.Providers.SoundCloud private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // TODO: error responses - - var ticket = await AuthenticateAsync(); - if (ticket == null) - { - logger.WriteWarning("Invalid return state, unable to redirect."); - Response.StatusCode = 500; - return true; - } - - var context = new SoundCloudReturnEndpointContext(Context, ticket) - { - SignInAsAuthenticationType = Options.SignInAsAuthenticationType, - RedirectUri = ticket.Properties.RedirectUri - }; - - await Options.Provider.ReturnEndpoint(context); - - if (context.SignInAsAuthenticationType != null && context.Identity != null) - { - var 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) - { - var 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new SoundCloudReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationMiddleware.cs b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationMiddleware.cs similarity index 57% rename from Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationMiddleware.cs index cbb2905..a4ea766 100644 --- a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationMiddleware.cs @@ -7,29 +7,28 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.SoundCloud.Provider; namespace Owin.Security.Providers.SoundCloud { public class SoundCloudAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public SoundCloudAuthenticationMiddleware( OwinMiddleware next, IAppBuilder app, SoundCloudAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new SoundCloudAuthenticationProvider(); @@ -42,38 +41,36 @@ namespace Owin.Security.Providers.SoundCloud Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin SoundCloud middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin SoundCloud middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } protected override AuthenticationHandler CreateHandler() { - return new SoundCloudAuthenticationHandler(httpClient, logger); + return new SoundCloudAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(SoundCloudAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(SoundCloudAuthenticationOptions options) { var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationOptions.cs b/src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/SoundCloud/SoundCloudAuthenticationOptions.cs rename to src/Owin.Security.Providers.SoundCloud/SoundCloudAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.SoundCloud/packages.config b/src/Owin.Security.Providers.SoundCloud/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.SoundCloud/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Spotify/Constants.cs b/src/Owin.Security.Providers.Spotify/Constants.cs similarity index 100% rename from Owin.Security.Providers/Spotify/Constants.cs rename to src/Owin.Security.Providers.Spotify/Constants.cs diff --git a/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.csproj b/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.csproj new file mode 100644 index 0000000..5a095c7 --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {683B4041-A399-40CE-84B8-392F08A6805D} + Library + Properties + Owin.Security.Providers.Spotify + Owin.Security.Providers.Spotify + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Spotify.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.nuspec b/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.nuspec new file mode 100644 index 0000000..b8c1aac --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/Owin.Security.Providers.Spotify.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Spotify + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Spotify OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Spotify + + + + + + + + + diff --git a/src/Owin.Security.Providers.Spotify/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Spotify/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..854c020 --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Spotify")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Spotify")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("0e329a9c-f1ea-43c1-806e-84e05e6281bd")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Spotify/Provider/ISpotifyAuthenticationProvider.cs b/src/Owin.Security.Providers.Spotify/Provider/ISpotifyAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Spotify/Provider/ISpotifyAuthenticationProvider.cs rename to src/Owin.Security.Providers.Spotify/Provider/ISpotifyAuthenticationProvider.cs index 46aa68f..f6dacfe 100644 --- a/Owin.Security.Providers/Spotify/Provider/ISpotifyAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Spotify/Provider/ISpotifyAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Spotify.Provider public interface ISpotifyAuthenticationProvider { /// - /// Invoked whenever Spotify succesfully authenticates a user + /// Invoked whenever Spotify successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticatedContext.cs b/src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticatedContext.cs similarity index 91% rename from Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticatedContext.cs rename to src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticatedContext.cs index 2be9efc..d942bce 100644 --- a/Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticatedContext.cs @@ -18,7 +18,7 @@ namespace Owin.Security.Providers.Spotify.Provider RefreshToken = refreshToken; int expiresValue; - if (Int32.TryParse(expiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } @@ -86,12 +86,12 @@ namespace Owin.Security.Providers.Spotify.Provider private static string TryGetListValue(JObject user, string listPropertyName, int listPosition, string listEntryPropertyName) { JToken listValue; - bool valueExists = user.TryGetValue(listPropertyName, out listValue); + var valueExists = user.TryGetValue(listPropertyName, out listValue); if (!valueExists) return null; - JArray list = (JArray)listValue; + var list = (JArray)listValue; if (list.Count <= listPosition) return null; - JToken entry = list[listPosition]; + var entry = list[listPosition]; return entry.Value(listEntryPropertyName); } diff --git a/Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticationProvider.cs b/src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticationProvider.cs rename to src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticationProvider.cs index e8cee09..7049764 100644 --- a/Owin.Security.Providers/Spotify/Provider/SpotifyAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Spotify/Provider/SpotifyAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Spotify.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Spotify succesfully authenticates a user + /// Invoked whenever Spotify successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Spotify/Provider/SpotifyReturnEndpointContext.cs b/src/Owin.Security.Providers.Spotify/Provider/SpotifyReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Spotify/Provider/SpotifyReturnEndpointContext.cs rename to src/Owin.Security.Providers.Spotify/Provider/SpotifyReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Spotify/Resources.Designer.cs b/src/Owin.Security.Providers.Spotify/Resources.Designer.cs new file mode 100644 index 0000000..ef7c768 --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Spotify { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Spotify.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Spotify/Resources.resx b/src/Owin.Security.Providers.Spotify/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Spotify/SpotifyAuthenticationExtensions.cs b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Spotify/SpotifyAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Spotify/SpotifyAuthenticationExtensions.cs index 821ebbe..e69295c 100644 --- a/Owin.Security.Providers/Spotify/SpotifyAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Spotify SpotifyAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(SpotifyAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationHandler.cs b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationHandler.cs new file mode 100644 index 0000000..86e2cbf --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationHandler.cs @@ -0,0 +1,241 @@ +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.Spotify.Provider; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Spotify +{ + public class SpotifyAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://accounts.spotify.com/api/token"; + private const string UserInfoEndpoint = "https://api.spotify.com/v1/me"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public SpotifyAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + // Check for error + if (Request.Query.Get("error") != null) + return new AuthenticationTicket(null, properties); + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri) + }; + + var secret = Options.ClientId + ":" + Options.ClientSecret; + var secretBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret)); + + var tokenRequest = new HttpRequestMessage(HttpMethod.Post, TokenEndpoint); + tokenRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", secretBase64); + tokenRequest.Content = new FormUrlEncodedContent(body); + + // Request the token + var tokenResponse = await _httpClient.SendAsync(tokenRequest); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var refreshToken = (string)response.refresh_token; + var expiresIn = (string)response.expires_in; + + // Get the Spotify user + var graphRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); + graphRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var graphResponse = await _httpClient.SendAsync(graphRequest, Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new SpotifyAuthenticatedContext(Context, user, accessToken, refreshToken, expiresIn) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.ProfilePicture)) + { + context.Identity.AddClaim(new Claim("urn:spotify:profilepicture", context.ProfilePicture, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://accounts.spotify.com/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new SpotifyReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Spotify/SpotifyAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/Spotify/SpotifyAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Spotify/SpotifyAuthenticationMiddleware.cs index acbaaa8..4367f40 100644 --- a/Owin.Security.Providers/Spotify/SpotifyAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationMiddleware.cs @@ -4,7 +4,6 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.Spotify.Provider; using System; using System.Globalization; @@ -14,37 +13,37 @@ namespace Owin.Security.Providers.Spotify { public class SpotifyAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public SpotifyAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SpotifyAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new SpotifyAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(SpotifyAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 @@ -61,24 +60,22 @@ namespace Owin.Security.Providers.Spotify /// protected override AuthenticationHandler CreateHandler() { - return new SpotifyAuthenticationHandler(httpClient, logger); + return new SpotifyAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(SpotifyAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(SpotifyAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Spotify/SpotifyAuthenticationOptions.cs b/src/Owin.Security.Providers.Spotify/SpotifyAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Spotify/SpotifyAuthenticationOptions.cs rename to src/Owin.Security.Providers.Spotify/SpotifyAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Spotify/packages.config b/src/Owin.Security.Providers.Spotify/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Spotify/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/StackExchange/Constants.cs b/src/Owin.Security.Providers.StackExchange/Constants.cs similarity index 100% rename from Owin.Security.Providers/StackExchange/Constants.cs rename to src/Owin.Security.Providers.StackExchange/Constants.cs diff --git a/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.csproj b/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.csproj new file mode 100644 index 0000000..538aaa8 --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {2C0E07ED-F26D-4FF8-8C3D-F760C09A2D5A} + Library + Properties + Owin.Security.Providers.StackExchange + Owin.Security.Providers.StackExchange + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.StackExchange.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.nuspec b/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.nuspec new file mode 100644 index 0000000..7bf76e2 --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/Owin.Security.Providers.StackExchange.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.StackExchange + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a StackExchange OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth StackExchange + + + + + + + + + diff --git a/src/Owin.Security.Providers.StackExchange/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.StackExchange/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..66b40e4 --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.StackExchange")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.StackExchange")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("c71a88f2-9163-46a1-879c-ba97a25e8dde")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/StackExchange/Provider/IStackExchangeAuthenticationProvider.cs b/src/Owin.Security.Providers.StackExchange/Provider/IStackExchangeAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/StackExchange/Provider/IStackExchangeAuthenticationProvider.cs rename to src/Owin.Security.Providers.StackExchange/Provider/IStackExchangeAuthenticationProvider.cs index a99ee19..00f035d 100644 --- a/Owin.Security.Providers/StackExchange/Provider/IStackExchangeAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.StackExchange/Provider/IStackExchangeAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.StackExchange public interface IStackExchangeAuthenticationProvider { /// - /// Invoked whenever StackExchange succesfully authenticates a user + /// Invoked whenever StackExchange successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticatedContext.cs b/src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticatedContext.cs similarity index 90% rename from Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticatedContext.cs rename to src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticatedContext.cs index 1291325..c99d6b3 100644 --- a/Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticatedContext.cs @@ -29,7 +29,7 @@ namespace Owin.Security.Providers.StackExchange AccessToken = accessToken; int expiresValue; - if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) { ExpiresIn = TimeSpan.FromSeconds(expiresValue); } @@ -45,9 +45,9 @@ namespace Owin.Security.Providers.StackExchange /// Gets the JSON-serialized user /// /// - /// The endpoint https://api.stackexchange.com/2.2/me?order=desc&sort=reputation&site=stackoverflow&key={{key}}&access_token={{access_token}} returns a list of user accounts. This represents the first user account in that list. + /// The endpoint https://api.stackexchange.com/2.2/me?order=desc&sort=reputation&site=stackoverflow&key={{key}}&access_token={{access_token}} returns a list of user accounts. This represents the first user account in that list. /// The user object schema can be found here - http://api.stackexchange.com/docs/types/user - /// Sample user objects can be viewed in the response of /users API here - http://api.stackexchange.com/docs/users#order=desc&sort=reputation&filter=default&site=stackoverflow&run=true + /// Sample user objects can be viewed in the response of /users API here - http://api.stackexchange.com/docs/users#order=desc&sort=reputation&filter=default&site=stackoverflow&run=true /// public JObject User { get; private set; } diff --git a/Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticationProvider.cs b/src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticationProvider.cs rename to src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticationProvider.cs index ce4fe91..4856f82 100644 --- a/Owin.Security.Providers/StackExchange/Provider/StackExchangeAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.StackExchange/Provider/StackExchangeAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.StackExchange public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever StackExchange succesfully authenticates a user + /// Invoked whenever StackExchange successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/StackExchange/Provider/StackExchangeReturnEndpointContext.cs b/src/Owin.Security.Providers.StackExchange/Provider/StackExchangeReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/StackExchange/Provider/StackExchangeReturnEndpointContext.cs rename to src/Owin.Security.Providers.StackExchange/Provider/StackExchangeReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.StackExchange/Resources.Designer.cs b/src/Owin.Security.Providers.StackExchange/Resources.Designer.cs new file mode 100644 index 0000000..b8069ae --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.StackExchange { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.StackExchange.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.StackExchange/Resources.resx b/src/Owin.Security.Providers.StackExchange/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationExtensions.cs b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationExtensions.cs similarity index 87% rename from Owin.Security.Providers/StackExchange/StackExchangeAuthenticationExtensions.cs rename to src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationExtensions.cs index 79eb38e..58bc9eb 100644 --- a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.StackExchange StackExchangeAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(StackExchangeAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationHandler.cs b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationHandler.cs similarity index 52% rename from Owin.Security.Providers/StackExchange/StackExchangeAuthenticationHandler.cs rename to src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationHandler.cs index 4f690c1..029bb37 100644 --- a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationHandler.cs @@ -1,6 +1,5 @@ using System.IO; using System.IO.Compression; -using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; @@ -21,13 +20,13 @@ namespace Owin.Security.Providers.StackExchange private const string TokenEndpoint = "https://stackexchange.com/oauth/access_token"; private const string UserInfoEndpoint = "https://api.stackexchange.com/2.2/me?order=desc&sort=reputation&site=stackoverflow"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public StackExchangeAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -39,8 +38,8 @@ namespace Owin.Security.Providers.StackExchange string code = null; string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = values[0]; @@ -58,36 +57,38 @@ namespace Owin.Security.Providers.StackExchange } // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; // Build up the body for the token request - var body = new List>(); - body.Add(new KeyValuePair("code", code)); - body.Add(new KeyValuePair("redirect_uri", redirectUri)); - body.Add(new KeyValuePair("client_id", Options.ClientId)); - body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + var body = new List> + { + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; // Request the token - HttpResponseMessage tokenResponse = - await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response //dynamic response = JsonConvert.DeserializeObject(text); var responseTokens = HttpUtility.ParseQueryString(text); - string accessToken = responseTokens["access_token"]; - string expires = responseTokens["expires"]; + var accessToken = responseTokens["access_token"]; + var expires = responseTokens["expires"]; // Get the StackExchange user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "&access_token=" + Uri.EscapeDataString(accessToken) + "&key=" + Uri.EscapeDataString(Options.Key)); - HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint + "&access_token=" + Uri.EscapeDataString(accessToken) + "&key=" + Uri.EscapeDataString(Options.Key)); + var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); graphResponse.EnsureSuccessStatusCode(); //According to http://api.stackexchange.com/docs/compression - @@ -100,9 +101,9 @@ namespace Owin.Security.Providers.StackExchange text= sr.ReadToEnd(); } - JObject userResponse = JObject.Parse(text); + var userResponse = JObject.Parse(text); JToken userAccounts; - JObject user = userResponse.TryGetValue("items", out userAccounts) + var user = userResponse.TryGetValue("items", out userAccounts) ? userAccounts.First as JObject : null; @@ -146,7 +147,7 @@ namespace Owin.Security.Providers.StackExchange } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); } @@ -158,49 +159,47 @@ namespace Owin.Security.Providers.StackExchange return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - 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); - - // comma separated - string scope = string.Join(",", Options.Scope); - - string state = Options.StateDataFormat.Protect(properties); - - string authorizationEndpoint = - "https://stackexchange.com/oauth" + - "?client_id=" + Uri.EscapeDataString(Options.ClientId) + - "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + - "&scope=" + Uri.EscapeDataString(scope) + - "&state=" + Uri.EscapeDataString(state); - - Response.Redirect(authorizationEndpoint); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://stackexchange.com/oauth" + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + return Task.FromResult(null); } @@ -211,50 +210,47 @@ namespace Owin.Security.Providers.StackExchange private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // 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 StackExchangeReturnEndpointContext(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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new StackExchangeReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationMiddleware.cs b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationMiddleware.cs similarity index 60% rename from Owin.Security.Providers/StackExchange/StackExchangeAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationMiddleware.cs index 1e187bb..41dd882 100644 --- a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationMiddleware.cs @@ -7,46 +7,45 @@ 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.StackExchange { public class StackExchangeAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public StackExchangeAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, StackExchangeAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - if (String.IsNullOrWhiteSpace(Options.Key)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.Key)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "Key")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new StackExchangeAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (StackExchangeAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -63,24 +62,22 @@ namespace Owin.Security.Providers.StackExchange /// protected override AuthenticationHandler CreateHandler() { - return new StackExchangeAuthenticationHandler(httpClient, logger); + return new StackExchangeAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(StackExchangeAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(StackExchangeAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/StackExchange/StackExchangeAuthenticationOptions.cs b/src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/StackExchange/StackExchangeAuthenticationOptions.cs rename to src/Owin.Security.Providers.StackExchange/StackExchangeAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.StackExchange/packages.config b/src/Owin.Security.Providers.StackExchange/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.StackExchange/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.csproj b/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.csproj new file mode 100644 index 0000000..6cef086 --- /dev/null +++ b/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {312C4ED7-8CA1-4723-9203-ABC694DFDC7C} + Library + Properties + Owin.Security.Providers.Steam + Owin.Security.Providers.Steam + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {4fd7b873-1994-4990-aa40-c37060121494} + Owin.Security.Providers.OpenIDBase + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Steam.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.nuspec b/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.nuspec new file mode 100644 index 0000000..759824d --- /dev/null +++ b/src/Owin.Security.Providers.Steam/Owin.Security.Providers.Steam.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Steam + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Steam OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Steam + + + + + + + + + diff --git a/src/Owin.Security.Providers.Steam/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Steam/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4dffe32 --- /dev/null +++ b/src/Owin.Security.Providers.Steam/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Steam")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Steam")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("a9779ce7-6d2a-4f4a-873f-a67e6f517fc5")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Owin.Security.Providers.Steam/Resources.Designer.cs b/src/Owin.Security.Providers.Steam/Resources.Designer.cs new file mode 100644 index 0000000..9e72449 --- /dev/null +++ b/src/Owin.Security.Providers.Steam/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Steam { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Steam.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Steam/Resources.resx b/src/Owin.Security.Providers.Steam/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Steam/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Steam/SteamAuthenticationExtensions.cs b/src/Owin.Security.Providers.Steam/SteamAuthenticationExtensions.cs similarity index 92% rename from Owin.Security.Providers/Steam/SteamAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Steam/SteamAuthenticationExtensions.cs index d3b3ba9..bde7723 100644 --- a/Owin.Security.Providers/Steam/SteamAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Steam/SteamAuthenticationExtensions.cs @@ -17,11 +17,11 @@ namespace Owin.Security.Providers.Steam { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } return app.Use(typeof(SteamAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Steam/SteamAuthenticationHandler.cs b/src/Owin.Security.Providers.Steam/SteamAuthenticationHandler.cs new file mode 100644 index 0000000..3a89d69 --- /dev/null +++ b/src/Owin.Security.Providers.Steam/SteamAuthenticationHandler.cs @@ -0,0 +1,33 @@ +using Microsoft.Owin.Logging; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Text.RegularExpressions; +using Owin.Security.Providers.OpenIDBase; + +namespace Owin.Security.Providers.Steam +{ + internal sealed class SteamAuthenticationHandler : OpenIDAuthenticationHandlerBase + { + private readonly Regex _accountIDRegex = new Regex(@"^http://steamcommunity\.com/openid/id/(7[0-9]{15,25})$", RegexOptions.Compiled); + + private const string UserInfoUri = "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={0}&steamids={1}"; + + public SteamAuthenticationHandler(HttpClient httpClient, ILogger logger) : base(httpClient, logger) + { } + + protected override void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary attributeExchangeProperties) + { + var accountIDMatch = _accountIDRegex.Match(claimedID); + if (!accountIDMatch.Success) return; + var accountID = accountIDMatch.Groups[1].Value; + + var getUserInfoTask = HTTPClient.GetStringAsync(string.Format(UserInfoUri, Options.ApplicationKey, accountID)); + getUserInfoTask.Wait(); + var userInfoRaw = getUserInfoTask.Result; + dynamic userInfo = JsonConvert.DeserializeObject(userInfoRaw); + identity.AddClaim(new Claim(ClaimTypes.Name, (string)userInfo.response.players[0].personaname, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); + } + } +} diff --git a/Owin.Security.Providers/Steam/SteamAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Steam/SteamAuthenticationMiddleware.cs similarity index 89% rename from Owin.Security.Providers/Steam/SteamAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Steam/SteamAuthenticationMiddleware.cs index 74cd619..1bb7c11 100644 --- a/Owin.Security.Providers/Steam/SteamAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Steam/SteamAuthenticationMiddleware.cs @@ -1,6 +1,6 @@ using Microsoft.Owin; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.OpenID; +using Owin.Security.Providers.OpenIDBase; namespace Owin.Security.Providers.Steam { @@ -21,7 +21,7 @@ namespace Owin.Security.Providers.Steam protected override AuthenticationHandler CreateSpecificHandler() { - return new SteamAuthenticationHandler(_httpClient, _logger); + return new SteamAuthenticationHandler(HTTPClient, Logger); } } } diff --git a/Owin.Security.Providers/Steam/SteamAuthenticationOptions.cs b/src/Owin.Security.Providers.Steam/SteamAuthenticationOptions.cs similarity index 92% rename from Owin.Security.Providers/Steam/SteamAuthenticationOptions.cs rename to src/Owin.Security.Providers.Steam/SteamAuthenticationOptions.cs index 33849cc..5de9731 100644 --- a/Owin.Security.Providers/Steam/SteamAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Steam/SteamAuthenticationOptions.cs @@ -1,5 +1,5 @@ using Microsoft.Owin; -using Owin.Security.Providers.OpenID; +using Owin.Security.Providers.OpenIDBase; namespace Owin.Security.Providers.Steam { diff --git a/src/Owin.Security.Providers.Steam/packages.config b/src/Owin.Security.Providers.Steam/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Steam/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/TripIt/Constants.cs b/src/Owin.Security.Providers.TripIt/Constants.cs similarity index 100% rename from Owin.Security.Providers/TripIt/Constants.cs rename to src/Owin.Security.Providers.TripIt/Constants.cs diff --git a/Owin.Security.Providers/TripIt/Messages/AccessToken.cs b/src/Owin.Security.Providers.TripIt/Messages/AccessToken.cs similarity index 100% rename from Owin.Security.Providers/TripIt/Messages/AccessToken.cs rename to src/Owin.Security.Providers.TripIt/Messages/AccessToken.cs diff --git a/Owin.Security.Providers/TripIt/Messages/RequestToken.cs b/src/Owin.Security.Providers.TripIt/Messages/RequestToken.cs similarity index 100% rename from Owin.Security.Providers/TripIt/Messages/RequestToken.cs rename to src/Owin.Security.Providers.TripIt/Messages/RequestToken.cs diff --git a/Owin.Security.Providers/TripIt/Messages/RequestTokenSerializer.cs b/src/Owin.Security.Providers.TripIt/Messages/RequestTokenSerializer.cs similarity index 88% rename from Owin.Security.Providers/TripIt/Messages/RequestTokenSerializer.cs rename to src/Owin.Security.Providers.TripIt/Messages/RequestTokenSerializer.cs index 01a7be0..283a527 100644 --- a/Owin.Security.Providers/TripIt/Messages/RequestTokenSerializer.cs +++ b/src/Owin.Security.Providers.TripIt/Messages/RequestTokenSerializer.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler.Serializer; namespace Owin.Security.Providers.TripIt.Messages @@ -60,11 +59,11 @@ namespace Owin.Security.Providers.TripIt.Messages { if (writer == null) { - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); } if (token == null) { - throw new ArgumentNullException("token"); + throw new ArgumentNullException(nameof(token)); } writer.Write(FormatVersion); @@ -83,7 +82,7 @@ namespace Owin.Security.Providers.TripIt.Messages { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (reader.ReadInt32() != FormatVersion) @@ -91,10 +90,10 @@ namespace Owin.Security.Providers.TripIt.Messages return null; } - string token = reader.ReadString(); - string tokenSecret = reader.ReadString(); - bool callbackConfirmed = reader.ReadBoolean(); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var token = reader.ReadString(); + var tokenSecret = reader.ReadString(); + var callbackConfirmed = reader.ReadBoolean(); + var properties = PropertiesSerializer.Read(reader); if (properties == null) { return null; diff --git a/Owin.Security.Providers/TripIt/Messages/Serializers.cs b/src/Owin.Security.Providers.TripIt/Messages/Serializers.cs similarity index 100% rename from Owin.Security.Providers/TripIt/Messages/Serializers.cs rename to src/Owin.Security.Providers.TripIt/Messages/Serializers.cs diff --git a/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.csproj b/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.csproj new file mode 100644 index 0000000..5a87e6c --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {B35E2616-DC00-48B4-BD58-7E23046257F1} + Library + Properties + Owin.Security.Providers.TripIt + Owin.Security.Providers.TripIt + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.TripIt.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.nuspec b/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.nuspec new file mode 100644 index 0000000..3a7514b --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/Owin.Security.Providers.TripIt.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.TripIt + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a TripIt OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth TripIt + + + + + + + + + diff --git a/src/Owin.Security.Providers.TripIt/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.TripIt/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..864320f --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.TripIt")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.TripIt")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("1dde8b52-638a-4202-a942-0b4b82db4993")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/TripIt/Provider/ITripItAuthenticationProvider.cs b/src/Owin.Security.Providers.TripIt/Provider/ITripItAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/TripIt/Provider/ITripItAuthenticationProvider.cs rename to src/Owin.Security.Providers.TripIt/Provider/ITripItAuthenticationProvider.cs index 816abf1..6208426 100644 --- a/Owin.Security.Providers/TripIt/Provider/ITripItAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.TripIt/Provider/ITripItAuthenticationProvider.cs @@ -10,7 +10,7 @@ namespace Owin.Security.Providers.TripIt.Provider public interface ITripItAuthenticationProvider { /// - /// Invoked whenever TripIt succesfully authenticates a user + /// Invoked whenever TripIt successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/TripIt/Provider/TripItAuthenticatedContext.cs b/src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticatedContext.cs similarity index 63% rename from Owin.Security.Providers/TripIt/Provider/TripItAuthenticatedContext.cs rename to src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticatedContext.cs index e26ffc3..e85be4c 100644 --- a/Owin.Security.Providers/TripIt/Provider/TripItAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticatedContext.cs @@ -38,17 +38,9 @@ namespace Owin.Security.Providers.TripIt.Provider AccessTokenSecret = accessTokenSecret; } - private string GetUserId(JObject profile) + private static string GetUserId(JObject profile) { - var attributes = profile["@attributes"]; - if (attributes != null) - { - var reference = attributes["ref"]; - if (reference != null) - return reference.ToString(); - } - - return null; + return profile["@attributes"]?["ref"]?.ToString(); } /// @@ -105,46 +97,42 @@ namespace Owin.Security.Providers.TripIt.Provider return user.TryGetValue(propertyName, out value) ? value.ToString() : null; } - private string GetEmail(JObject user) + private static string GetEmail(JObject user) { - if (user != null) + if (user == null) return null; + try { - try - { - JToken email = null; - JToken profileEmailAddresses = user["ProfileEmailAddresses"]; + JToken email = null; + var profileEmailAddresses = user["ProfileEmailAddresses"]; - if (profileEmailAddresses != null) + if (profileEmailAddresses != null) + { + var profileEmailAddress = profileEmailAddresses["ProfileEmailAddress"]; + + if (profileEmailAddress != null) { - JToken profileEmailAddress = profileEmailAddresses["ProfileEmailAddress"]; - - if (profileEmailAddress != null) + if (profileEmailAddress.Type == JTokenType.Array) { - if (profileEmailAddress.Type == JTokenType.Array) - { - // Try and get the primary email address - email = profileEmailAddress.FirstOrDefault(e => e["is_primary"].ToString() == "true"); + // Try and get the primary email address + email = profileEmailAddress.FirstOrDefault(e => e["is_primary"].ToString() == "true") ?? + profileEmailAddress.FirstOrDefault(); - // If no primary email was located, select the first email we can find - if (email == null) - email = profileEmailAddress.FirstOrDefault(); - } - else if (profileEmailAddress.Type == JTokenType.Object) - { - // If the emails element is a single object and not an array, then take that object as the email object - email = profileEmailAddress; - } + // If no primary email was located, select the first email we can find + } + else if (profileEmailAddress.Type == JTokenType.Object) + { + // If the emails element is a single object and not an array, then take that object as the email object + email = profileEmailAddress; } - - // If we managed to find an email (primary or otherwise), then return the email - if (email != null && email["address"] != null) - return email["address"].ToString(); } + + // If we managed to find an email (primary or otherwise), then return the email + if (email?["address"] != null) return email["address"].ToString(); } - catch - { - // Suppress any exception here - } + } + catch + { + // Suppress any exception here } return null; diff --git a/Owin.Security.Providers/TripIt/Provider/TripItAuthenticationProvider.cs b/src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/TripIt/Provider/TripItAuthenticationProvider.cs rename to src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticationProvider.cs index c8082fc..baa933a 100644 --- a/Owin.Security.Providers/TripIt/Provider/TripItAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.TripIt/Provider/TripItAuthenticationProvider.cs @@ -30,7 +30,7 @@ namespace Owin.Security.Providers.TripIt.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever TripIt succesfully authenticates a user + /// Invoked whenever TripIt successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/TripIt/Provider/TripItReturnEndpointContext.cs b/src/Owin.Security.Providers.TripIt/Provider/TripItReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/TripIt/Provider/TripItReturnEndpointContext.cs rename to src/Owin.Security.Providers.TripIt/Provider/TripItReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.TripIt/Resources.Designer.cs b/src/Owin.Security.Providers.TripIt/Resources.Designer.cs new file mode 100644 index 0000000..22429f3 --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.TripIt { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.TripIt.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.TripIt/Resources.resx b/src/Owin.Security.Providers.TripIt/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/TripIt/TripItAuthenticationExtensions.cs b/src/Owin.Security.Providers.TripIt/TripItAuthenticationExtensions.cs similarity index 93% rename from Owin.Security.Providers/TripIt/TripItAuthenticationExtensions.cs rename to src/Owin.Security.Providers.TripIt/TripItAuthenticationExtensions.cs index bdf317d..b354a7d 100644 --- a/Owin.Security.Providers/TripIt/TripItAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.TripIt/TripItAuthenticationExtensions.cs @@ -19,11 +19,11 @@ namespace Owin.Security.Providers.TripIt { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } app.Use(typeof(TripItAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/TripIt/TripItAuthenticationHandler.cs b/src/Owin.Security.Providers.TripIt/TripItAuthenticationHandler.cs similarity index 71% rename from Owin.Security.Providers/TripIt/TripItAuthenticationHandler.cs rename to src/Owin.Security.Providers.TripIt/TripItAuthenticationHandler.cs index 08e3ebe..baececd 100644 --- a/Owin.Security.Providers/TripIt/TripItAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.TripIt/TripItAuthenticationHandler.cs @@ -53,10 +53,10 @@ namespace Owin.Security.Providers.TripIt AuthenticationProperties properties = null; try { - IReadableStringCollection query = Request.Query; - string protectedRequestToken = Request.Cookies[StateCookie]; + var query = Request.Query; + var protectedRequestToken = Request.Cookies[StateCookie]; - RequestToken requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); + var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); if (requestToken == null) { @@ -66,7 +66,7 @@ namespace Owin.Security.Providers.TripIt properties = requestToken.Properties; - string returnedToken = query.Get("oauth_token"); + var returnedToken = query.Get("oauth_token"); if (string.IsNullOrWhiteSpace(returnedToken)) { _logger.WriteWarning("Missing oauth_token"); @@ -79,38 +79,41 @@ namespace Owin.Security.Providers.TripIt return new AuthenticationTicket(null, properties); } - AccessToken accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken); + var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken); - JObject profileObject = await ObtainUserProfile(Options.ConsumerKey, Options.ConsumerSecret, accessToken); + var profileObject = await ObtainUserProfile(Options.ConsumerKey, Options.ConsumerSecret, accessToken); - var context = new TripItAuthenticatedContext(Context, profileObject, accessToken.Token, accessToken.TokenSecret); + var context = new TripItAuthenticatedContext(Context, profileObject, accessToken.Token, accessToken.TokenSecret) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!String.IsNullOrEmpty(context.ScreenName)) + if (!string.IsNullOrEmpty(context.ScreenName)) { context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.ScreenName, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.DisplayName)) + if (!string.IsNullOrEmpty(context.DisplayName)) { context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.DisplayName, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.Email)) + if (!string.IsNullOrEmpty(context.Email)) { context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.UserId)) + if (!string.IsNullOrEmpty(context.UserId)) { context.Identity.AddClaim(new Claim("urn:tripit:userid", context.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.ScreenName)) + if (!string.IsNullOrEmpty(context.ScreenName)) { + // ReSharper disable once StringLiteralTypo context.Identity.AddClaim(new Claim("urn:tripit:sreenname", context.ScreenName, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } @@ -137,24 +140,24 @@ namespace Owin.Security.Providers.TripIt return; } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge != null) { - string requestPrefix = Request.Scheme + "://" + Request.Host; - string callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; - AuthenticationProperties extra = challenge.Properties; + var extra = challenge.Properties; if (string.IsNullOrEmpty(extra.RedirectUri)) { extra.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; } - RequestToken requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, extra); + var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, extra); if (requestToken.CallbackConfirmed) { - string authenticationEndpoint = AuthenticationEndpoint + "?oauth_token=" + requestToken.Token + var authenticationEndpoint = AuthenticationEndpoint + "?oauth_token=" + requestToken.Token + "&oauth_callback=" + Uri.EscapeDataString(callBackUrl); var cookieOptions = new CookieOptions @@ -176,7 +179,7 @@ namespace Owin.Security.Providers.TripIt public async Task InvokeReturnPathAsync() { - AuthenticationTicket model = await AuthenticateAsync(); + var model = await AuthenticateAsync(); if (model == null) { _logger.WriteWarning("Invalid return state, unable to redirect."); @@ -195,7 +198,7 @@ namespace Owin.Security.Providers.TripIt if (context.SignInAsAuthenticationType != null && context.Identity != null) { - ClaimsIdentity signInIdentity = context.Identity; + var signInIdentity = context.Identity; if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) { signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); @@ -203,16 +206,14 @@ namespace Owin.Security.Providers.TripIt Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } @@ -221,7 +222,7 @@ namespace Owin.Security.Providers.TripIt { _logger.WriteVerbose("ObtainRequestToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { @@ -238,16 +239,16 @@ namespace Owin.Security.Providers.TripIt parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, null, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); //-- @@ -263,11 +264,11 @@ namespace Owin.Security.Providers.TripIt var request = new HttpRequestMessage(HttpMethod.Post, RequestTokenEndpoint); request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); response.EnsureSuccessStatusCode(); - string responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseParameters = WebHelpers.ParseForm(responseText); return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties }; } @@ -275,7 +276,7 @@ namespace Owin.Security.Providers.TripIt { _logger.WriteVerbose("ObtainAccessToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { @@ -294,16 +295,16 @@ namespace Owin.Security.Providers.TripIt parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -318,7 +319,7 @@ namespace Owin.Security.Providers.TripIt var request = new HttpRequestMessage(HttpMethod.Post, AccessTokenEndpoint); request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); if (!response.IsSuccessStatusCode) { @@ -326,9 +327,9 @@ namespace Owin.Security.Providers.TripIt response.EnsureSuccessStatusCode(); // throw } - string responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseParameters = WebHelpers.ParseForm(responseText); return new AccessToken { @@ -341,8 +342,8 @@ namespace Owin.Security.Providers.TripIt { _logger.WriteVerbose("ObtainUserProfile"); - string nonce = Guid.NewGuid().ToString("N"); - string requestUrl = "https://api.tripit.com/v1/get/profile"; + var nonce = Guid.NewGuid().ToString("N"); + var requestUrl = "https://api.tripit.com/v1/get/profile"; var queryParts = new Dictionary { @@ -364,16 +365,16 @@ namespace Owin.Security.Providers.TripIt parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Get.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(requestUrl)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Get.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(requestUrl)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -390,7 +391,7 @@ namespace Owin.Security.Providers.TripIt request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); if (!response.IsSuccessStatusCode) { @@ -398,20 +399,17 @@ namespace Owin.Security.Providers.TripIt response.EnsureSuccessStatusCode(); // throw } - string responseText = await response.Content.ReadAsStringAsync(); - JObject responseObject = JObject.Parse(responseText); + var responseText = await response.Content.ReadAsStringAsync(); + var responseObject = JObject.Parse(responseText); - JObject profileObject = (JObject)responseObject.SelectToken("Profile"); - if (profileObject != null) - return profileObject; - - return null; + var profileObject = (JObject)responseObject.SelectToken("Profile"); + return profileObject; } private static string GenerateTimeStamp() { - TimeSpan secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; - return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + var secondsSinceUnixEpochStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpochStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData) @@ -423,7 +421,7 @@ namespace Owin.Security.Providers.TripIt "{0}&{1}", Uri.EscapeDataString(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret))); - byte[] hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); + var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); return Convert.ToBase64String(hash); } } diff --git a/Owin.Security.Providers/TripIt/TripItAuthenticationMiddleware.cs b/src/Owin.Security.Providers.TripIt/TripItAuthenticationMiddleware.cs similarity index 88% rename from Owin.Security.Providers/TripIt/TripItAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.TripIt/TripItAuthenticationMiddleware.cs index 64c8c0e..f8bec7f 100644 --- a/Owin.Security.Providers/TripIt/TripItAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.TripIt/TripItAuthenticationMiddleware.cs @@ -11,7 +11,6 @@ using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataHandler.Encoder; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.TripIt.Messages; using Owin.Security.Providers.TripIt.Provider; @@ -56,7 +55,7 @@ namespace Owin.Security.Providers.TripIt } if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(TripItAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new SecureDataFormat( @@ -64,14 +63,17 @@ namespace Owin.Security.Providers.TripIt dataProtector, TextEncodings.Base64Url); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) { - Options.SignInAsAuthenticationType = AppBuilderSecurityExtensions.GetDefaultSignInAsAuthenticationType(app); + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } - _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); - _httpClient.Timeout = Options.BackchannelTimeout; - _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + // 10 MB _httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin TripIt middleware"); _httpClient.DefaultRequestHeaders.ExpectContinue = false; @@ -89,7 +91,7 @@ namespace Owin.Security.Providers.TripIt [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")] private static HttpMessageHandler ResolveHttpMessageHandler(TripItAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // Set the cert validate callback var webRequestHandler = handler as WebRequestHandler; diff --git a/Owin.Security.Providers/TripIt/TripItAuthenticationOptions.cs b/src/Owin.Security.Providers.TripIt/TripItAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/TripIt/TripItAuthenticationOptions.cs rename to src/Owin.Security.Providers.TripIt/TripItAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.TripIt/packages.config b/src/Owin.Security.Providers.TripIt/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.TripIt/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Twitch/Constants.cs b/src/Owin.Security.Providers.Twitch/Constants.cs similarity index 100% rename from Owin.Security.Providers/Twitch/Constants.cs rename to src/Owin.Security.Providers.Twitch/Constants.cs diff --git a/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.csproj b/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.csproj new file mode 100644 index 0000000..f0520ea --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {C3CF8734-6AAC-4F59-9A3E-1CBA8582CD48} + Library + Properties + Owin.Security.Providers.Twitch + Owin.Security.Providers.Twitch + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Twitch.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.nuspec b/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.nuspec new file mode 100644 index 0000000..e456ec4 --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/Owin.Security.Providers.Twitch.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Twitch + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Twitch OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Twitch + + + + + + + + + diff --git a/src/Owin.Security.Providers.Twitch/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Twitch/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3dadd69 --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Twitch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Twitch")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("57b8305e-950b-4b12-b653-30214d30e870")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Twitch/Provider/ITwitchAuthenticationProvider.cs b/src/Owin.Security.Providers.Twitch/Provider/ITwitchAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Twitch/Provider/ITwitchAuthenticationProvider.cs rename to src/Owin.Security.Providers.Twitch/Provider/ITwitchAuthenticationProvider.cs index e1d1d37..4c4f448 100644 --- a/Owin.Security.Providers/Twitch/Provider/ITwitchAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Twitch/Provider/ITwitchAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Twitch public interface ITwitchAuthenticationProvider { /// - /// Invoked whenever Twitch succesfully authenticates a user + /// Invoked whenever Twitch successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Twitch/Provider/TwitchAuthenticatedContext.cs b/src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/Twitch/Provider/TwitchAuthenticatedContext.cs rename to src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticatedContext.cs index 8246343..9a07c8c 100644 --- a/Owin.Security.Providers/Twitch/Provider/TwitchAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; diff --git a/Owin.Security.Providers/Twitch/Provider/TwitchAuthenticationProvider.cs b/src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Twitch/Provider/TwitchAuthenticationProvider.cs rename to src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticationProvider.cs index 2c47fdb..9ecd4d3 100644 --- a/Owin.Security.Providers/Twitch/Provider/TwitchAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Twitch/Provider/TwitchAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Twitch public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Twitch succesfully authenticates a user + /// Invoked whenever Twitch successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Twitch/Provider/TwitchReturnEndpointContext.cs b/src/Owin.Security.Providers.Twitch/Provider/TwitchReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Twitch/Provider/TwitchReturnEndpointContext.cs rename to src/Owin.Security.Providers.Twitch/Provider/TwitchReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Twitch/Resources.Designer.cs b/src/Owin.Security.Providers.Twitch/Resources.Designer.cs new file mode 100644 index 0000000..f476519 --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Twitch { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Twitch.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Twitch/Resources.resx b/src/Owin.Security.Providers.Twitch/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Twitch/TwitchAuthenticationExtensions.cs b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationExtensions.cs similarity index 91% rename from Owin.Security.Providers/Twitch/TwitchAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Twitch/TwitchAuthenticationExtensions.cs index aa70adc..832d651 100644 --- a/Owin.Security.Providers/Twitch/TwitchAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationExtensions.cs @@ -14,9 +14,9 @@ namespace Owin.Security.Providers.Twitch TwitchAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(TwitchAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Twitch/TwitchAuthenticationHandler.cs b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationHandler.cs new file mode 100644 index 0000000..36a42d8 --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationHandler.cs @@ -0,0 +1,240 @@ +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.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.Twitch +{ + public class TwitchAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public TwitchAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret), + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("code", code) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint) + { + Content = new FormUrlEncodedContent(body) + }; + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Get the Twitch user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken)); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new TwitchAuthenticatedContext(Context, user, accessToken) + { + 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:Twitch:name", context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Link)) + { + context.Identity.AddClaim(new Claim("urn:Twitch: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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(" ", Options.Scope.Distinct()); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&scope=" + Uri.EscapeDataString(scope) + + "&response_type=" + "code" + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new TwitchReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Twitch/TwitchAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/Twitch/TwitchAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Twitch/TwitchAuthenticationMiddleware.cs index 5b51348..aea17d7 100644 --- a/Owin.Security.Providers/Twitch/TwitchAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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.Twitch { public class TwitchAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public TwitchAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, TwitchAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new TwitchAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (TwitchAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Twitch middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Twitch middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,24 +61,22 @@ namespace Owin.Security.Providers.Twitch /// protected override AuthenticationHandler CreateHandler() { - return new TwitchAuthenticationHandler(httpClient, logger); + return new TwitchAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(TwitchAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(TwitchAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Twitch/TwitchAuthenticationOptions.cs b/src/Owin.Security.Providers.Twitch/TwitchAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Twitch/TwitchAuthenticationOptions.cs rename to src/Owin.Security.Providers.Twitch/TwitchAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Twitch/packages.config b/src/Owin.Security.Providers.Twitch/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Twitch/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Untappd/ApiResponse.cs b/src/Owin.Security.Providers.Untappd/ApiResponse.cs new file mode 100644 index 0000000..3f4fe89 --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/ApiResponse.cs @@ -0,0 +1,20 @@ +namespace Owin.Security.Providers.Untappd +{ + + internal class ResponseRoot + { + public Meta Meta { get; set; } + public Response Response { get; set; } + } + + public class Meta + { + public int HTTPCode { get; set; } + } + + public class Response + { + public string AccessToken { get; set; } + } + +} diff --git a/Owin.Security.Providers/Untappd/Constants.cs b/src/Owin.Security.Providers.Untappd/Constants.cs similarity index 100% rename from Owin.Security.Providers/Untappd/Constants.cs rename to src/Owin.Security.Providers.Untappd/Constants.cs diff --git a/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.csproj b/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.csproj new file mode 100644 index 0000000..bff58ff --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {3E89ECA3-F4E7-4181-B26B-8250D5151044} + Library + Properties + Owin.Security.Providers.Untappd + Owin.Security.Providers.Untappd + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Untappd.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.nuspec b/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.nuspec new file mode 100644 index 0000000..284a755 --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/Owin.Security.Providers.Untappd.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Untappd + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Untappd OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Untappd + + + + + + + + + diff --git a/src/Owin.Security.Providers.Untappd/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Untappd/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c80d465 --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Untappd")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Untappd")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("3e2e42d5-76ad-4d03-b934-5cdf62709d4c")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs b/src/Owin.Security.Providers.Untappd/Provider/IUntappdAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs rename to src/Owin.Security.Providers.Untappd/Provider/IUntappdAuthenticationProvider.cs index f3aab47..3e27442 100644 --- a/Owin.Security.Providers/Untappd/Provider/IUntappdAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Untappd/Provider/IUntappdAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Untappd public interface IUntappdAuthenticationProvider { /// - /// Invoked whenever Untappd succesfully authenticates a user + /// Invoked whenever Untappd successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs b/src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticatedContext.cs similarity index 95% rename from Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs rename to src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticatedContext.cs index a40a8da..f92c46c 100644 --- a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticatedContext.cs @@ -1,7 +1,5 @@ // 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; @@ -28,7 +26,7 @@ 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"] + " " + user["response"]["user"]["last_name"]; 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/Provider/UntappdAuthenticationProvider.cs b/src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs rename to src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticationProvider.cs index a290334..04e4d5c 100644 --- a/Owin.Security.Providers/Untappd/Provider/UntappdAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Untappd/Provider/UntappdAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Untappd public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Untappd succesfully authenticates a user + /// Invoked whenever Untappd successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs b/src/Owin.Security.Providers.Untappd/Provider/UntappdReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Untappd/Provider/UntappdReturnEndpointContext.cs rename to src/Owin.Security.Providers.Untappd/Provider/UntappdReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Untappd/Resources.Designer.cs b/src/Owin.Security.Providers.Untappd/Resources.Designer.cs new file mode 100644 index 0000000..00e85d7 --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Untappd { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Untappd.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Untappd/Resources.resx b/src/Owin.Security.Providers.Untappd/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationExtensions.cs similarity index 91% rename from Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Untappd/UntappdAuthenticationExtensions.cs index f16a748..7c2b446 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationExtensions.cs @@ -14,9 +14,9 @@ namespace Owin.Security.Providers.Untappd UntappdAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(UntappdAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationHandler.cs similarity index 51% rename from Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs rename to src/Owin.Security.Providers.Untappd/UntappdAuthenticationHandler.cs index 4a1eb15..39c5225 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -20,13 +19,13 @@ namespace Owin.Security.Providers.Untappd private const string StateCookie = "_StateCookie"; private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; - private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; public UntappdAuthenticationHandler(HttpClient httpClient, ILogger logger) { - this.httpClient = httpClient; - this.logger = logger; + _httpClient = httpClient; + _logger = logger; } protected override async Task AuthenticateCoreAsync() @@ -36,17 +35,16 @@ namespace Owin.Security.Providers.Untappd try { string code = null; - string state = null; - IReadableStringCollection query = Request.Query; - IList values = query.GetValues("code"); + var query = Request.Query; + var values = query.GetValues("code"); if (values != null && values.Count == 1) { code = string.Copy(values.First()); } // restore State from Cookie - state = Request.Cookies[StateCookie]; + var state = Request.Cookies[StateCookie]; properties = Options.StateDataFormat.Unprotect(state); if (properties == null) { @@ -54,13 +52,13 @@ namespace Owin.Security.Providers.Untappd } // OAuth2 10.12 CSRF - if (!ValidateCorrelationId(properties, logger)) + if (!ValidateCorrelationId(properties, _logger)) { return new AuthenticationTicket(null, properties); } - string requestPrefix = Request.Scheme + "://" + Request.Host; - string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; //// Build up the body for the token request //var body = new List>(); @@ -72,29 +70,32 @@ namespace Owin.Security.Providers.Untappd // 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)); + $@"{Options.Endpoints.TokenEndpoint}/?client_id={Options.ClientId}&client_secret={Options + .ClientSecret}&response_type=code&redirect_url={redirectUri}&code={code}"); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage); + var tokenResponse = await _httpClient.SendAsync(requestMessage); tokenResponse.EnsureSuccessStatusCode(); - string text = await tokenResponse.Content.ReadAsStringAsync(); + var text = await tokenResponse.Content.ReadAsStringAsync(); // Deserializes the token response var response = JsonConvert.DeserializeObject(text); - string accessToken = response.response.access_token; + var accessToken = response.Response.AccessToken; // Get the Untappd user - HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken)); + var 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); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); userResponse.EnsureSuccessStatusCode(); text = await userResponse.Content.ReadAsStringAsync(); - JObject user = JObject.Parse(text); + var user = JObject.Parse(text); - var context = new UntappdAuthenticatedContext(Context, user, accessToken); - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); + var context = new UntappdAuthenticatedContext(Context, user, accessToken) + { + Identity = new ClaimsIdentity( + 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)); @@ -137,7 +138,7 @@ namespace Owin.Security.Providers.Untappd } catch (Exception ex) { - logger.WriteError(ex.Message); + _logger.WriteError(ex.Message); } return new AuthenticationTicket(null, properties); } @@ -149,50 +150,48 @@ namespace Owin.Security.Providers.Untappd return Task.FromResult(null); } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge != null) + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { - 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_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); + properties.RedirectUri = currentUri; } + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&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); + return Task.FromResult(null); } @@ -203,50 +202,47 @@ namespace Owin.Security.Providers.Untappd private async Task InvokeReplyPathAsync() { - if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { - // 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; + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; } - return false; + + var context = new UntappdReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; } } } \ No newline at end of file diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationMiddleware.cs similarity index 70% rename from Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Untappd/UntappdAuthenticationMiddleware.cs index eb9d6c8..f34deb8 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationMiddleware.cs @@ -7,49 +7,48 @@ 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; + 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, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new UntappdAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(UntappdAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Untappd middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Untappd middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -62,12 +61,12 @@ namespace Owin.Security.Providers.Untappd /// protected override AuthenticationHandler CreateHandler() { - return new UntappdAuthenticationHandler(httpClient, logger); + return new UntappdAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(UntappdAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(UntappdAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. if (options.BackchannelCertificateValidator == null) return handler; diff --git a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationOptions.cs similarity index 99% rename from Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs rename to src/Owin.Security.Providers.Untappd/UntappdAuthenticationOptions.cs index 43cc055..9372ca9 100644 --- a/Owin.Security.Providers/Untappd/UntappdAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Untappd/UntappdAuthenticationOptions.cs @@ -130,7 +130,7 @@ namespace Owin.Security.Providers.Untappd CallbackPath = new PathString("/signin-untappd"); AuthenticationMode = AuthenticationMode.Passive; //untappd has no scopes AFAIK - Scope = new List{}; + Scope = new List(); BackchannelTimeout = TimeSpan.FromSeconds(60); Endpoints = new UntappdAuthenticationEndpoints { diff --git a/src/Owin.Security.Providers.Untappd/packages.config b/src/Owin.Security.Providers.Untappd/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Untappd/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/VKontakte/Constants.cs b/src/Owin.Security.Providers.VKontakte/Constants.cs similarity index 100% rename from Owin.Security.Providers/VKontakte/Constants.cs rename to src/Owin.Security.Providers.VKontakte/Constants.cs diff --git a/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.csproj b/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.csproj new file mode 100644 index 0000000..7175a01 --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {32D70E31-3799-482A-AC7A-081FF9206FC3} + Library + Properties + Owin.Security.Providers.VKontakte + Owin.Security.Providers.VKontakte + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.VKontakte.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.nuspec b/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.nuspec new file mode 100644 index 0000000..6921fac --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/Owin.Security.Providers.VKontakte.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.VKontakte + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a VKontakte OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth VKontakte + + + + + + + + + diff --git a/src/Owin.Security.Providers.VKontakte/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.VKontakte/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..10afa16 --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.VKontakte")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.VKontakte")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("8764f7b1-01d0-4c90-ba3e-861b380c5c44")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/VKontakte/Provider/IVKontakteAuthenticationProvider.cs b/src/Owin.Security.Providers.VKontakte/Provider/IVKontakteAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/VKontakte/Provider/IVKontakteAuthenticationProvider.cs rename to src/Owin.Security.Providers.VKontakte/Provider/IVKontakteAuthenticationProvider.cs index 300110b..bca5c25 100644 --- a/Owin.Security.Providers/VKontakte/Provider/IVKontakteAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.VKontakte/Provider/IVKontakteAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.VKontakte.Provider public interface IVKontakteAuthenticationProvider { /// - /// Invoked whenever GitHub succesfully authenticates a user + /// Invoked whenever GitHub successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticatedContext.cs b/src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticatedContext.cs similarity index 95% rename from Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticatedContext.cs rename to src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticatedContext.cs index 8fd9699..9e8913e 100644 --- a/Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticatedContext.cs @@ -26,8 +26,8 @@ namespace Owin.Security.Providers.VKontakte.Provider AccessToken = accessToken; Id = TryGetValue(user, "uid"); - string firstName = TryGetValue(user, "first_name"); - string lastName = TryGetValue(user, "last_name"); + var firstName = TryGetValue(user, "first_name"); + var lastName = TryGetValue(user, "last_name"); UserName = firstName + " " + lastName; } diff --git a/Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticationProvider.cs b/src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticationProvider.cs rename to src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticationProvider.cs index 30e7827..deb2724 100644 --- a/Owin.Security.Providers/VKontakte/Provider/VKontakteAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.VKontakte/Provider/VKontakteAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.VKontakte.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever VK succesfully authenticates a user + /// Invoked whenever VK successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/VKontakte/Provider/VKontakteReturnEndpointContext.cs b/src/Owin.Security.Providers.VKontakte/Provider/VKontakteReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/VKontakte/Provider/VKontakteReturnEndpointContext.cs rename to src/Owin.Security.Providers.VKontakte/Provider/VKontakteReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.VKontakte/Resources.Designer.cs b/src/Owin.Security.Providers.VKontakte/Resources.Designer.cs new file mode 100644 index 0000000..1c524fc --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.VKontakte { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.VKontakte.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.VKontakte/Resources.resx b/src/Owin.Security.Providers.VKontakte/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationEndpoints.cs b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationEndpoints.cs similarity index 100% rename from Owin.Security.Providers/VKontakte/VKontakteAuthenticationEndpoints.cs rename to src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationEndpoints.cs diff --git a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationExtensions.cs b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/VKontakte/VKontakteAuthenticationExtensions.cs rename to src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationExtensions.cs index d99f9b6..bb7f666 100644 --- a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.VKontakte VKontakteAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(VKontakteAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationHandler.cs b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationHandler.cs new file mode 100644 index 0000000..213ef7c --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationHandler.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json.Linq; +using Owin.Security.Providers.VKontakte.Provider; + +namespace Owin.Security.Providers.VKontakte +{ + public class VKontakteAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public VKontakteAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public override async Task InvokeAsync() + { + return await InvokeReplyPathAsync(); + } + + protected override Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) + { + return Task.FromResult(null); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = $"{Request.Scheme}{Uri.SchemeDelimiter}{Request.Host}{Request.PathBase}"; + + var currentUri = $"{baseUri}{Request.Path}{Request.QueryString}"; + + var redirectUri = $"{baseUri}{Options.CallbackPath}"; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // comma separated + var scope = string.Join(",", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + $"{Options.Endpoints.AuthorizationEndpoint}?client_id={Uri.EscapeDataString(Options.ClientId)}&redirect_uri={Uri.EscapeDataString(redirectUri)}&scope={Uri.EscapeDataString(scope)}&state={Uri.EscapeDataString(state)}&display={Uri.EscapeDataString(Options.Display)}"; + + Response.Redirect(authorizationEndpoint); + + return Task.FromResult(null); + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + var authorizationCode = GetParameterValueFromRequest("code"); + var state = GetParameterValueFromRequest("state"); + + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, _logger)) + { + return new AuthenticationTicket(null, properties); + } + + var response = await GetAuthorizationToken(authorizationCode); + var accessToken = (string)response["access_token"]; + + var user = await GetUser(response, accessToken); + + var context = CreateAuthenticatedContext(user, accessToken, 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); + } + + private string GetParameterValueFromRequest(string parameterName) + { + string value = null; + var query = Request.Query; + var values = query.GetValues(parameterName); + if (values != null && values.Count == 1) + { + value = values[0]; + } + return value; + } + + private VKontakteAuthenticatedContext CreateAuthenticatedContext(JObject user, string accessToken, + AuthenticationProperties properties) + { + var context = new VKontakteAuthenticatedContext(Context, user, accessToken) + { + 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)); + } + + context.Properties = properties; + return context; + } + + private async Task GetUser(JObject response, string accessToken) + { + var userId = (int)response["user_id"]; + + // Get the VK user + var userRequestUri = new Uri( + $"{Options.Endpoints.UserInfoEndpoint}?access_token={Uri.EscapeDataString(accessToken)}&user_id{userId}"); + var userResponse = await _httpClient.GetAsync(userRequestUri, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + + var userResponseAsString = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(userResponseAsString)["response"]; + return (JObject)user[0]; + } + + private async Task GetAuthorizationToken(string authorizationCode) + { + var redirectUri = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Options.CallbackPath}"; + + // Build up the body for the token request + var body = new Dictionary + { + {"code", authorizationCode}, + {"redirect_uri", redirectUri}, + {"client_id", Options.ClientId}, + {"client_secret", Options.ClientSecret} + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(Options.Endpoints.TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var tokenResponseAsString = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + var response = JObject.Parse(tokenResponseAsString); + return response; + } + + private async Task InvokeReplyPathAsync() + { + if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false; + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new VKontakteReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationMiddleware.cs b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationMiddleware.cs similarity index 59% rename from Owin.Security.Providers/VKontakte/VKontakteAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationMiddleware.cs index 2ae5ee3..05707ef 100644 --- a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationMiddleware.cs @@ -7,37 +7,36 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.VKontakte.Provider; namespace Owin.Security.Providers.VKontakte { public class VKontakteAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public VKontakteAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, VKontakteAuthenticationOptions 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, + 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")); SetDefaults(app); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin VKontakte middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin VKontakte middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -50,24 +49,22 @@ namespace Owin.Security.Providers.VKontakte /// protected override AuthenticationHandler CreateHandler() { - return new VKontakteAuthenticationHandler(httpClient, logger); + return new VKontakteAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(VKontakteAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(VKontakteAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } @@ -78,13 +75,13 @@ namespace Owin.Security.Providers.VKontakte if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(VKontakteAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); } } diff --git a/Owin.Security.Providers/VKontakte/VKontakteAuthenticationOptions.cs b/src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/VKontakte/VKontakteAuthenticationOptions.cs rename to src/Owin.Security.Providers.VKontakte/VKontakteAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.VKontakte/packages.config b/src/Owin.Security.Providers.VKontakte/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.VKontakte/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Vimeo/Constants.cs b/src/Owin.Security.Providers.Vimeo/Constants.cs similarity index 100% rename from Owin.Security.Providers/Vimeo/Constants.cs rename to src/Owin.Security.Providers.Vimeo/Constants.cs diff --git a/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.csproj b/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.csproj new file mode 100644 index 0000000..0c1d329 --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {98ECC703-D651-4EAD-A55D-AA3E903AE4D7} + Library + Properties + Owin.Security.Providers.Vimeo + Owin.Security.Providers.Vimeo + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Vimeo.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.nuspec b/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.nuspec new file mode 100644 index 0000000..c6ded45 --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/Owin.Security.Providers.Vimeo.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Vimeo + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Vimeo OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Vimeo + + + + + + + + + diff --git a/src/Owin.Security.Providers.Vimeo/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Vimeo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..bccdf39 --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Vimeo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Vimeo")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("3a6798f7-4a11-46cc-bedb-ac8d1f969d0e")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs b/src/Owin.Security.Providers.Vimeo/Provider/IVimeoAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs rename to src/Owin.Security.Providers.Vimeo/Provider/IVimeoAuthenticationProvider.cs index a361ac8..c8d0eda 100644 --- a/Owin.Security.Providers/Vimeo/Provider/IVimeoAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Vimeo/Provider/IVimeoAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Vimeo public interface IVimeoAuthenticationProvider { /// - /// Invoked whenever Vimeo succesfully authenticates a user + /// Invoked whenever Vimeo successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs b/src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticatedContext.cs similarity index 95% rename from Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs rename to src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticatedContext.cs index 45d7550..d28ba2d 100644 --- a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticatedContext.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Provider; @@ -30,7 +31,7 @@ namespace Owin.Security.Providers.Vimeo var uri = TryGetValue(user, "uri"); if (!string.IsNullOrEmpty(uri)) { - Id = uri.Substring(uri.LastIndexOf("/") + 1); // parse format /users/123456 + Id = uri.Substring(uri.LastIndexOf("/", StringComparison.Ordinal) + 1); // parse format /users/123456 } } diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs b/src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs rename to src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticationProvider.cs index a373ae3..7033040 100644 --- a/Owin.Security.Providers/Vimeo/Provider/VimeoAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Vimeo/Provider/VimeoAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Vimeo public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Vimeo succesfully authenticates a user + /// Invoked whenever Vimeo successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Vimeo/Provider/VimeoReturnEndpointContext.cs b/src/Owin.Security.Providers.Vimeo/Provider/VimeoReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Vimeo/Provider/VimeoReturnEndpointContext.cs rename to src/Owin.Security.Providers.Vimeo/Provider/VimeoReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Vimeo/Resources.Designer.cs b/src/Owin.Security.Providers.Vimeo/Resources.Designer.cs new file mode 100644 index 0000000..e54684c --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Vimeo { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Vimeo.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Vimeo/Resources.resx b/src/Owin.Security.Providers.Vimeo/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationExtensions.cs similarity index 76% rename from Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Vimeo/VimeoAuthenticationExtensions.cs index 3bb7a9e..85470b9 100644 --- a/Owin.Security.Providers/Vimeo/VimeoAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationExtensions.cs @@ -7,9 +7,9 @@ namespace Owin.Security.Providers.Vimeo public static IAppBuilder UseVimeoAuthentication(this IAppBuilder app, VimeoAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(VimeoAuthenticationMiddleware), app, options); @@ -19,9 +19,9 @@ namespace Owin.Security.Providers.Vimeo public static IAppBuilder UseVimeoAuthentication(this IAppBuilder app, string clientId, string clientSecret) { if (string.IsNullOrEmpty(clientId)) - throw new ArgumentNullException("clientId"); + throw new ArgumentNullException(nameof(clientId)); if (string.IsNullOrEmpty(clientSecret)) - throw new ArgumentNullException("clientSecret"); + throw new ArgumentNullException(nameof(clientSecret)); return app.UseVimeoAuthentication(new VimeoAuthenticationOptions { diff --git a/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationHandler.cs b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationHandler.cs new file mode 100644 index 0000000..ddbef37 --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationHandler.cs @@ -0,0 +1,218 @@ +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.Vimeo +{ + public class VimeoAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://api.vimeo.com/oauth/access_token"; + private const string AuthorizationEndpoint = "https://api.vimeo.com/oauth/authorize"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public VimeoAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + + // Vimeo includes the user information in the response + var context = new VimeoAuthenticatedContext(Context, response.user, accessToken) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + AuthorizationEndpoint + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + if (!string.IsNullOrWhiteSpace(Options.Scope)) + { + authorizationEndpoint += "&scope=" + Uri.EscapeDataString(Options.Scope); + } + + 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) return false; + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new VimeoReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationMiddleware.cs similarity index 60% rename from Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Vimeo/VimeoAuthenticationMiddleware.cs index 2a6228e..d219783 100644 --- a/Owin.Security.Providers/Vimeo/VimeoAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationMiddleware.cs @@ -4,7 +4,6 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.Properties; using System; using System.Globalization; using System.Net.Http; @@ -13,36 +12,36 @@ namespace Owin.Security.Providers.Vimeo { public class VimeoAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public VimeoAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, VimeoAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new VimeoAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(VimeoAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 @@ -59,24 +58,22 @@ namespace Owin.Security.Providers.Vimeo /// protected override AuthenticationHandler CreateHandler() { - return new VimeoAuthenticationHandler(httpClient, logger); + return new VimeoAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(VimeoAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(VimeoAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/Vimeo/VimeoAuthenticationOptions.cs b/src/Owin.Security.Providers.Vimeo/VimeoAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Vimeo/VimeoAuthenticationOptions.cs rename to src/Owin.Security.Providers.Vimeo/VimeoAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Vimeo/packages.config b/src/Owin.Security.Providers.Vimeo/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Vimeo/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.VisualStudio/Constants.cs b/src/Owin.Security.Providers.VisualStudio/Constants.cs new file mode 100644 index 0000000..7dc6233 --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Constants.cs @@ -0,0 +1,5 @@ +namespace Owin.Security.Providers.VisualStudio { + internal static class Constants { + public const string DefaultAuthenticationType = "Visual Studio Online"; + } +} diff --git a/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.csproj b/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.csproj new file mode 100644 index 0000000..29fb4a9 --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {3B19FA31-DDFF-427F-9D73-F860DE74BBC2} + Library + Properties + Owin.Security.Providers.VisualStudio + Owin.Security.Providers.VisualStudio + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.VisualStudio.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.nuspec b/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.nuspec new file mode 100644 index 0000000..2bd985e --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Owin.Security.Providers.VisualStudio.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.VisualStudio + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a VisualStudio OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth VisualStudio + + + + + + + + + diff --git a/src/Owin.Security.Providers.VisualStudio/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.VisualStudio/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3187e2d --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.VisualStudio")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.VisualStudio")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("13295793-6bd8-4241-93fa-e071cce91b17")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs b/src/Owin.Security.Providers.VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs rename to src/Owin.Security.Providers.VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs index 2ea50b3..1c66aea 100644 --- a/Owin.Security.Providers/VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.VisualStudio/Provider/IVisualStudioAuthenticationProvider.cs @@ -7,7 +7,7 @@ namespace Owin.Security.Providers.VisualStudio { /// public interface IVisualStudioAuthenticationProvider { /// - /// Invoked whenever Visual Studio Online succesfully authenticates a user + /// Invoked whenever Visual Studio Online successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticatedContext.cs b/src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticatedContext.cs similarity index 79% rename from Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticatedContext.cs rename to src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticatedContext.cs index 41ba043..22f1a41 100644 --- a/Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticatedContext.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Security.Claims; using Microsoft.Owin; using Microsoft.Owin.Security; @@ -12,14 +11,15 @@ namespace Owin.Security.Providers.VisualStudio { /// Contains information about the login session as well as the user . /// public class VisualStudioAuthenticatedContext : BaseContext{ - - /// - /// Initializes a - /// - /// The OWIN environment - /// The JSON-serialized user - /// Visual Studio Online Access token - public VisualStudioAuthenticatedContext(IOwinContext context, JObject user, string accessToken, int expiresIn, string refreshToken) + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// Visual Studio Online Access token + /// + /// + public VisualStudioAuthenticatedContext(IOwinContext context, JObject user, string accessToken, int expiresIn, string refreshToken) : base(context) { AccessToken = accessToken; @@ -47,7 +47,7 @@ namespace Owin.Security.Providers.VisualStudio { public string AccessToken { get; private set; } /// - /// Gets the Google OAuth refresh token. This is only available when the RequestOfflineAccess property of is set to true + /// Gets the Google OAuth refresh token. This is only available when the RequestOfflineAccess property of GooglePlusAuthenticationOptions is set to true /// public string RefreshToken { get; private set; } diff --git a/Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticationProvider.cs b/src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticationProvider.cs rename to src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticationProvider.cs index 43af752..19b94f9 100644 --- a/Owin.Security.Providers/VisualStudio/Provider/VisualStudioAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioAuthenticationProvider.cs @@ -27,7 +27,7 @@ namespace Owin.Security.Providers.VisualStudio { public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Visual Studio Online succesfully authenticates a user + /// Invoked whenever Visual Studio Online successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/VisualStudio/Provider/VisualStudioReturnEndpointContext.cs b/src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/VisualStudio/Provider/VisualStudioReturnEndpointContext.cs rename to src/Owin.Security.Providers.VisualStudio/Provider/VisualStudioReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.VisualStudio/Resources.Designer.cs b/src/Owin.Security.Providers.VisualStudio/Resources.Designer.cs new file mode 100644 index 0000000..d56410d --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.VisualStudio { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.VisualStudio.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.VisualStudio/Resources.resx b/src/Owin.Security.Providers.VisualStudio/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationExtensions.cs b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationExtensions.cs similarity index 81% rename from Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationExtensions.cs rename to src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationExtensions.cs index 9b79a17..fb1627a 100644 --- a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationExtensions.cs @@ -3,8 +3,8 @@ namespace Owin.Security.Providers.VisualStudio { public static class VisualStudioAuthenticationExtensions { public static IAppBuilder UseVisualStudioAuthentication(this IAppBuilder app, VisualStudioAuthenticationOptions options) { - if (app == null) throw new ArgumentNullException("app"); - if (options == null) throw new ArgumentNullException("options"); + if (app == null) throw new ArgumentNullException(nameof(app)); + if (options == null) throw new ArgumentNullException(nameof(options)); app.Use(typeof(VisualStudioAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationHandler.cs b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationHandler.cs new file mode 100644 index 0000000..5a10304 --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationHandler.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Threading.Tasks; +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.VisualStudio { + public class VisualStudioAuthenticationHandler : AuthenticationHandler { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public VisualStudioAuthenticationHandler(HttpClient httpClient, ILogger logger) { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() { + AuthenticationProperties properties = null; + + try { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) { + code = values[0]; + } + 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); + } + + var requestPrefix = "https://" + Request.Host; // Schema must be HTTPS + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("client_assertion_type", + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"), + new KeyValuePair("client_assertion", Options.AppSecret), + new KeyValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"), + new KeyValuePair("assertion", code), + new KeyValuePair("redirect_uri", redirectUri) + }; + + // Request the token + var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + requestMessage.Content = new FormUrlEncodedContent(body); + var tokenResponse = await _httpClient.SendAsync(requestMessage); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var refreshToken = (string)response.refresh_token; + var expiresIn = (int)response.expires_in; + + // Get the Visual Studio Online user + var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint); + userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + userResponse.EnsureSuccessStatusCode(); + text = await userResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + var context = new VisualStudioAuthenticatedContext(Context, user, accessToken, expiresIn, refreshToken) + { + 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.Name)) { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Email)) { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Alias)) { + context.Identity.AddClaim(new Claim("urn:vso:alias", context.Alias, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + "https" + //Schema must be HTTPS + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + // space separated + var scope = string.Join(" ", Options.Scope); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + Options.Endpoints.AuthorizationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.AppId) + + "&response_type=Assertion" + + "&state=" + Uri.EscapeDataString(state) + + "&scope=" + Uri.EscapeDataString(scope) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new VisualStudioReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} diff --git a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationMiddleware.cs b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationMiddleware.cs similarity index 58% rename from Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationMiddleware.cs index b94fde1..8a19cba 100644 --- a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationMiddleware.cs @@ -7,46 +7,45 @@ 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.VisualStudio { public class VisualStudioAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public VisualStudioAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, VisualStudioAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.AppId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppId)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientId")); - if (String.IsNullOrWhiteSpace(Options.AppSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + if (string.IsNullOrWhiteSpace(Options.AppSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new VisualStudioAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (VisualStudioAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10, }; - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin VisualStudio middleware"); - httpClient.DefaultRequestHeaders.ExpectContinue = false; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin VisualStudio middleware"); + _httpClient.DefaultRequestHeaders.ExpectContinue = false; } /// @@ -58,23 +57,22 @@ namespace Owin.Security.Providers.VisualStudio { /// supplied to the constructor. /// protected override AuthenticationHandler CreateHandler() { - return new VisualStudioAuthenticationHandler(httpClient, logger); + return new VisualStudioAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(VisualStudioAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + private static HttpMessageHandler ResolveHttpMessageHandler(VisualStudioAuthenticationOptions options) { + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; - } + 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; + return handler; } } } diff --git a/Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationOptions.cs b/src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/VisualStudio/VisualStudioAuthenticationOptions.cs rename to src/Owin.Security.Providers.VisualStudio/VisualStudioAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.VisualStudio/packages.config b/src/Owin.Security.Providers.VisualStudio/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.VisualStudio/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Wargaming/Constants.cs b/src/Owin.Security.Providers.Wargaming/Constants.cs similarity index 82% rename from Owin.Security.Providers/Wargaming/Constants.cs rename to src/Owin.Security.Providers.Wargaming/Constants.cs index a2fc304..ed2175e 100644 --- a/Owin.Security.Providers/Wargaming/Constants.cs +++ b/src/Owin.Security.Providers.Wargaming/Constants.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace Owin.Security.Providers.Wargaming +namespace Owin.Security.Providers.Wargaming { internal static class Constants { diff --git a/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.csproj b/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.csproj new file mode 100644 index 0000000..52f7d80 --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.csproj @@ -0,0 +1,107 @@ + + + + + Debug + AnyCPU + {AA72BFCE-8495-4A4D-988D-F8D490521776} + Library + Properties + Owin.Security.Providers.Wargaming + Owin.Security.Providers.Wargaming + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {4FD7B873-1994-4990-AA40-C37060121494} + Owin.Security.Providers.OpenIDBase + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Wargaming.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.nuspec b/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.nuspec new file mode 100644 index 0000000..c4b282c --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/Owin.Security.Providers.Wargaming.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Wargaming + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Wargaming OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Wargaming + + + + + + + + + diff --git a/src/Owin.Security.Providers.Wargaming/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Wargaming/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..497db0d --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Wargaming")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Wargaming")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("8f5acd7a-3c19-49d6-ac5f-2368e2534452")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Owin.Security.Providers.Wargaming/Resources.Designer.cs b/src/Owin.Security.Providers.Wargaming/Resources.Designer.cs new file mode 100644 index 0000000..d266130 --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Wargaming { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Wargaming.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Wargaming/Resources.resx b/src/Owin.Security.Providers.Wargaming/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Wargaming/WargamingAccountAuthenticationExtensions.cs b/src/Owin.Security.Providers.Wargaming/WargamingAccountAuthenticationExtensions.cs similarity index 95% rename from Owin.Security.Providers/Wargaming/WargamingAccountAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Wargaming/WargamingAccountAuthenticationExtensions.cs index 02b9403..1dc20f8 100644 --- a/Owin.Security.Providers/Wargaming/WargamingAccountAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Wargaming/WargamingAccountAuthenticationExtensions.cs @@ -37,11 +37,11 @@ namespace Owin.Security.Providers.Wargaming { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } return app.Use(typeof (WargamingAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationHandler.cs b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationHandler.cs new file mode 100644 index 0000000..6c2473f --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.Owin.Logging; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Text.RegularExpressions; +using Owin.Security.Providers.OpenIDBase; + +namespace Owin.Security.Providers.Wargaming +{ + internal sealed class WargamingAuthenticationHandler : OpenIDAuthenticationHandlerBase + { + private readonly Regex _accountIDRegex = new Regex(@"^https://na.wargaming.net/id/([0-9]{10}).*$", RegexOptions.Compiled); + + private const string UserInfoUri = "https://api.worldoftanks.com/wot/account/info/?application_id={0}&account_id={1}&fields=nickname"; + + public WargamingAuthenticationHandler(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + protected override void SetIdentityInformations(ClaimsIdentity identity, string claimedID, IDictionary attributeExchangeProperties) + { + var accountIDMatch = _accountIDRegex.Match(claimedID); + if (!accountIDMatch.Success) return; + var accountID = accountIDMatch.Groups[1].Value; + var getUserInfoTask = HTTPClient.GetStringAsync(string.Format(UserInfoUri, Options.AppId, accountID)); + getUserInfoTask.Wait(); + var userInfoRaw = getUserInfoTask.Result; + dynamic userInfo = JsonConvert.DeserializeObject(userInfoRaw); + identity.AddClaim(new Claim(ClaimTypes.Name, (string)userInfo["data"][accountID].nickname, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Wargaming/WargamingAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationMiddleware.cs similarity index 73% rename from Owin.Security.Providers/Wargaming/WargamingAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Wargaming/WargamingAuthenticationMiddleware.cs index 3f541e3..5f05b6c 100644 --- a/Owin.Security.Providers/Wargaming/WargamingAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationMiddleware.cs @@ -1,13 +1,6 @@ -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; using Microsoft.Owin.Security.Infrastructure; -using Owin.Security.Providers.OpenID; +using Owin.Security.Providers.OpenIDBase; namespace Owin.Security.Providers.Wargaming { @@ -29,7 +22,7 @@ namespace Owin.Security.Providers.Wargaming protected override AuthenticationHandler CreateSpecificHandler() { - return new WargamingAuthenticationHandler(_httpClient, _logger); + return new WargamingAuthenticationHandler(HTTPClient, Logger); } } } diff --git a/Owin.Security.Providers/Wargaming/WargamingAuthenticationOptions.cs b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationOptions.cs similarity index 71% rename from Owin.Security.Providers/Wargaming/WargamingAuthenticationOptions.cs rename to src/Owin.Security.Providers.Wargaming/WargamingAuthenticationOptions.cs index be1a2a7..78a72ac 100644 --- a/Owin.Security.Providers/Wargaming/WargamingAuthenticationOptions.cs +++ b/src/Owin.Security.Providers.Wargaming/WargamingAuthenticationOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Owin.Security.Providers.OpenID; +using Owin.Security.Providers.OpenIDBase; namespace Owin.Security.Providers.Wargaming { @@ -27,6 +21,4 @@ namespace Owin.Security.Providers.Wargaming /// public string AppId { get; set; } } - - } diff --git a/src/Owin.Security.Providers.Wargaming/packages.config b/src/Owin.Security.Providers.Wargaming/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Wargaming/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/Constants.cs b/src/Owin.Security.Providers.WordPress/Constants.cs similarity index 100% rename from Owin.Security.Providers/WordPress/Constants.cs rename to src/Owin.Security.Providers.WordPress/Constants.cs diff --git a/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.csproj b/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.csproj new file mode 100644 index 0000000..e009f1e --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {0EDE8223-DD5F-4DB8-A98A-64B1F4591F48} + Library + Properties + Owin.Security.Providers.WordPress + Owin.Security.Providers.WordPress + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.WordPress.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.nuspec b/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.nuspec new file mode 100644 index 0000000..7c8da7a --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/Owin.Security.Providers.WordPress.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.WordPress + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a WordPress OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth WordPress + + + + + + + + + diff --git a/src/Owin.Security.Providers.WordPress/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.WordPress/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2c24e50 --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.WordPress")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.WordPress")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("47803b51-a4ca-401e-b070-f468a87fca68")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs b/src/Owin.Security.Providers.WordPress/Provider/IWordPressAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs rename to src/Owin.Security.Providers.WordPress/Provider/IWordPressAuthenticationProvider.cs index d04ae07..2189a99 100644 --- a/Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.WordPress/Provider/IWordPressAuthenticationProvider.cs @@ -8,7 +8,7 @@ namespace Owin.Security.Providers.WordPress public interface IWordPressAuthenticationProvider { /// - /// Invoked whenever WordPress succesfully authenticates a user + /// Invoked whenever WordPress successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs b/src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticatedContext.cs similarity index 98% rename from Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs rename to src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticatedContext.cs index 22a0b4c..cef3584 100644 --- a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticatedContext.cs @@ -1,7 +1,6 @@ // 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; @@ -20,6 +19,7 @@ namespace Owin.Security.Providers.WordPress /// /// The OWIN environment /// The JSON-serialized user + /// /// WordPress Access token /// The ID for this blog /// The URL for this blog diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs b/src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs rename to src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticationProvider.cs index 175f861..a2cdf3c 100644 --- a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.WordPress/Provider/WordPressAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.WordPress public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever WordPress succesfully authenticates a user + /// Invoked whenever WordPress successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs b/src/Owin.Security.Providers.WordPress/Provider/WordPressReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs rename to src/Owin.Security.Providers.WordPress/Provider/WordPressReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.WordPress/Resources.Designer.cs b/src/Owin.Security.Providers.WordPress/Resources.Designer.cs new file mode 100644 index 0000000..55a411a --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.WordPress { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.WordPress.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.WordPress/Resources.resx b/src/Owin.Security.Providers.WordPress/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationExtensions.cs similarity index 86% rename from Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs rename to src/Owin.Security.Providers.WordPress/WordPressAuthenticationExtensions.cs index 85f4f13..1cbc462 100644 --- a/Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationExtensions.cs @@ -8,9 +8,9 @@ namespace Owin.Security.Providers.WordPress WordPressAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(WordPressAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.WordPress/WordPressAuthenticationHandler.cs b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationHandler.cs new file mode 100644 index 0000000..917d98e --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationHandler.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +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.WordPress +{ + public class WordPressAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://public-api.wordpress.com/oauth2/token"; + private const string UserInfoEndpoint = "https://public-api.wordpress.com/rest/v1/me"; + private const string SiteInfoEndpoint = "https://public-api.wordpress.com/rest/v1/sites/"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public WordPressAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + 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); + } + + var requestPrefix = Request.Scheme + "://" + Request.Host; + var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List> + { + new KeyValuePair("grant_type", "authorization_code"), + new KeyValuePair("code", code), + new KeyValuePair("redirect_uri", redirectUri), + new KeyValuePair("client_id", Options.ClientId), + new KeyValuePair("client_secret", Options.ClientSecret) + }; + + // Request the token + var tokenResponse = + await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token; + var blogId = (string)response.blog_id; + var blogUrl = (string)response.blog_url; + + // Get the Wordpress user + var userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); + userRequest.Headers.Add("User-Agent", "OWIN OAuth Provider"); + userRequest.Headers.Add("Authorization", "BEARER " + accessToken); + var graphResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + var user = JObject.Parse(text); + + // Get the site details + var siteRequest = new HttpRequestMessage(HttpMethod.Get, SiteInfoEndpoint + blogId); + siteRequest.Headers.Add("User-Agent", "OWIN OAuth Provider"); + siteRequest.Headers.Add("Authorization", "BEARER " + accessToken); + var siteResponse = await _httpClient.SendAsync(siteRequest, Request.CallCancelled); + siteResponse.EnsureSuccessStatusCode(); + text = await siteResponse.Content.ReadAsStringAsync(); + var site = JObject.Parse(text); + + var context = new WordPressAuthenticatedContext(Context, user, site, accessToken, blogId, blogUrl) + { + 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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Email)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + "https://public-api.wordpress.com/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new WordPressReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationMiddleware.cs similarity index 61% rename from Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.WordPress/WordPressAuthenticationMiddleware.cs index a891329..79b0a32 100644 --- a/Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationMiddleware.cs @@ -7,43 +7,42 @@ 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.WordPress { public class WordPressAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public WordPressAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, WordPressAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, + 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, + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new WordPressAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof (WordPressAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024*1024*10 @@ -60,24 +59,22 @@ namespace Owin.Security.Providers.WordPress /// protected override AuthenticationHandler CreateHandler() { - return new WordPressAuthenticationHandler(httpClient, logger); + return new WordPressAuthenticationHandler(_httpClient, _logger); } - private HttpMessageHandler ResolveHttpMessageHandler(WordPressAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(WordPressAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // If they provided a validator, apply it or fail. - if (options.BackchannelCertificateValidator != null) + if (options.BackchannelCertificateValidator == null) return handler; + // Set the cert validate callback + var webRequestHandler = handler as WebRequestHandler; + if (webRequestHandler == null) { - // Set the cert validate callback - var webRequestHandler = handler as WebRequestHandler; - if (webRequestHandler == null) - { - throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); - } - webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; + throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch); } + webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate; return handler; } diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs b/src/Owin.Security.Providers.WordPress/WordPressAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs rename to src/Owin.Security.Providers.WordPress/WordPressAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.WordPress/packages.config b/src/Owin.Security.Providers.WordPress/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.WordPress/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Xing/Constants.cs b/src/Owin.Security.Providers.Xing/Constants.cs similarity index 100% rename from Owin.Security.Providers/Xing/Constants.cs rename to src/Owin.Security.Providers.Xing/Constants.cs diff --git a/Owin.Security.Providers/Xing/Messages/AccessToken.cs b/src/Owin.Security.Providers.Xing/Messages/AccessToken.cs similarity index 100% rename from Owin.Security.Providers/Xing/Messages/AccessToken.cs rename to src/Owin.Security.Providers.Xing/Messages/AccessToken.cs diff --git a/Owin.Security.Providers/Xing/Messages/RequestToken.cs b/src/Owin.Security.Providers.Xing/Messages/RequestToken.cs similarity index 100% rename from Owin.Security.Providers/Xing/Messages/RequestToken.cs rename to src/Owin.Security.Providers.Xing/Messages/RequestToken.cs diff --git a/Owin.Security.Providers/Xing/Messages/RequestTokenSerializer.cs b/src/Owin.Security.Providers.Xing/Messages/RequestTokenSerializer.cs similarity index 85% rename from Owin.Security.Providers/Xing/Messages/RequestTokenSerializer.cs rename to src/Owin.Security.Providers.Xing/Messages/RequestTokenSerializer.cs index 4fc05af..21c1baa 100644 --- a/Owin.Security.Providers/Xing/Messages/RequestTokenSerializer.cs +++ b/src/Owin.Security.Providers.Xing/Messages/RequestTokenSerializer.cs @@ -1,5 +1,4 @@ -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.DataHandler.Serializer; +using Microsoft.Owin.Security.DataHandler.Serializer; using System; using System.IO; @@ -55,11 +54,11 @@ namespace Owin.Security.Providers.Xing.Messages { if (writer == null) { - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); } if (token == null) { - throw new ArgumentNullException("token"); + throw new ArgumentNullException(nameof(token)); } writer.Write(FormatVersion); @@ -78,7 +77,7 @@ namespace Owin.Security.Providers.Xing.Messages { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (reader.ReadInt32() != FormatVersion) @@ -86,10 +85,10 @@ namespace Owin.Security.Providers.Xing.Messages return null; } - string token = reader.ReadString(); - string tokenSecret = reader.ReadString(); - bool callbackConfirmed = reader.ReadBoolean(); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var token = reader.ReadString(); + var tokenSecret = reader.ReadString(); + var callbackConfirmed = reader.ReadBoolean(); + var properties = PropertiesSerializer.Read(reader); if (properties == null) { return null; diff --git a/Owin.Security.Providers/Xing/Messages/Serializer.cs b/src/Owin.Security.Providers.Xing/Messages/Serializer.cs similarity index 100% rename from Owin.Security.Providers/Xing/Messages/Serializer.cs rename to src/Owin.Security.Providers.Xing/Messages/Serializer.cs diff --git a/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.csproj b/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.csproj new file mode 100644 index 0000000..24e393c --- /dev/null +++ b/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.csproj @@ -0,0 +1,110 @@ + + + + + Debug + AnyCPU + {D497D8BD-6EF9-4C30-B195-B0DD153418D6} + Library + Properties + Owin.Security.Providers.Xing + Owin.Security.Providers.Xing + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Xing.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.nuspec b/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.nuspec new file mode 100644 index 0000000..13a283b --- /dev/null +++ b/src/Owin.Security.Providers.Xing/Owin.Security.Providers.Xing.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Xing + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Xing OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Xing + + + + + + + + + diff --git a/src/Owin.Security.Providers.Xing/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Xing/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..dd407ba --- /dev/null +++ b/src/Owin.Security.Providers.Xing/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Xing")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Xing")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("c21a2178-4434-4240-bc5f-0c49abebd152")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Xing/Provider/IXingAuthenticationProvider.cs b/src/Owin.Security.Providers.Xing/Provider/IXingAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Xing/Provider/IXingAuthenticationProvider.cs rename to src/Owin.Security.Providers.Xing/Provider/IXingAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Xing/Provider/XingApplyRedirectContext.cs b/src/Owin.Security.Providers.Xing/Provider/XingApplyRedirectContext.cs similarity index 100% rename from Owin.Security.Providers/Xing/Provider/XingApplyRedirectContext.cs rename to src/Owin.Security.Providers.Xing/Provider/XingApplyRedirectContext.cs diff --git a/Owin.Security.Providers/Xing/Provider/XingAuthenticatedContext.cs b/src/Owin.Security.Providers.Xing/Provider/XingAuthenticatedContext.cs similarity index 100% rename from Owin.Security.Providers/Xing/Provider/XingAuthenticatedContext.cs rename to src/Owin.Security.Providers.Xing/Provider/XingAuthenticatedContext.cs diff --git a/Owin.Security.Providers/Xing/Provider/XingAuthenticationProvider.cs b/src/Owin.Security.Providers.Xing/Provider/XingAuthenticationProvider.cs similarity index 100% rename from Owin.Security.Providers/Xing/Provider/XingAuthenticationProvider.cs rename to src/Owin.Security.Providers.Xing/Provider/XingAuthenticationProvider.cs diff --git a/Owin.Security.Providers/Xing/Provider/XingReturnEndpointContext.cs b/src/Owin.Security.Providers.Xing/Provider/XingReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Xing/Provider/XingReturnEndpointContext.cs rename to src/Owin.Security.Providers.Xing/Provider/XingReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Xing/Resources.Designer.cs b/src/Owin.Security.Providers.Xing/Resources.Designer.cs new file mode 100644 index 0000000..bd1673c --- /dev/null +++ b/src/Owin.Security.Providers.Xing/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Xing { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Xing.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Xing/Resources.resx b/src/Owin.Security.Providers.Xing/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Xing/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Xing/XingAuthenticationExtensions.cs b/src/Owin.Security.Providers.Xing/XingAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Xing/XingAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Xing/XingAuthenticationExtensions.cs index 947b4a2..48a49b6 100644 --- a/Owin.Security.Providers/Xing/XingAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Xing/XingAuthenticationExtensions.cs @@ -7,9 +7,9 @@ namespace Owin.Security.Providers.Xing public static IAppBuilder UseXingAuthentication(this IAppBuilder app, XingAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(XingAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Xing/XingAuthenticationHandler.cs b/src/Owin.Security.Providers.Xing/XingAuthenticationHandler.cs similarity index 84% rename from Owin.Security.Providers/Xing/XingAuthenticationHandler.cs rename to src/Owin.Security.Providers.Xing/XingAuthenticationHandler.cs index 1d92abd..a0524b2 100644 --- a/Owin.Security.Providers/Xing/XingAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Xing/XingAuthenticationHandler.cs @@ -83,18 +83,23 @@ namespace Owin.Security.Providers.Xing var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); - var context = new XingAuthenticatedContext(Context, accessToken.UserId, accessToken.Token, accessToken.TokenSecret); + var context = new XingAuthenticatedContext(Context, accessToken.UserId, accessToken.Token, + accessToken.TokenSecret) + { + Identity = new ClaimsIdentity( + new[] + { + new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, + "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType), + new Claim("urn:xing:userid", accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", + Options.AuthenticationType), + }, + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType), + Properties = requestToken.Properties + }; - context.Identity = new ClaimsIdentity( - new[] - { - new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType), - new Claim("urn:xing:userid", accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType), - }, - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - context.Properties = requestToken.Properties; Response.Cookies.Delete(StateCookie); @@ -184,16 +189,14 @@ namespace Owin.Security.Providers.Xing Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } @@ -214,14 +217,14 @@ namespace Owin.Security.Providers.Xing { "oauth_version", "1.0" } }; - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(GetParameters(authorizationParts))); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(GetParameters(authorizationParts))); - var signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, null, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -275,14 +278,14 @@ namespace Owin.Security.Providers.Xing parameterBuilder.Length--; var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); authorizationParts.Remove("oauth_verifier"); @@ -338,8 +341,8 @@ namespace Owin.Security.Providers.Xing private static string GenerateTimeStamp() { - var secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; - return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + var secondsSinceUnixEpochStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpochStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData) diff --git a/Owin.Security.Providers/Xing/XingAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Xing/XingAuthenticationMiddleware.cs similarity index 99% rename from Owin.Security.Providers/Xing/XingAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Xing/XingAuthenticationMiddleware.cs index 6c9a464..9d7849f 100644 --- a/Owin.Security.Providers/Xing/XingAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Xing/XingAuthenticationMiddleware.cs @@ -9,7 +9,6 @@ using Owin.Security.Providers.Xing.Messages; using System; using System.Globalization; using System.Net.Http; -using Owin.Security.Providers.Properties; using Owin.Security.Providers.Xing.Provider; namespace Owin.Security.Providers.Xing diff --git a/Owin.Security.Providers/Xing/XingAuthenticationOptions.cs b/src/Owin.Security.Providers.Xing/XingAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Xing/XingAuthenticationOptions.cs rename to src/Owin.Security.Providers.Xing/XingAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Xing/packages.config b/src/Owin.Security.Providers.Xing/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Xing/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Yahoo/Constants.cs b/src/Owin.Security.Providers.Yahoo/Constants.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/Constants.cs rename to src/Owin.Security.Providers.Yahoo/Constants.cs diff --git a/Owin.Security.Providers/Yahoo/Messages/AccessToken.cs b/src/Owin.Security.Providers.Yahoo/Messages/AccessToken.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/Messages/AccessToken.cs rename to src/Owin.Security.Providers.Yahoo/Messages/AccessToken.cs diff --git a/Owin.Security.Providers/Yahoo/Messages/RequestToken.cs b/src/Owin.Security.Providers.Yahoo/Messages/RequestToken.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/Messages/RequestToken.cs rename to src/Owin.Security.Providers.Yahoo/Messages/RequestToken.cs diff --git a/Owin.Security.Providers/Yahoo/Messages/RequestTokenSerializer.cs b/src/Owin.Security.Providers.Yahoo/Messages/RequestTokenSerializer.cs similarity index 88% rename from Owin.Security.Providers/Yahoo/Messages/RequestTokenSerializer.cs rename to src/Owin.Security.Providers.Yahoo/Messages/RequestTokenSerializer.cs index 1aa9325..5539bed 100644 --- a/Owin.Security.Providers/Yahoo/Messages/RequestTokenSerializer.cs +++ b/src/Owin.Security.Providers.Yahoo/Messages/RequestTokenSerializer.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler.Serializer; namespace Owin.Security.Providers.Yahoo.Messages @@ -60,11 +59,11 @@ namespace Owin.Security.Providers.Yahoo.Messages { if (writer == null) { - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); } if (token == null) { - throw new ArgumentNullException("token"); + throw new ArgumentNullException(nameof(token)); } writer.Write(FormatVersion); @@ -83,7 +82,7 @@ namespace Owin.Security.Providers.Yahoo.Messages { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (reader.ReadInt32() != FormatVersion) @@ -91,10 +90,10 @@ namespace Owin.Security.Providers.Yahoo.Messages return null; } - string token = reader.ReadString(); - string tokenSecret = reader.ReadString(); - bool callbackConfirmed = reader.ReadBoolean(); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var token = reader.ReadString(); + var tokenSecret = reader.ReadString(); + var callbackConfirmed = reader.ReadBoolean(); + var properties = PropertiesSerializer.Read(reader); if (properties == null) { return null; diff --git a/Owin.Security.Providers/Yahoo/Messages/Serializers.cs b/src/Owin.Security.Providers.Yahoo/Messages/Serializers.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/Messages/Serializers.cs rename to src/Owin.Security.Providers.Yahoo/Messages/Serializers.cs diff --git a/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.csproj b/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.csproj new file mode 100644 index 0000000..b4b2010 --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {1765BEDB-9E4B-468C-BAF6-06784CDCED67} + Library + Properties + Owin.Security.Providers.Yahoo + Owin.Security.Providers.Yahoo + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Yahoo.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.nuspec b/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.nuspec new file mode 100644 index 0000000..418db23 --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/Owin.Security.Providers.Yahoo.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Yahoo + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Yahoo OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Yahoo + + + + + + + + + diff --git a/src/Owin.Security.Providers.Yahoo/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Yahoo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ff2b0d8 --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Yahoo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Yahoo")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("b502e83b-6d22-4af2-bcb4-1b44b3908a0d")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Yahoo/Provider/IYahooAuthenticationProvider.cs b/src/Owin.Security.Providers.Yahoo/Provider/IYahooAuthenticationProvider.cs similarity index 94% rename from Owin.Security.Providers/Yahoo/Provider/IYahooAuthenticationProvider.cs rename to src/Owin.Security.Providers.Yahoo/Provider/IYahooAuthenticationProvider.cs index 8a8594f..8d2e5f1 100644 --- a/Owin.Security.Providers/Yahoo/Provider/IYahooAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Yahoo/Provider/IYahooAuthenticationProvider.cs @@ -10,7 +10,7 @@ namespace Owin.Security.Providers.Yahoo public interface IYahooAuthenticationProvider { /// - /// Invoked whenever Yahoo succesfully authenticates a user + /// Invoked whenever Yahoo successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Yahoo/Provider/YahooAuthenticatedContext.cs b/src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticatedContext.cs similarity index 67% rename from Owin.Security.Providers/Yahoo/Provider/YahooAuthenticatedContext.cs rename to src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticatedContext.cs index 818b40d..217e408 100644 --- a/Owin.Security.Providers/Yahoo/Provider/YahooAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticatedContext.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -using System; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Claims; using Microsoft.Owin; using Microsoft.Owin.Security; @@ -89,44 +87,40 @@ namespace Owin.Security.Providers.Yahoo return user.TryGetValue(propertyName, out value) ? value.ToString() : null; } - private string GetEmail(JObject user) + private static string GetEmail(JObject user) { - if (user != null) + if (user == null) return null; + try { - try + JToken email = null; + JToken emails; + + // Get the emails element + user.TryGetValue("emails", out emails); + + if (emails != null) { - JToken email = null; - JToken emails = null; - - // Get the emails element - user.TryGetValue("emails", out emails); - - if (emails != null) + if (emails.Type == JTokenType.Array) { - if (emails.Type == JTokenType.Array) - { - // Try and get the primary email address - email = emails.FirstOrDefault(e => e["primary"]!=null && e["primary"].ToString() == "true"); + // Try and get the primary email address + email = emails.FirstOrDefault(e => e["primary"]!=null && e["primary"].ToString() == "true") ?? + emails.FirstOrDefault(); - // If no primary email was located, select the first email we can find - if (email == null) - email = emails.FirstOrDefault(); - } - else if (emails.Type == JTokenType.Object) - { - // If the emails element is a single object and not an array, then take that object as the email object - email = emails; - } - - // If we managed to find an email (primary or otherwise), then return the email - if (email != null && email["handle"] != null) - return email["handle"].ToString(); + // If no primary email was located, select the first email we can find } + else if (emails.Type == JTokenType.Object) + { + // If the emails element is a single object and not an array, then take that object as the email object + email = emails; + } + + // If we managed to find an email (primary or otherwise), then return the email + if (email?["handle"] != null) return email["handle"].ToString(); } - catch - { - // Suppress any exception here - } + } + catch + { + // Suppress any exception here } return null; diff --git a/Owin.Security.Providers/Yahoo/Provider/YahooAuthenticationProvider.cs b/src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Yahoo/Provider/YahooAuthenticationProvider.cs rename to src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticationProvider.cs index 9e66d2a..485cce7 100644 --- a/Owin.Security.Providers/Yahoo/Provider/YahooAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Yahoo/Provider/YahooAuthenticationProvider.cs @@ -30,7 +30,7 @@ namespace Owin.Security.Providers.Yahoo public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Yahoo succesfully authenticates a user + /// Invoked whenever Yahoo successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Yahoo/Provider/YahooReturnEndpointContext.cs b/src/Owin.Security.Providers.Yahoo/Provider/YahooReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/Provider/YahooReturnEndpointContext.cs rename to src/Owin.Security.Providers.Yahoo/Provider/YahooReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Yahoo/Resources.Designer.cs b/src/Owin.Security.Providers.Yahoo/Resources.Designer.cs new file mode 100644 index 0000000..0a00b79 --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Yahoo { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Yahoo.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Yahoo/Resources.resx b/src/Owin.Security.Providers.Yahoo/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Yahoo/YahooAuthenticationExtensions.cs b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationExtensions.cs similarity index 93% rename from Owin.Security.Providers/Yahoo/YahooAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Yahoo/YahooAuthenticationExtensions.cs index 805e5c2..1e61e77 100644 --- a/Owin.Security.Providers/Yahoo/YahooAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationExtensions.cs @@ -19,11 +19,11 @@ namespace Owin.Security.Providers.Yahoo { if (app == null) { - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); } if (options == null) { - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); } app.Use(typeof(YahooAuthenticationMiddleware), app, options); diff --git a/Owin.Security.Providers/Yahoo/YahooAuthenticationHandler.cs b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationHandler.cs similarity index 72% rename from Owin.Security.Providers/Yahoo/YahooAuthenticationHandler.cs rename to src/Owin.Security.Providers.Yahoo/YahooAuthenticationHandler.cs index 1f46e98..15fdfea 100644 --- a/Owin.Security.Providers/Yahoo/YahooAuthenticationHandler.cs +++ b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationHandler.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using System.Runtime.InteropServices; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -53,10 +52,10 @@ namespace Owin.Security.Providers.Yahoo AuthenticationProperties properties = null; try { - IReadableStringCollection query = Request.Query; - string protectedRequestToken = Request.Cookies[StateCookie]; + var query = Request.Query; + var protectedRequestToken = Request.Cookies[StateCookie]; - RequestToken requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); + var requestToken = Options.StateDataFormat.Unprotect(protectedRequestToken); if (requestToken == null) { @@ -66,7 +65,7 @@ namespace Owin.Security.Providers.Yahoo properties = requestToken.Properties; - string returnedToken = query.Get("oauth_token"); + var returnedToken = query.Get("oauth_token"); if (string.IsNullOrWhiteSpace(returnedToken)) { _logger.WriteWarning("Missing oauth_token"); @@ -79,44 +78,47 @@ namespace Owin.Security.Providers.Yahoo return new AuthenticationTicket(null, properties); } - string oauthVerifier = query.Get("oauth_verifier"); + var oauthVerifier = query.Get("oauth_verifier"); if (string.IsNullOrWhiteSpace(oauthVerifier)) { _logger.WriteWarning("Missing or blank oauth_verifier"); return new AuthenticationTicket(null, properties); } - AccessToken accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); + var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); - JObject userCard = await ObtainUserProfile(Options.ConsumerKey, Options.ConsumerSecret, accessToken, oauthVerifier); + var userCard = await ObtainUserProfile(Options.ConsumerKey, Options.ConsumerSecret, accessToken, oauthVerifier); - var context = new YahooAuthenticatedContext(Context, userCard, accessToken.UserId, accessToken.Token, accessToken.TokenSecret); + var context = new YahooAuthenticatedContext(Context, userCard, accessToken.UserId, accessToken.Token, + accessToken.TokenSecret) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; - context.Identity = new ClaimsIdentity( - Options.AuthenticationType, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!String.IsNullOrEmpty(context.UserId)) + if (!string.IsNullOrEmpty(context.UserId)) { context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.NickName)) + if (!string.IsNullOrEmpty(context.NickName)) { context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.NickName, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.Email)) + if (!string.IsNullOrEmpty(context.Email)) { context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.UserId)) + if (!string.IsNullOrEmpty(context.UserId)) { context.Identity.AddClaim(new Claim("urn:yahoo:userid", context.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); } - if (!String.IsNullOrEmpty(context.NickName)) + if (!string.IsNullOrEmpty(context.NickName)) { context.Identity.AddClaim(new Claim("urn:yahoo:nickname", context.NickName, "http://www.w3.org/2001/XMLSchema#string", Options.AuthenticationType)); @@ -144,24 +146,24 @@ namespace Owin.Security.Providers.Yahoo return; } - AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); if (challenge != null) { - string requestPrefix = Request.Scheme + "://" + Request.Host; - string callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; + var requestPrefix = Request.Scheme + "://" + Request.Host; + var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; - AuthenticationProperties extra = challenge.Properties; + var extra = challenge.Properties; if (string.IsNullOrEmpty(extra.RedirectUri)) { extra.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; } - RequestToken requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, extra); + var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, extra); if (requestToken.CallbackConfirmed) { - string yahooAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token; + var yahooAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token; var cookieOptions = new CookieOptions { @@ -182,7 +184,7 @@ namespace Owin.Security.Providers.Yahoo public async Task InvokeReturnPathAsync() { - AuthenticationTicket model = await AuthenticateAsync(); + var model = await AuthenticateAsync(); if (model == null) { _logger.WriteWarning("Invalid return state, unable to redirect."); @@ -201,7 +203,7 @@ namespace Owin.Security.Providers.Yahoo if (context.SignInAsAuthenticationType != null && context.Identity != null) { - ClaimsIdentity signInIdentity = context.Identity; + var signInIdentity = context.Identity; if (!string.Equals(signInIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal)) { signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType); @@ -209,16 +211,14 @@ namespace Owin.Security.Providers.Yahoo Context.Authentication.SignIn(context.Properties, signInIdentity); } - if (!context.IsRequestCompleted && context.RedirectUri != null) + if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted; + if (context.Identity == null) { - if (context.Identity == null) - { - // add a redirect hint that sign-in failed in some way - context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); - } - Response.Redirect(context.RedirectUri); - context.RequestCompleted(); + // add a redirect hint that sign-in failed in some way + context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied"); } + Response.Redirect(context.RedirectUri); + context.RequestCompleted(); return context.IsRequestCompleted; } @@ -229,7 +229,7 @@ namespace Owin.Security.Providers.Yahoo _logger.WriteVerbose("ObtainRequestToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { @@ -247,16 +247,16 @@ namespace Owin.Security.Providers.Yahoo parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(RequestTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, null, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, null, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); //-- @@ -272,11 +272,11 @@ namespace Owin.Security.Providers.Yahoo var request = new HttpRequestMessage(HttpMethod.Post, RequestTokenEndpoint); request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); response.EnsureSuccessStatusCode(); - string responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseParameters = WebHelpers.ParseForm(responseText); if (string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.InvariantCulture)) { return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties }; @@ -291,7 +291,7 @@ namespace Owin.Security.Providers.Yahoo _logger.WriteVerbose("ObtainAccessToken"); - string nonce = Guid.NewGuid().ToString("N"); + var nonce = Guid.NewGuid().ToString("N"); var authorizationParts = new SortedDictionary { @@ -310,16 +310,16 @@ namespace Owin.Security.Providers.Yahoo parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Post.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(AccessTokenEndpoint)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -341,7 +341,7 @@ namespace Owin.Security.Providers.Yahoo request.Content = new FormUrlEncodedContent(formPairs); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); if (!response.IsSuccessStatusCode) { @@ -349,14 +349,15 @@ namespace Owin.Security.Providers.Yahoo response.EnsureSuccessStatusCode(); // throw } - string responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync(); - IFormCollection responseParameters = WebHelpers.ParseForm(responseText); + var responseParameters = WebHelpers.ParseForm(responseText); return new AccessToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), + // ReSharper disable once StringLiteralTypo UserId = Uri.UnescapeDataString(responseParameters["xoauth_yahoo_guid"]) }; } @@ -365,8 +366,8 @@ namespace Owin.Security.Providers.Yahoo { _logger.WriteVerbose("ObtainUserProfile"); - string nonce = Guid.NewGuid().ToString("N"); - string requestUrl = "https://query.yahooapis.com/v1/yql"; + var nonce = Guid.NewGuid().ToString("N"); + var requestUrl = "https://query.yahooapis.com/v1/yql"; var queryParts = new Dictionary { @@ -390,16 +391,16 @@ namespace Owin.Security.Providers.Yahoo parameterBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(authorizationKey.Key), Uri.EscapeDataString(authorizationKey.Value)); } parameterBuilder.Length--; - string parameterString = parameterBuilder.ToString(); + var parameterString = parameterBuilder.ToString(); - var canonicalizedRequestBuilder = new StringBuilder(); - canonicalizedRequestBuilder.Append(HttpMethod.Get.Method); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(requestUrl)); - canonicalizedRequestBuilder.Append("&"); - canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString)); + var canonicalRequestBuilder = new StringBuilder(); + canonicalRequestBuilder.Append(HttpMethod.Get.Method); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(requestUrl)); + canonicalRequestBuilder.Append("&"); + canonicalRequestBuilder.Append(Uri.EscapeDataString(parameterString)); - string signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalizedRequestBuilder.ToString()); + var signature = ComputeSignature(consumerSecret, token.TokenSecret, canonicalRequestBuilder.ToString()); authorizationParts.Add("oauth_signature", signature); var authorizationHeaderBuilder = new StringBuilder(); @@ -416,7 +417,7 @@ namespace Owin.Security.Providers.Yahoo request.Headers.Add("Authorization", authorizationHeaderBuilder.ToString()); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage response = await _httpClient.SendAsync(request, Request.CallCancelled); + var response = await _httpClient.SendAsync(request, Request.CallCancelled); if (!response.IsSuccessStatusCode) { @@ -424,28 +425,22 @@ namespace Owin.Security.Providers.Yahoo response.EnsureSuccessStatusCode(); // throw } - string responseText = await response.Content.ReadAsStringAsync(); - JObject responseObject = JObject.Parse(responseText); + var responseText = await response.Content.ReadAsStringAsync(); + var responseObject = JObject.Parse(responseText); var queryObject = responseObject.SelectToken("query"); - if (queryObject != null) - { - int count = (int) queryObject.SelectToken("count"); - if (count > 0) - { - JObject userCard = (JObject)queryObject.SelectToken("results.profile"); + if (queryObject == null) return null; + var count = (int) queryObject.SelectToken("count"); + if (count <= 0) return null; + var userCard = (JObject)queryObject.SelectToken("results.profile"); - return userCard; - } - } - - return null; + return userCard; } private static string GenerateTimeStamp() { - TimeSpan secondsSinceUnixEpocStart = DateTime.UtcNow - Epoch; - return Convert.ToInt64(secondsSinceUnixEpocStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); + var secondsSinceUnixEpochStart = DateTime.UtcNow - Epoch; + return Convert.ToInt64(secondsSinceUnixEpochStart.TotalSeconds).ToString(CultureInfo.InvariantCulture); } private static string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData) @@ -457,7 +452,7 @@ namespace Owin.Security.Providers.Yahoo "{0}&{1}", Uri.EscapeDataString(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret))); - byte[] hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); + var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)); return Convert.ToBase64String(hash); } } diff --git a/Owin.Security.Providers/Yahoo/YahooAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationMiddleware.cs similarity index 91% rename from Owin.Security.Providers/Yahoo/YahooAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Yahoo/YahooAuthenticationMiddleware.cs index de4593a..f0ec06d 100644 --- a/Owin.Security.Providers/Yahoo/YahooAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationMiddleware.cs @@ -12,7 +12,6 @@ using Microsoft.Owin.Security.DataProtection; using Microsoft.Owin.Security.Infrastructure; using Owin.Security.Providers.Yahoo.Messages; using AppBuilderSecurityExtensions = Microsoft.Owin.Security.AppBuilderSecurityExtensions; -using Owin.Security.Providers.Properties; namespace Owin.Security.Providers.Yahoo { @@ -55,7 +54,7 @@ namespace Owin.Security.Providers.Yahoo } if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(YahooAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new SecureDataFormat( @@ -63,14 +62,17 @@ namespace Owin.Security.Providers.Yahoo dataProtector, TextEncodings.Base64Url); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) { Options.SignInAsAuthenticationType = AppBuilderSecurityExtensions.GetDefaultSignInAsAuthenticationType(app); } - _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)); - _httpClient.Timeout = Options.BackchannelTimeout; - _httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB + _httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + // 10 MB _httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft Owin Yahoo middleware"); _httpClient.DefaultRequestHeaders.ExpectContinue = false; @@ -88,7 +90,7 @@ namespace Owin.Security.Providers.Yahoo [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")] private static HttpMessageHandler ResolveHttpMessageHandler(YahooAuthenticationOptions options) { - HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); + var handler = options.BackchannelHttpHandler ?? new WebRequestHandler(); // Set the cert validate callback var webRequestHandler = handler as WebRequestHandler; diff --git a/Owin.Security.Providers/Yahoo/YahooAuthenticationOptions.cs b/src/Owin.Security.Providers.Yahoo/YahooAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Yahoo/YahooAuthenticationOptions.cs rename to src/Owin.Security.Providers.Yahoo/YahooAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Yahoo/packages.config b/src/Owin.Security.Providers.Yahoo/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Yahoo/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Owin.Security.Providers/Yammer/Constants.cs b/src/Owin.Security.Providers.Yammer/Constants.cs similarity index 100% rename from Owin.Security.Providers/Yammer/Constants.cs rename to src/Owin.Security.Providers.Yammer/Constants.cs diff --git a/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.csproj b/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.csproj new file mode 100644 index 0000000..70f8d30 --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {8D029A93-E687-4DDF-82B0-700EBBF477F7} + Library + Properties + Owin.Security.Providers.Yammer + Owin.Security.Providers.Yammer + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + Resources.resx + True + True + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + PostBuildMacros; + + if $(ConfigurationName) == Release $(SolutionDir)\nuget\NuGet pack "$(ProjectDir)Owin.Security.Providers.Yammer.nuspec" -o "$(SolutionDir)Output" -version @(VersionNumber) + + \ No newline at end of file diff --git a/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.nuspec b/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.nuspec new file mode 100644 index 0000000..b591092 --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/Owin.Security.Providers.Yammer.nuspec @@ -0,0 +1,29 @@ + + + + Owin.Security.Providers.Yammer + 2.0.0 + Jerrie Pelser, Eonasdan and contributors + Eonasdan + http://opensource.org/licenses/MIT + https://github.com/Eonasdan/OwinOAuthProviders + false + + Adds a Yammer OAuth provider for OWIN to use with ASP.NET + + + Providers have now been split into their own packages from Owin.Security.Providers + + + + Copyright 2016 + owin katana oauth Yammer + + + + + + + + + diff --git a/src/Owin.Security.Providers.Yammer/Properties/AssemblyInfo.cs b/src/Owin.Security.Providers.Yammer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8543347 --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Owin.Security.Providers.Yammer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Owin.Security.Providers.Yammer")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("832d3991-998d-4b42-8064-e75ac9d15158")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/Owin.Security.Providers/Yammer/Provider/IYammerAuthenticationProvider.cs b/src/Owin.Security.Providers.Yammer/Provider/IYammerAuthenticationProvider.cs similarity index 93% rename from Owin.Security.Providers/Yammer/Provider/IYammerAuthenticationProvider.cs rename to src/Owin.Security.Providers.Yammer/Provider/IYammerAuthenticationProvider.cs index 00ac549..346e8d2 100644 --- a/Owin.Security.Providers/Yammer/Provider/IYammerAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Yammer/Provider/IYammerAuthenticationProvider.cs @@ -5,7 +5,7 @@ namespace Owin.Security.Providers.Yammer.Provider public interface IYammerAuthenticationProvider { /// - /// Invoked whenever Yammer succesfully authenticates a user + /// Invoked whenever Yammer successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Yammer/Provider/YammerAuthenticatedContext.cs b/src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticatedContext.cs similarity index 89% rename from Owin.Security.Providers/Yammer/Provider/YammerAuthenticatedContext.cs rename to src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticatedContext.cs index 508962b..d1ebc88 100644 --- a/Owin.Security.Providers/Yammer/Provider/YammerAuthenticatedContext.cs +++ b/src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticatedContext.cs @@ -1,10 +1,7 @@ -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.Yammer.Provider { @@ -27,12 +24,10 @@ namespace Owin.Security.Providers.Yammer.Provider Name = user.full_name; Url = user.url; Network = user.network_name; - if (user.contact.email_addresses != null) + if (user.contact.email_addresses == null) return; + foreach (var eml in user.contact.email_addresses) { - foreach (var eml in user.contact.email_addresses) - { - if (eml.type == "primary") PrimaryEmail = eml.address; - } + if (eml.type == "primary") PrimaryEmail = eml.address; } } diff --git a/Owin.Security.Providers/Yammer/Provider/YammerAuthenticationProvider.cs b/src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticationProvider.cs similarity index 96% rename from Owin.Security.Providers/Yammer/Provider/YammerAuthenticationProvider.cs rename to src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticationProvider.cs index 73ea506..7cb1e2f 100644 --- a/Owin.Security.Providers/Yammer/Provider/YammerAuthenticationProvider.cs +++ b/src/Owin.Security.Providers.Yammer/Provider/YammerAuthenticationProvider.cs @@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Yammer.Provider public Func OnReturnEndpoint { get; set; } /// - /// Invoked whenever Yammer succesfully authenticates a user + /// Invoked whenever Yammer successfully authenticates a user /// /// Contains information about the login session as well as the user . /// A representing the completed operation. diff --git a/Owin.Security.Providers/Yammer/Provider/YammerReturnEndpointContext.cs b/src/Owin.Security.Providers.Yammer/Provider/YammerReturnEndpointContext.cs similarity index 100% rename from Owin.Security.Providers/Yammer/Provider/YammerReturnEndpointContext.cs rename to src/Owin.Security.Providers.Yammer/Provider/YammerReturnEndpointContext.cs diff --git a/src/Owin.Security.Providers.Yammer/Resources.Designer.cs b/src/Owin.Security.Providers.Yammer/Resources.Designer.cs new file mode 100644 index 0000000..312df27 --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Owin.Security.Providers.Yammer { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + var temp = new global::System.Resources.ResourceManager("Owin.Security.Providers.Yammer.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The '{0}' option must be provided.. + /// + internal static string Exception_OptionMustBeProvided { + get { + return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.. + /// + internal static string Exception_ValidatorHandlerMismatch { + get { + return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture); + } + } + } +} diff --git a/src/Owin.Security.Providers.Yammer/Resources.resx b/src/Owin.Security.Providers.Yammer/Resources.resx new file mode 100644 index 0000000..2a19bea --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '{0}' option must be provided. + + + An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler. + + \ No newline at end of file diff --git a/Owin.Security.Providers/Yammer/YammerAuthenticationExtensions.cs b/src/Owin.Security.Providers.Yammer/YammerAuthenticationExtensions.cs similarity index 85% rename from Owin.Security.Providers/Yammer/YammerAuthenticationExtensions.cs rename to src/Owin.Security.Providers.Yammer/YammerAuthenticationExtensions.cs index aa31c9b..8f4c5f2 100644 --- a/Owin.Security.Providers/Yammer/YammerAuthenticationExtensions.cs +++ b/src/Owin.Security.Providers.Yammer/YammerAuthenticationExtensions.cs @@ -7,9 +7,9 @@ namespace Owin.Security.Providers.Yammer public static IAppBuilder UseYammerAuthentication(this IAppBuilder app, YammerAuthenticationOptions options) { if (app == null) - throw new ArgumentNullException("app"); + throw new ArgumentNullException(nameof(app)); if (options == null) - throw new ArgumentNullException("options"); + throw new ArgumentNullException(nameof(options)); app.Use(typeof(YammerAuthenticationMiddleware), app, options); diff --git a/src/Owin.Security.Providers.Yammer/YammerAuthenticationHandler.cs b/src/Owin.Security.Providers.Yammer/YammerAuthenticationHandler.cs new file mode 100644 index 0000000..d618dcc --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/YammerAuthenticationHandler.cs @@ -0,0 +1,230 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Owin.Security.Providers.Yammer.Provider; + +namespace Owin.Security.Providers.Yammer +{ + public class YammerAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://www.yammer.com/oauth2/access_token.json"; + private const string UserAuthenticationEndpoint = "https://www.yammer.com/dialog/oauth"; + + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public YammerAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + var query = Request.Query; + var values = query.GetValues("code"); + if (values != null && values.Count == 1) + { + code = values[0]; + } + values = query.GetValues("state"); + if (values != null && values.Count == 1) + { + state = values[0]; + } + + properties = Options.StateDataFormat.Unprotect(state); + if (properties == null) + { + return null; + } + if (code == null) + { + throw new Exception(query["error"] + " - " + query["error_description"]); + } + + // OAuth2 10.12 CSRF + if (!ValidateCorrelationId(properties, _logger)) + { + return new AuthenticationTicket(null, properties); + } + + var endPoint = + TokenEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&client_secret=" + Uri.EscapeDataString(Options.ClientSecret) + + "&code=" + Uri.EscapeDataString(code); + + // Request the token + var tokenResponse = await _httpClient.GetAsync(endPoint); + tokenResponse.EnsureSuccessStatusCode(); + var text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + var accessToken = (string)response.access_token.token; + + // Get the Yammer user + dynamic user = response.user; + var context = new YammerAuthenticatedContext(Context, user, accessToken) + { + Identity = new ClaimsIdentity( + Options.AuthenticationType, + ClaimsIdentity.DefaultNameClaimType, + ClaimsIdentity.DefaultRoleClaimType) + }; + EnsureAcceptedNetwork(Options.AcceptedNetworks, context.Network); + + if (!string.IsNullOrEmpty(context.Id)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.PrimaryEmail)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.PrimaryEmail, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Url)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Uri, context.Url, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + context.Identity.AddClaim(new Claim("urn:Yammer:accesstoken", context.AccessToken, 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); + } + + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge == null) return Task.FromResult(null); + var baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + var currentUri = + baseUri + + Request.Path + + Request.QueryString; + + var redirectUri = + baseUri + + Options.CallbackPath; + + var properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + var state = Options.StateDataFormat.Protect(properties); + + var authorizationEndpoint = + UserAuthenticationEndpoint + + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + 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) return false; + // TODO: error responses + + var ticket = await AuthenticateAsync(); + if (ticket == null) + { + _logger.WriteWarning("Invalid return state, unable to redirect."); + Response.StatusCode = 500; + return true; + } + + var context = new YammerReturnEndpointContext(Context, ticket) + { + SignInAsAuthenticationType = Options.SignInAsAuthenticationType, + RedirectUri = ticket.Properties.RedirectUri + }; + + await Options.Provider.ReturnEndpoint(context); + + if (context.SignInAsAuthenticationType != null && + context.Identity != null) + { + var 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) return context.IsRequestCompleted; + var 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; + } + + private static void EnsureAcceptedNetwork(string[] validNetworks, string userNetwork) + { + if (validNetworks == null || validNetworks.Length <= 0) return; + var isValid = validNetworks.Any(network => userNetwork == network); + if (!isValid) throw new Exception("User is not in list of accepted networks"); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/Yammer/YammerAuthenticationMiddleware.cs b/src/Owin.Security.Providers.Yammer/YammerAuthenticationMiddleware.cs similarity index 77% rename from Owin.Security.Providers/Yammer/YammerAuthenticationMiddleware.cs rename to src/Owin.Security.Providers.Yammer/YammerAuthenticationMiddleware.cs index cd5a110..4d69269 100644 --- a/Owin.Security.Providers/Yammer/YammerAuthenticationMiddleware.cs +++ b/src/Owin.Security.Providers.Yammer/YammerAuthenticationMiddleware.cs @@ -14,33 +14,33 @@ namespace Owin.Security.Providers.Yammer { public class YammerAuthenticationMiddleware : AuthenticationMiddleware { - private readonly HttpClient httpClient; - private readonly ILogger logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; public YammerAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, YammerAuthenticationOptions options) : base(next, options) { - if (String.IsNullOrWhiteSpace(Options.ClientId)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientId")); - if (String.IsNullOrWhiteSpace(Options.ClientSecret)) - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientSecret")); + if (string.IsNullOrWhiteSpace(Options.ClientId)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientId")); + if (string.IsNullOrWhiteSpace(Options.ClientSecret)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Option must be provided {0}", "ClientSecret")); - logger = app.CreateLogger(); + _logger = app.CreateLogger(); if (Options.Provider == null) Options.Provider = new YammerAuthenticationProvider(); if (Options.StateDataFormat == null) { - IDataProtector dataProtector = app.CreateDataProtector( + var dataProtector = app.CreateDataProtector( typeof(YammerAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1"); Options.StateDataFormat = new PropertiesDataFormat(dataProtector); } - if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType)) Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); - httpClient = new HttpClient(ResolveHttpMessageHandler()) + _httpClient = new HttpClient(ResolveHttpMessageHandler()) { Timeout = Options.BackchannelTimeout, MaxResponseContentBufferSize = 1024 * 1024 * 10 @@ -57,7 +57,7 @@ namespace Owin.Security.Providers.Yammer /// protected override AuthenticationHandler CreateHandler() { - return new YammerAuthenticationHandler(httpClient, logger); + return new YammerAuthenticationHandler(_httpClient, _logger); } private HttpClientHandler ResolveHttpMessageHandler() diff --git a/Owin.Security.Providers/Yammer/YammerAuthenticationOptions.cs b/src/Owin.Security.Providers.Yammer/YammerAuthenticationOptions.cs similarity index 100% rename from Owin.Security.Providers/Yammer/YammerAuthenticationOptions.cs rename to src/Owin.Security.Providers.Yammer/YammerAuthenticationOptions.cs diff --git a/src/Owin.Security.Providers.Yammer/packages.config b/src/Owin.Security.Providers.Yammer/packages.config new file mode 100644 index 0000000..a35e97b --- /dev/null +++ b/src/Owin.Security.Providers.Yammer/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file