Compare commits
3 Commits
dependabot
...
LinkedIn-F
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53024cd531 | ||
|
|
4e3d613e16 | ||
|
|
b0e3eba0c1 |
79
.gitignore
vendored
79
.gitignore
vendored
@@ -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
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -1,4 +0,0 @@
|
||||
source 'http://rubygems.org'
|
||||
gem 'os'
|
||||
gem 'albacore'
|
||||
gem 'nokogiri'
|
||||
31
Gemfile.lock
31
Gemfile.lock
@@ -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
|
||||
33
NuGet/Owin.Security.Providers.nuspec
Normal file
33
NuGet/Owin.Security.Providers.nuspec
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
@@ -2,6 +2,6 @@
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
internal const string DefaultAuthenticationType = "ArcGIS Online";
|
||||
public const string DefaultAuthenticationType = "ArcGIS Online";
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -22,7 +22,7 @@ namespace Owin.Security.Providers.BattleNet
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
Region = Region.Us
|
||||
Region = Region.US
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Owin.Security.Providers.BattleNet
|
||||
public enum Region
|
||||
{
|
||||
Europe,
|
||||
Us,
|
||||
US,
|
||||
Korea,
|
||||
Taiwan,
|
||||
China
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
222
Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs
Normal file
222
Owin.Security.Providers/Buffer/BufferAuthenticationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
229
Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs
Normal file
229
Owin.Security.Providers/Dropbox/DropboxAuthenticationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Owin.Security.Providers.EVEOnline
|
||||
namespace Owin.Security.Providers.EveOnline
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Owin.Security.Providers.EVEOnline
|
||||
namespace Owin.Security.Providers.EveOnline
|
||||
{
|
||||
public static class EveOnlineAuthenticationExtensions
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Owin.Security.Providers.EVEOnline
|
||||
namespace Owin.Security.Providers.EveOnline
|
||||
{
|
||||
/// <summary>
|
||||
/// Default <see cref="IEveOnlineAuthenticationProvider"/> implementation.
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
237
Owin.Security.Providers/GitHub/GitHubAuthenticationHandler.cs
Normal file
237
Owin.Security.Providers/GitHub/GitHubAuthenticationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Owin.Security.Providers.GitHub;
|
||||
|
||||
namespace Owin.Security.Providers.HealthGraph.Provider
|
||||
{
|
||||
0
src/Owin.Security.Providers.Instagram/Constants.cs → Owin.Security.Providers/Instagram/Constants.cs
Normal file → Executable file
0
src/Owin.Security.Providers.Instagram/Constants.cs → Owin.Security.Providers/Instagram/Constants.cs
Normal file → Executable 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);
|
||||
|
||||
231
Owin.Security.Providers/Instagram/InstagramAuthenticationHandler.cs
Executable file
231
Owin.Security.Providers/Instagram/InstagramAuthenticationHandler.cs
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user