Compare commits

..

3 Commits

Author SHA1 Message Date
ByteBlast
53024cd531 Release version 1.17-pre 2015-04-07 19:32:09 +01:00
ByteBlast
4e3d613e16 Merge branch 'jlcj1974-master' into LinkedIn-Fix 2015-04-07 19:25:34 +01:00
jlcj1974
b0e3eba0c1 Overrides ExpectContinue for LinkedIn HTTP API requests 2015-04-07 11:53:54 -05:00
898 changed files with 9261 additions and 52836 deletions

79
.gitignore vendored
View File

@@ -1,3 +1,34 @@
#################
## 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
#################
@@ -11,25 +42,18 @@
*.sln.docstates
# Build results
.gitignore/
.bundle/
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
OwinOAuthProvidersDemo/App_Data/
# Visual Studo 2015 cache/options directory
.vs/
Owin.Security.Providers.nuspec
Owin.Security.Providers*.nupkg
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
src/**/*.nuspec
src/**/*.nupkg
tools/
*_i.c
*_p.c
*.ilk
@@ -70,9 +94,6 @@ ipch/
# Guidance Automation Toolkit
*.gpState
# CodeRush is a .NET coding add-in
.cr/
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
@@ -164,4 +185,34 @@ $RECYCLE.BIN/
# Mac crap
.DS_Store
/Output
#############
## 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

View File

@@ -1,4 +0,0 @@
source 'http://rubygems.org'
gem 'os'
gem 'albacore'
gem 'nokogiri'

View File

@@ -1,31 +0,0 @@
GEM
remote: http://rubygems.org/
specs:
albacore (3.0.1)
map (~> 6.5)
nokogiri (~> 1.5)
rake (~> 10)
semver2 (~> 3.4)
map (6.6.0)
mini_portile2 (2.8.0)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.6-x64-mingw32)
racc (~> 1.4)
os (1.0.0)
racc (1.6.0)
rake (10.5.0)
semver2 (3.4.2)
PLATFORMS
ruby
x64-mingw32
DEPENDENCIES
albacore
nokogiri
os
BUNDLED WITH
1.13.7

View File

@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Owin.Security.Providers</id>
<version>1.17.0-pre</version>
<authors>Jerrie Pelser and contributors</authors>
<owners>Jerrie Pelser</owners>
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/owin-middleware/OwinOAuthProviders</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>
Adds additional OAuth providers for OWIN to use with ASP.NET
</description>
<summary>
Additional OAuth providers for Katana (OWIN). Includes providers for LinkedIn, Yahoo, Google+, GitHub, Reddit, Instagram, StackExchange, SalesForce, TripIt, Buffer, ArcGIS, Dropbox, Wordpress, Battle.NET, Twitch and Yammer
Also adds generic OpenID 2.0 providers as well as a Steam-specific implementatio
</summary>
<releaseNotes>
Version 1.17-pre
Temporary fix for broken LinkedIn provider.
</releaseNotes>
<copyright>Copyright 2013, 2014</copyright>
<tags>owin katana oauth LinkedIn Yahoo Google+ GitHub Reddit Instagram StackExchange SalesForce TripIt Buffer ArcGIS Dropbox Wordpress Battle.NET Yammer OpenID Steam Twitch</tags>
<dependencies>
<dependency id="Microsoft.Owin.Security" version="2.1.0" />
<dependency id="Newtonsoft.Json" version="6.0.1" />
</dependencies>
</metadata>
<files>
<file src="..\Owin.Security.Providers\bin\Release\Owin.Security.Providers.dll" target="lib\net45" />
</files>
</package>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.ArcGISOnline
ArcGISOnlineAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(ArcGISOnlineAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,218 @@
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<ArcGISOnlineAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
IReadableStringCollection query = Request.Query;
IList<string> values = query.GetValues("code");
if (values != null && values.Count == 1)
{
code = values[0];
}
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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
// 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<dynamic>(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<Owin.Security.Providers.ArcGISOnline.Provider.ArcGISOnlineUser>(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 = new AuthenticationProperties
{
RedirectUri = baseUri +
"/Account/ExternalLoginCallback"
};
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<object>(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;
// 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);
Response.Redirect(authorizationEndpoint);
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -7,48 +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.ArcGISOnline
{
public class ArcGISOnlineAuthenticationMiddleware : AuthenticationMiddleware<ArcGISOnlineAuthenticationOptions>
{
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<ArcGISOnlineAuthenticationMiddleware>();
logger = app.CreateLogger<ArcGISOnlineAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new ArcGISOnlineAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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;
}
/// <summary>
@@ -61,22 +62,24 @@ namespace Owin.Security.Providers.ArcGISOnline
/// </returns>
protected override AuthenticationHandler<ArcGISOnlineAuthenticationOptions> CreateHandler()
{
return new ArcGISOnlineAuthenticationHandler(_httpClient, _logger);
return new ArcGISOnlineAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(ArcGISOnlineAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(ArcGISOnlineAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -14,7 +14,7 @@ namespace Owin.Security.Providers.ArcGISOnline
/// Endpoint which is used to redirect users to request ArcGISOnline access
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/oauth2/authorize/
/// Defaults to https://www.arcgis.com/sharing/oauth2/authorize
/// </remarks>
public string AuthorizationEndpoint { get; set; }
@@ -22,7 +22,7 @@ namespace Owin.Security.Providers.ArcGISOnline
/// Endpoint which is used to exchange code for access token
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/oauth2/token/
/// Defaults to https://www.arcgis.com/sharing/oauth2/token
/// </remarks>
public string TokenEndpoint { get; set; }
@@ -30,14 +30,14 @@ namespace Owin.Security.Providers.ArcGISOnline
/// Endpoint which is used to obtain user information after authentication
/// </summary>
/// <remarks>
/// Defaults to https://www.arcgis.com/sharing/rest/community/self
/// Defaults to https://www.arcgis.com/sharing/rest/accounts/self
/// </remarks>
public string UserInfoEndpoint { get; set; }
}
private const string AuthorizationEndPoint = "https://www.arcgis.com/sharing/rest/oauth2/authorize/";
private const string TokenEndpoint = "https://www.arcgis.com/sharing/rest/oauth2/token/";
private const string UserInfoEndpoint = "https://www.arcgis.com/sharing/rest/community/self";
private const string AuthorizationEndPoint = "https://www.arcgis.com/sharing/oauth2/authorize";
private const string TokenEndpoint = "https://www.arcgis.com/sharing/oauth2/token";
private const string UserInfoEndpoint = "https://www.arcgis.com/sharing/rest/accounts/self";
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
@@ -123,7 +123,8 @@ namespace Owin.Security.Providers.ArcGISOnline
/// <summary>
/// Initializes a new <see cref="ArcGISOnlineAuthenticationOptions" />
/// </summary>
public ArcGISOnlineAuthenticationOptions() : base("ArcGIS Online")
public ArcGISOnlineAuthenticationOptions()
: base("ArcGIS Online")
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-arcgis-online");

View File

@@ -2,6 +2,6 @@
{
internal static class Constants
{
internal const string DefaultAuthenticationType = "ArcGIS Online";
public const string DefaultAuthenticationType = "ArcGIS Online";
}
}

View File

@@ -1,9 +1,13 @@
// 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
@@ -18,34 +22,28 @@ namespace Owin.Security.Providers.ArcGISOnline
/// </summary>
/// <param name="context">The OWIN environment</param>
/// <param name="user">The ArcGIS Online user</param>
/// <param name="accessToken">ArcGIS Online Access token</param>
/// <param name="refreshToken">ArcGIS Online Refresh token</param>
public ArcGISOnlineAuthenticatedContext(IOwinContext context, ArcGISOnlineUser user, string accessToken, string refreshToken)
/// <param name="accessToken">ArcGISOnline Access token</param>
public ArcGISOnlineAuthenticatedContext(IOwinContext context, ArcGISOnlineUser user, string accessToken)
: base(context)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
Id = user.Username;
Name = user.FullName;
Id = user.user.username;
Name = user.user.fullName;
Link = "https://www.arcgis.com/sharing/rest/community/users/" + Id;
UserName = Id;
Email = user.Email;
Email = user.user.email;
}
/// <summary>
/// Gets the ArcGIS Online access token
/// Gets the ArcGISOnline access token
/// </summary>
public string AccessToken { get; private set; }
/// <summary>
/// Gets the ArcGIS Online refresh token
/// Gets the ArcGISOnline user ID
/// </summary>
public string RefreshToken { get; private set; }
/// <summary>
/// Gets the ArcGIS Online user ID
/// </summary>
public string Id { get; }
public string Id { get; private set; }
/// <summary>
/// Gets the user's name
@@ -60,7 +58,7 @@ namespace Owin.Security.Providers.ArcGISOnline
public string Link { get; private set; }
/// <summary>
/// Gets the ArcGIS Online username
/// Gets the ArcGISOnline username
/// </summary>
public string UserName { get; private set; }

View File

@@ -28,7 +28,7 @@ namespace Owin.Security.Providers.ArcGISOnline
public Func<ArcGISOnlineReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever ArcGISOnline successfully authenticates a user
/// Invoked whenever ArcGISOnline succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -0,0 +1,16 @@
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; }
}
}

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.ArcGISOnline
public interface IArcGISOnlineAuthenticationProvider
{
/// <summary>
/// Invoked whenever ArcGISOnline successfully authenticates a user
/// Invoked whenever ArcGISOnline succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Asana
AsanaAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(AsanaAuthenticationMiddleware), app, options);

View File

@@ -4,11 +4,13 @@ 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
{
@@ -16,13 +18,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)
{
_httpClient = httpClient;
_logger = logger;
this.httpClient = httpClient;
this.logger = logger;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
@@ -34,8 +36,8 @@ namespace Owin.Security.Providers.Asana
string code = null;
string state = null;
var query = Request.Query;
var values = query.GetValues("code");
IReadableStringCollection query = Request.Query;
IList<string> values = query.GetValues("code");
if (values != null && values.Count == 1)
{
code = values[0];
@@ -53,23 +55,21 @@ namespace Owin.Security.Providers.Asana
}
// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, _logger))
if (!ValidateCorrelationId(properties, logger))
{
return new AuthenticationTicket(null, properties);
}
var requestPrefix = Request.Scheme + "://" + Request.Host;
var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
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<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("code", code)
};
var body = new List<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("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);
var tokenResponse = await _httpClient.SendAsync(requestMessage);
HttpResponseMessage tokenResponse = await httpClient.SendAsync(requestMessage);
tokenResponse.EnsureSuccessStatusCode();
var text = await tokenResponse.Content.ReadAsStringAsync();
string text = await tokenResponse.Content.ReadAsStringAsync();
// Deserializes the token response
dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
var accessToken = (string)response.access_token;
string accessToken = (string)response.access_token;
/*
* In the response, you will receive a JSON payload with the following parameters:
@@ -102,13 +102,11 @@ namespace Owin.Security.Providers.Asana
// Get the Asana user
var context = new AsanaAuthenticatedContext(Context, response.data, accessToken)
{
Identity = new ClaimsIdentity(
Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType)
};
var context = new AsanaAuthenticatedContext(Context, response.data, 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));
@@ -129,7 +127,7 @@ namespace Owin.Security.Providers.Asana
}
catch (Exception ex)
{
_logger.WriteError(ex.Message);
logger.WriteError(ex.Message);
}
return new AuthenticationTicket(null, properties);
}
@@ -141,44 +139,45 @@ namespace Owin.Security.Providers.Asana
return Task.FromResult<object>(null);
}
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge == null) return Task.FromResult<object>(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))
if (challenge != null)
{
properties.RedirectUri = currentUri;
}
string baseUri =
Request.Scheme +
Uri.SchemeDelimiter +
Request.Host +
Request.PathBase;
// OAuth2 10.12 CSRF
GenerateCorrelationId(properties);
string currentUri =
baseUri +
Request.Path +
Request.QueryString;
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)
;
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)
;
/*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.
@@ -186,7 +185,8 @@ 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<object>(null);
}
@@ -198,47 +198,50 @@ namespace Owin.Security.Providers.Asana
private async Task<bool> InvokeReplyPathAsync()
{
if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false;
// TODO: error responses
var ticket = await AuthenticateAsync();
if (ticket == null)
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
// TODO: error responses
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))
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket == null)
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
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();
var context = new AsanaReturnEndpointContext(Context, ticket);
context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType;
context.RedirectUri = ticket.Properties.RedirectUri;
return context.IsRequestCompleted;
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;
}
}
}

View File

@@ -7,48 +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.Asana
{
public class AsanaAuthenticationMiddleware : AuthenticationMiddleware<AsanaAuthenticationOptions>
{
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<AsanaAuthenticationMiddleware>();
logger = app.CreateLogger<AsanaAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new AsanaAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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;
}
/// <summary>
@@ -61,22 +62,24 @@ namespace Owin.Security.Providers.Asana
/// </returns>
protected override AuthenticationHandler<AsanaAuthenticationOptions> CreateHandler()
{
return new AsanaAuthenticationHandler(_httpClient, _logger);
return new AsanaAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(AsanaAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(AsanaAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;

View File

@@ -1,5 +1,7 @@
// 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;

View File

@@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Asana
public Func<AsanaReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Asana successfully authenticates a user
/// Invoked whenever Asana succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -16,7 +16,10 @@ namespace Owin.Security.Providers.Asana
/// </summary>
/// <param name="context">OWIN environment</param>
/// <param name="ticket">The authentication ticket</param>
public AsanaReturnEndpointContext(IOwinContext context,AuthenticationTicket ticket) : base(context, ticket)
public AsanaReturnEndpointContext(
IOwinContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Asana
public interface IAsanaAuthenticationProvider
{
/// <summary>
/// Invoked whenever Asana successfully authenticates a user
/// Invoked whenever Asana succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -22,7 +22,7 @@ namespace Owin.Security.Providers.BattleNet
{
ClientId = clientId,
ClientSecret = clientSecret,
Region = Region.Us
Region = Region.US
});
}
}

View File

@@ -0,0 +1,288 @@
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<BattleNetAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private readonly string _tokenEndpoint = "https://eu.battle.net/oauth/token";
private readonly string _accountUserIdEndpoint = "https://eu.api.battle.net/account/user/id";
private readonly string _accountUserBattleTagEndpoint = "https://eu.api.battle.net/account/user/battletag";
private readonly 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;
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;
}
}
protected override async Task<AuthenticationTicket> 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<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", redirectUri),
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -7,6 +7,7 @@ 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
{
@@ -18,11 +19,11 @@ namespace Owin.Security.Providers.BattleNet
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<BattleNetAuthenticationMiddleware>();
@@ -38,7 +39,7 @@ 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))
@@ -63,19 +64,21 @@ namespace Owin.Security.Providers.BattleNet
private static HttpMessageHandler ResolveHttpMessageHandler(BattleNetAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
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;
return handler;
}
}
}

View File

@@ -9,7 +9,7 @@ namespace Owin.Security.Providers.BattleNet
public enum Region
{
Europe,
Us,
US,
Korea,
Taiwan,
China

View File

@@ -31,7 +31,7 @@ namespace Owin.Security.Providers.BattleNet
AccessToken = accessToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}

View File

@@ -5,7 +5,7 @@ namespace Owin.Security.Providers.BattleNet
public interface IBattleNetAuthenticationProvider
{
/// <summary>
/// Invoked whenever Battle.net successfully authenticates a user
/// Invoked whenever Battle.net succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Buffer
BufferAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(BufferAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,222 @@
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<BufferAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -7,42 +7,43 @@ 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<BufferAuthenticationOptions>
{
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<BufferAuthenticationMiddleware>();
logger = app.CreateLogger<BufferAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new BufferAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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
@@ -59,22 +60,24 @@ namespace Owin.Security.Providers.Buffer
/// </returns>
protected override AuthenticationHandler<BufferAuthenticationOptions> CreateHandler()
{
return new BufferAuthenticationHandler(_httpClient, _logger);
return new BufferAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(BufferAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(BufferAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -31,7 +31,7 @@ namespace Owin.Security.Providers.Buffer
AccessToken = accessToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}

View File

@@ -28,7 +28,7 @@ namespace Owin.Security.Providers.Buffer
public Func<BufferReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Buffer successfully authenticates a user
/// Invoked whenever Buffer succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Buffer
public interface IBufferAuthenticationProvider
{
/// <summary>
/// Invoked whenever Buffer successfully authenticates a user
/// Invoked whenever Buffer succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Dropbox
DropboxAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(DropboxAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,229 @@
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<DropboxAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.AppKey));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -7,42 +7,43 @@ 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<DropboxAuthenticationOptions>
{
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<DropboxAuthenticationMiddleware>();
logger = app.CreateLogger<DropboxAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new DropboxAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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
@@ -59,22 +60,24 @@ namespace Owin.Security.Providers.Dropbox
/// </returns>
protected override AuthenticationHandler<DropboxAuthenticationOptions> CreateHandler()
{
return new DropboxAuthenticationHandler(_httpClient, _logger);
return new DropboxAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(DropboxAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(DropboxAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -1,5 +1,7 @@
// 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;
@@ -25,7 +27,7 @@ namespace Owin.Security.Providers.Dropbox
AccessToken = accessToken;
User = user;
Id = TryGetValue(user, "account_id");
Id = TryGetValue(user, "uid");
Name = TryGetValue(user, "display_name");
}

View File

@@ -1,4 +1,4 @@
namespace Owin.Security.Providers.EVEOnline
namespace Owin.Security.Providers.EveOnline
{
internal static class Constants
{

View File

@@ -1,6 +1,6 @@
using System;
namespace Owin.Security.Providers.EVEOnline
namespace Owin.Security.Providers.EveOnline
{
public static class EveOnlineAuthenticationExtensions
{

View File

@@ -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<EveOnlineAuthenticationOptions>
{
@@ -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,18 +38,16 @@ 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";
});
}
@@ -183,46 +181,48 @@ namespace Owin.Security.Providers.EVEOnline
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge == null) return Task.FromResult<object>(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))
if (challenge != null)
{
properties.RedirectUri = currentUri;
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);
}
// 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<object>(null);
}
@@ -233,47 +233,52 @@ namespace Owin.Security.Providers.EVEOnline
private async Task<bool> InvokeReplyPathAsync()
{
if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false;
// TODO: error responses
var ticket = await AuthenticateAsync();
if (ticket == null)
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
// TODO: error responses
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))
var ticket = await AuthenticateAsync();
if (ticket == null)
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
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();
var context = new EveOnlineReturnEndpointContext(Context, ticket)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = ticket.Properties.RedirectUri
};
return context.IsRequestCompleted;
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;
}
}
}

View File

@@ -7,8 +7,9 @@ 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<EveOnlineAuthenticationOptions>
{
@@ -18,11 +19,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<EveOnlineAuthenticationMiddleware>();
@@ -38,7 +39,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))
@@ -54,7 +55,7 @@ namespace Owin.Security.Providers.EVEOnline
/// </summary>
/// <returns>
/// An <see cref="T:Microsoft.Owin.Security.Infrastructure.AuthenticationHandler" /> configured with the
/// <see cref="T:Owin.Security.Providers.EVEOnline.EVeOnlineAuthenticationOptions" /> supplied to the constructor.
/// <see cref="T:Owin.Security.Providers.EveOnline.EVeOnlineAuthenticationOptions" /> supplied to the constructor.
/// </returns>
protected override AuthenticationHandler<EveOnlineAuthenticationOptions> CreateHandler()
{
@@ -63,17 +64,19 @@ namespace Owin.Security.Providers.EVEOnline
private static HttpMessageHandler ResolveHttpMessageHandler(EveOnlineAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -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
{

View File

@@ -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
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
@@ -22,7 +22,6 @@ namespace Owin.Security.Providers.EVEOnline
/// <param name="characterData">The JSON-serialized userId</param>
///
/// <param name="accessToken">EveOnline Access token</param>
/// <param name="refreshToken"></param>
/// <param name="expires">Seconds until expiration</param>
public EveOnlineAuthenticatedContext(IOwinContext context, JObject characterData, string accessToken, string refreshToken, string expires)
: base(context)
@@ -32,7 +31,7 @@ namespace Owin.Security.Providers.EVEOnline
RefreshToken = refreshToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}

View File

@@ -2,7 +2,7 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.EVEOnline
namespace Owin.Security.Providers.EveOnline
{
/// <summary>
/// Default <see cref="IEveOnlineAuthenticationProvider"/> implementation.

View File

@@ -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
{
/// <summary>
/// Provides context information to middleware providers.

View File

@@ -1,11 +1,11 @@
using System.Threading.Tasks;
namespace Owin.Security.Providers.EVEOnline
namespace Owin.Security.Providers.EveOnline
{
public interface IEveOnlineAuthenticationProvider
{
/// <summary>
/// Invoked whenever Battle.net successfully authenticates a user
/// Invoked whenever Battle.net succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,12 +8,12 @@ namespace Owin.Security.Providers.Foursquare
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
}
return app.Use(typeof(FoursquareAuthenticationMiddleware), app, options);

View File

@@ -27,15 +27,15 @@ namespace Owin.Security.Providers.Foursquare
public FoursquareAuthenticationHandler(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_logger = logger;
this._httpClient = httpClient;
this._logger = logger;
}
public override async Task<bool> InvokeAsync()
{
if ((string.IsNullOrEmpty(Options.CallbackPath) == false) && (Options.CallbackPath == Request.Path.ToString()))
if ((string.IsNullOrEmpty(this.Options.CallbackPath) == false) && (this.Options.CallbackPath == this.Request.Path.ToString()))
{
return await InvokeReturnPathAsync();
return await this.InvokeReturnPathAsync();
}
return false;
@@ -43,7 +43,7 @@ namespace Owin.Security.Providers.Foursquare
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
_logger.WriteVerbose("AuthenticateCore");
this._logger.WriteVerbose("AuthenticateCore");
AuthenticationProperties properties = null;
@@ -52,7 +52,7 @@ namespace Owin.Security.Providers.Foursquare
string code = null;
string state = null;
var query = Request.Query;
var query = this.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 = Options.StateDataFormat.Unprotect(state);
properties = this.Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
@@ -75,23 +75,23 @@ namespace Owin.Security.Providers.Foursquare
}
// OAuth2 10.12 CSRF
if (ValidateCorrelationId(properties, _logger) == false)
if (this.ValidateCorrelationId(properties, this._logger) == false)
{
return new AuthenticationTicket(null, properties);
}
var tokenRequestParameters = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("client_id", Options.ClientId),
new KeyValuePair<string, string>("client_secret", Options.ClientSecret),
new KeyValuePair<string, string>("client_id", this.Options.ClientId),
new KeyValuePair<string, string>("client_secret", this.Options.ClientSecret),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("redirect_uri", GenerateRedirectUri()),
new KeyValuePair<string, string>("redirect_uri", this.GenerateRedirectUri()),
new KeyValuePair<string, string>("code", code),
};
var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
var response = await _httpClient.PostAsync(TokenEndpoint, requestContent, Request.CallCancelled);
var response = await this._httpClient.PostAsync(TokenEndpoint, requestContent, this.Request.CallCancelled);
response.EnsureSuccessStatusCode();
var oauthTokenResponse = await response.Content.ReadAsStringAsync();
@@ -99,45 +99,44 @@ namespace Owin.Security.Providers.Foursquare
var oauth2Token = JObject.Parse(oauthTokenResponse);
var accessToken = oauth2Token["access_token"].Value<string>();
if (string.IsNullOrWhiteSpace(accessToken))
if (string.IsNullOrWhiteSpace(accessToken) == true)
{
_logger.WriteWarning("Access token was not found");
this._logger.WriteWarning("Access token was not found");
return new AuthenticationTicket(null, properties);
}
// ReSharper disable once StringLiteralTypo
var graphResponse = await _httpClient.GetAsync(GraphApiEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken) + "&m=foursquare&v=" + VersionDate.ToString("yyyyyMMdd"), Request.CallCancelled);
var graphResponse = await this._httpClient.GetAsync(GraphApiEndpoint + "?oauth_token=" + Uri.EscapeDataString(accessToken) + "&m=foursquare&v=" + VersionDate.ToString("yyyyyMMdd"), this.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(Context, user, accessToken);
var context = new FoursquareAuthenticatedContext(this.Context, user, accessToken);
context.Identity = new ClaimsIdentity(
new[]
{
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),
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),
},
Options.AuthenticationType,
this.Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if (string.IsNullOrWhiteSpace(context.Email) == false)
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, this.Options.AuthenticationType));
}
if (string.IsNullOrWhiteSpace(context.Twitter) == false)
{
context.Identity.AddClaim(new Claim("urn:foursquare:twitter", context.Twitter, XmlSchemaString, Options.AuthenticationType));
context.Identity.AddClaim(new Claim("urn:foursquare:twitter", context.Twitter, XmlSchemaString, this.Options.AuthenticationType));
}
await Options.Provider.Authenticated(context);
await this.Options.Provider.Authenticated(context);
context.Properties = properties;
@@ -145,65 +144,65 @@ namespace Owin.Security.Providers.Foursquare
}
catch (Exception ex)
{
_logger.WriteWarning("Authentication failed", ex);
this._logger.WriteWarning("Authentication failed", ex);
return new AuthenticationTicket(null, properties);
}
}
protected override Task ApplyResponseChallengeAsync()
{
_logger.WriteVerbose("ApplyResponseChallenge");
this._logger.WriteVerbose("ApplyResponseChallenge");
if (Response.StatusCode != (int)HttpStatusCode.Unauthorized)
if (this.Response.StatusCode != (int)HttpStatusCode.Unauthorized)
{
return Task.FromResult<object>(null);
return Task.FromResult<Object>(null);
}
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
var challenge = Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode);
if (challenge == null) return Task.FromResult<object>(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))
if (challenge != null)
{
extra.RedirectUri = currentUri;
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);
}
// 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<object>(null);
return Task.FromResult<Object>(null);
}
public async Task<bool> InvokeReturnPathAsync()
{
_logger.WriteVerbose("InvokeReturnPath");
this._logger.WriteVerbose("InvokeReturnPath");
var model = await AuthenticateAsync();
var model = await this.AuthenticateAsync();
var context = new FoursquareReturnEndpointContext(Context, model)
{
SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
RedirectUri = model.Properties.RedirectUri
};
var context = new FoursquareReturnEndpointContext(Context, model);
context.SignInAsAuthenticationType = this.Options.SignInAsAuthenticationType;
context.RedirectUri = model.Properties.RedirectUri;
model.Properties.RedirectUri = null;
await Options.Provider.ReturnEndpoint(context);
await this.Options.Provider.ReturnEndpoint(context);
if ((context.SignInAsAuthenticationType != null) && (context.Identity != null))
{
@@ -214,27 +213,28 @@ namespace Owin.Security.Providers.Foursquare
signInIdentity = new ClaimsIdentity(signInIdentity.Claims, context.SignInAsAuthenticationType, signInIdentity.NameClaimType, signInIdentity.RoleClaimType);
}
Context.Authentication.SignIn(context.Properties, signInIdentity);
this.Context.Authentication.SignIn(context.Properties, signInIdentity);
}
if (context.IsRequestCompleted || (context.RedirectUri == null))
return context.IsRequestCompleted;
if (context.Identity == null)
if ((context.IsRequestCompleted == false) && (context.RedirectUri != null))
{
context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied");
if (context.Identity == null)
{
context.RedirectUri = WebUtilities.AddQueryString(context.RedirectUri, "error", "access_denied");
}
this.Response.Redirect(context.RedirectUri);
context.RequestCompleted();
}
Response.Redirect(context.RedirectUri);
context.RequestCompleted();
return context.IsRequestCompleted;
}
private string GenerateRedirectUri()
{
var requestPrefix = Request.Scheme + "://" + Request.Host;
var redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath;
var requestPrefix = this.Request.Scheme + "://" + this.Request.Host;
var redirectUri = requestPrefix + this.RequestPathBase + this.Options.CallbackPath;
return redirectUri;
}
}

View File

@@ -18,40 +18,37 @@ namespace Owin.Security.Providers.Foursquare
public FoursquareAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, FoursquareAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.ClientId))
if (string.IsNullOrWhiteSpace(this.Options.ClientId) == true)
{
throw new ArgumentException("The 'ClientId' must be provided.");
}
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
if (string.IsNullOrWhiteSpace(this.Options.ClientSecret) == true)
{
throw new ArgumentException("The 'ClientSecret' option must be provided.");
}
_logger = app.CreateLogger<FoursquareAuthenticationMiddleware>();
this._logger = app.CreateLogger<FoursquareAuthenticationMiddleware>();
if (Options.Provider == null)
if (this.Options.Provider == null)
{
Options.Provider = new FoursquareAuthenticationProvider();
this.Options.Provider = new FoursquareAuthenticationProvider();
}
if (Options.StateDataFormat == null)
if (this.Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(typeof(FoursquareAuthenticationMiddleware).FullName, Options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
var dataProtector = app.CreateDataProtector(typeof(FoursquareAuthenticationMiddleware).FullName, this.Options.AuthenticationType, "v1");
this.Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
if (string.IsNullOrEmpty(this.Options.SignInAsAuthenticationType) == true)
{
Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
this.Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
{
Timeout = Options.BackchannelTimeout,
MaxResponseContentBufferSize = 1024*1024*10
};
// 10 MB
this._httpClient = new HttpClient(ResolveHttpMessageHandler(this.Options));
this._httpClient.Timeout = this.Options.BackchannelTimeout;
this._httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
}
/// <summary>
@@ -64,7 +61,7 @@ namespace Owin.Security.Providers.Foursquare
/// </returns>
protected override AuthenticationHandler<FoursquareAuthenticationOptions> CreateHandler()
{
return new FoursquareAuthenticationHandler(_httpClient, _logger);
return new FoursquareAuthenticationHandler(this._httpClient, this._logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(FoursquareAuthenticationOptions options)
@@ -72,16 +69,18 @@ namespace Owin.Security.Providers.Foursquare
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)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException("Validator Handler Mismatch");
}
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
if (webRequestHandler == null)
{
throw new InvalidOperationException("Validator Handler Mismatch");
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
return handler;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin.Security;
using Owin.Security.Providers.Foursquare.Provider;
@@ -13,10 +14,10 @@ namespace Owin.Security.Providers.Foursquare
public FoursquareAuthenticationOptions()
: base(Constants.DefaultAuthenticationType)
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = "/signin-foursquare";
AuthenticationMode = AuthenticationMode.Passive;
BackchannelTimeout = TimeSpan.FromSeconds(60);
this.Caption = Constants.DefaultAuthenticationType;
this.CallbackPath = "/signin-foursquare";
this.AuthenticationMode = AuthenticationMode.Passive;
this.BackchannelTimeout = TimeSpan.FromSeconds(60);
}
/// <summary>
@@ -85,8 +86,8 @@ namespace Owin.Security.Providers.Foursquare
/// </summary>
public string Caption
{
get { return Description.Caption; }
set { Description.Caption = value; }
get { return this.Description.Caption; }
set { this.Description.Caption = value; }
}
}
}

View File

@@ -23,39 +23,39 @@ namespace Owin.Security.Providers.Foursquare.Provider
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
throw new ArgumentNullException("user");
}
User = user;
AccessToken = accessToken;
this.User = user;
this.AccessToken = accessToken;
var userId = User["id"];
var userId = this.User["id"];
if (userId == null)
{
throw new ArgumentException("The user does not have an id.", nameof(user));
throw new ArgumentException("The user does not have an id.", "user");
}
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;
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;
}
/// <summary>
@@ -64,7 +64,7 @@ namespace Owin.Security.Providers.Foursquare.Provider
/// <remarks>
/// Contains the Foursquare user obtained from the User Info endpoint https://api.foursquare.com/v2/users/self
/// </remarks>
public JObject User { get; }
public JObject User { get; private set; }
/// <summary>
/// Gets the Foursquare access token
/// </summary>
@@ -72,15 +72,15 @@ namespace Owin.Security.Providers.Foursquare.Provider
/// <summary>
/// Gets the Foursquare user ID
/// </summary>
public string Id { get; }
public string Id { get; private set; }
/// <summary>
/// Gets the user's first name
/// </summary>
public string FirstName { get; }
public string FirstName { get; private set; }
/// <summary>
/// Gets the user's last name
/// </summary>
public string LastName { get; }
public string LastName { get; private set; }
/// <summary>
/// Gets the user's full name
/// </summary>
@@ -108,7 +108,7 @@ namespace Owin.Security.Providers.Foursquare.Provider
/// <summary>
/// Gets the user's contact
/// </summary>
public JObject Contact { get; }
public JObject Contact { get; private set; }
/// <summary>
/// Gets the user's phone
/// </summary>

View File

@@ -13,8 +13,8 @@ namespace Owin.Security.Providers.Foursquare.Provider
/// </summary>
public FoursquareAuthenticationProvider()
{
OnAuthenticated = context => Task.FromResult<object>(null);
OnReturnEndpoint = context => Task.FromResult<object>(null);
this.OnAuthenticated = context => Task.FromResult<object>(null);
this.OnReturnEndpoint = context => Task.FromResult<object>(null);
}
/// <summary>
@@ -28,13 +28,13 @@ namespace Owin.Security.Providers.Foursquare.Provider
public Func<FoursquareReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Foursquare successfully authenticates a user
/// Invoked whenever Foursquare succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(FoursquareAuthenticatedContext context)
{
return OnAuthenticated(context);
return this.OnAuthenticated(context);
}
/// <summary>
@@ -44,7 +44,7 @@ namespace Owin.Security.Providers.Foursquare.Provider
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(FoursquareReturnEndpointContext context)
{
return OnReturnEndpoint(context);
return this.OnReturnEndpoint(context);
}
}
}

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.Foursquare.Provider
public interface IFoursquareAuthenticationProvider
{
/// <summary>
/// Invoked whenever Foursquare successfully authenticates a user
/// Invoked whenever Foursquare succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.GitHub
GitHubAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(GitHubAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,237 @@
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<GitHubAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -7,48 +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.GitHub
{
public class GitHubAuthenticationMiddleware : AuthenticationMiddleware<GitHubAuthenticationOptions>
{
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<GitHubAuthenticationMiddleware>();
logger = app.CreateLogger<GitHubAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new GitHubAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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;
}
/// <summary>
@@ -61,22 +62,24 @@ namespace Owin.Security.Providers.GitHub
/// </returns>
protected override AuthenticationHandler<GitHubAuthenticationOptions> CreateHandler()
{
return new GitHubAuthenticationHandler(_httpClient, _logger);
return new GitHubAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(GitHubAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(GitHubAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -1,5 +1,7 @@
// 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;

View File

@@ -28,7 +28,7 @@ namespace Owin.Security.Providers.GitHub
public Func<GitHubReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever GitHub successfully authenticates a user
/// Invoked whenever GitHub succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.GitHub
public interface IGitHubAuthenticationProvider
{
/// <summary>
/// Invoked whenever GitHub successfully authenticates a user
/// Invoked whenever GitHub succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.GooglePlus
GooglePlusAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(GooglePlusAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,259 @@
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<GooglePlusAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -8,42 +8,43 @@ 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<GooglePlusAuthenticationOptions>
{
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<GooglePlusAuthenticationMiddleware>();
logger = app.CreateLogger<GooglePlusAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new GooglePlusAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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
@@ -60,22 +61,24 @@ namespace Owin.Security.Providers.GooglePlus
/// </returns>
protected override AuthenticationHandler<GooglePlusAuthenticationOptions> CreateHandler()
{
return new GooglePlusAuthenticationHandler(_httpClient, _logger);
return new GooglePlusAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(GooglePlusAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(GooglePlusAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -24,7 +24,6 @@ namespace Owin.Security.Providers.GooglePlus.Provider
/// <param name="person"></param>
/// <param name="accessToken">Google+ Access token</param>
/// <param name="expires">Seconds until expiration</param>
/// <param name="refreshToken"></param>
public GooglePlusAuthenticatedContext(IOwinContext context, JObject user, JObject person, string accessToken, string expires, string refreshToken)
: base(context)
{
@@ -34,7 +33,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider
RefreshToken = refreshToken;
int expiresValue;
if (int.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}

View File

@@ -28,7 +28,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider
public Func<GooglePlusReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Invoked whenever Google+ successfully authenticates a user
/// Invoked whenever Google+ succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -8,7 +8,7 @@ namespace Owin.Security.Providers.GooglePlus.Provider
public interface IGooglePlusAuthenticationProvider
{
/// <summary>
/// Invoked whenever Google+ successfully authenticates a user
/// Invoked whenever Google+ succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>

View File

@@ -9,9 +9,9 @@ namespace Owin.Security.Providers.HealthGraph
HealthGraphAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(HealthGraphAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,234 @@
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<HealthGraphAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -8,70 +8,73 @@ 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<HealthGraphAuthenticationOptions>
{
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<HealthGraphAuthenticationMiddleware>();
logger = app.CreateLogger<HealthGraphAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new HealthGraphAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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<HealthGraphAuthenticationOptions> CreateHandler()
{
return new HealthGraphAuthenticationHandler(_httpClient, _logger);
return new HealthGraphAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(HealthGraphAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(HealthGraphAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security;

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Owin.Security.Providers.GitHub;
namespace Owin.Security.Providers.HealthGraph.Provider
{

View File

@@ -8,9 +8,9 @@ namespace Owin.Security.Providers.Instagram
InstagramAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException("app");
if (options == null)
throw new ArgumentNullException(nameof(options));
throw new ArgumentNullException("options");
app.Use(typeof(InstagramAuthenticationMiddleware), app, options);

View File

@@ -0,0 +1,231 @@
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<InstagramAuthenticationOptions>
{
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<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> 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<KeyValuePair<string, string>>();
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code));
body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
body.Add(new KeyValuePair<string, string>("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<dynamic>(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<object>(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<object>(null);
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> 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;
}
}
}

View File

@@ -8,42 +8,43 @@ 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<InstagramAuthenticationOptions>
{
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<InstagramAuthenticationMiddleware>();
logger = app.CreateLogger<InstagramAuthenticationMiddleware>();
if (Options.Provider == null)
Options.Provider = new InstagramAuthenticationProvider();
if (Options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
IDataProtector 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
@@ -60,22 +61,24 @@ namespace Owin.Security.Providers.Instagram
/// </returns>
protected override AuthenticationHandler<InstagramAuthenticationOptions> CreateHandler()
{
return new InstagramAuthenticationHandler(_httpClient, _logger);
return new InstagramAuthenticationHandler(httpClient, logger);
}
private static HttpMessageHandler ResolveHttpMessageHandler(InstagramAuthenticationOptions options)
private HttpMessageHandler ResolveHttpMessageHandler(InstagramAuthenticationOptions options)
{
var handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();
// If they provided a validator, apply it or fail.
if (options.BackchannelCertificateValidator == null) return handler;
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
if (options.BackchannelCertificateValidator != null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
// Set the cert validate callback
var webRequestHandler = handler as WebRequestHandler;
if (webRequestHandler == null)
{
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
}
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
return handler;
}

Some files were not shown because too many files have changed in this diff Show More