Merge branch 'kappa7194-imgur'
This commit is contained in:
87
Owin.Security.Providers/Imgur/ImgurAuthenticationDefaults.cs
Normal file
87
Owin.Security.Providers/Imgur/ImgurAuthenticationDefaults.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
namespace Owin.Security.Providers.Imgur
|
||||
{
|
||||
/// <summary>Configuration strings for the imgur provider.</summary>
|
||||
internal static class ImgurAuthenticationDefaults
|
||||
{
|
||||
/// <summary>The error message for user authentication failure.</summary>
|
||||
internal const string AccessDeniedErrorMessage = "access_denied";
|
||||
|
||||
/// <summary>The name of the access token property in the imgur authentication response.</summary>
|
||||
internal const string AccessTokenPropertyName = "access_token";
|
||||
|
||||
/// <summary>The name of the account id property in the imgur authentication response.</summary>
|
||||
internal const string AccountIdPropertyName = "account_id";
|
||||
|
||||
/// <summary>The name of the account username property in the imgur authentication response.</summary>
|
||||
internal const string AccountUsernamePropertyName = "account_username";
|
||||
|
||||
/// <summary>The name of the provider.</summary>
|
||||
internal const string AuthenticationType = "Imgur";
|
||||
|
||||
/// <summary>The grant type to be used.</summary>
|
||||
internal const string AuthorizationCodeGrantType = "authorization_code";
|
||||
|
||||
/// <summary>The user authorization endpoint URL.</summary>
|
||||
internal const string AuthorizationUrl = "https://api.imgur.com/oauth2/authorize";
|
||||
|
||||
/// <summary>The default callback path.</summary>
|
||||
internal const string CallbackPath = "/signin-imgur";
|
||||
|
||||
/// <summary>The name of the application client id parameter.</summary>
|
||||
internal const string ClientIdParameter = "client_id";
|
||||
|
||||
/// <summary>The name of the application client secret parameter.</summary>
|
||||
internal const string ClientSecretParameter = "client_secret";
|
||||
|
||||
/// <summary>The name of the response code parameter.</summary>
|
||||
internal const string CodeParameter = "code";
|
||||
|
||||
/// <summary>The code type of the authentication response.</summary>
|
||||
internal const string CodeResponseType = "code";
|
||||
|
||||
/// <summary>The message for the communication failure error.</summary>
|
||||
internal const string CommunicationFailureMessage = "An error occurred while talking with imgur's server.";
|
||||
|
||||
/// <summary>The message for the authentication response deserialization failure error.</summary>
|
||||
internal const string DeserializationFailureMessage = "The deserialization of the imgur's response failed. Perhaps imgur changed the response format?";
|
||||
|
||||
/// <summary>The name of the error parameter.</summary>
|
||||
internal const string ErrorParameter = "error";
|
||||
|
||||
/// <summary>The name of the access token duration property in the imgur authentication response.</summary>
|
||||
internal const string ExpiresInPropertyName = "expires_in";
|
||||
|
||||
/// <summary>The name of the grant type parameter.</summary>
|
||||
internal const string GrantTypeParameter = "grant_type";
|
||||
|
||||
/// <summary>The format to use to stringify <see cref="System.Int32"/>s.</summary>
|
||||
internal const string Int32Format = "D";
|
||||
|
||||
/// <summary>The message for the invalid authentication ticket error.</summary>
|
||||
internal const string InvalidAuthenticationTicketMessage = "Invalid authentication ticket.";
|
||||
|
||||
/// <summary>The name of the refresh token property in the imgur authentication response.</summary>
|
||||
internal const string RefreshInPropertyName = "refresh_token";
|
||||
|
||||
/// <summary>The name of the response type parameter.</summary>
|
||||
internal const string ResponseTypeParameter = "response_type";
|
||||
|
||||
/// <summary>The name of the scope property in the imgur authentication response.</summary>
|
||||
internal const string ScopePropertyName = "scope";
|
||||
|
||||
/// <summary>The name of the state parameter.</summary>
|
||||
internal const string StateParameter = "state";
|
||||
|
||||
/// <summary>The name of the token type property in the imgur authentication response.</summary>
|
||||
internal const string TokenTypePropertyName = "token_type";
|
||||
|
||||
/// <summary>The token exchange endpoint URL.</summary>
|
||||
internal const string TokenUrl = "https://api.imgur.com/oauth2/token";
|
||||
|
||||
/// <summary>The version of the provider.</summary>
|
||||
internal const string Version = "v1";
|
||||
|
||||
/// <summary>The string value type for <see cref="System.Security.Claims.Claim"/>s.</summary>
|
||||
internal const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Owin.Security.Providers.Imgur
|
||||
{
|
||||
/// <summary>imgur extensions for the <see cref="IAppBuilder"/>.</summary>
|
||||
public static class ImgurAuthenticationExtensions
|
||||
{
|
||||
/// <summary>Configures the <see cref="IAppBuilder"/> to use <see cref="ImgurAuthenticationMiddleware"/> to authenticate user.</summary>
|
||||
/// <param name="appBuilder">The OWIN <see cref="IAppBuilder"/> to be configured.</param>
|
||||
/// <param name="options">The <see cref="ImgurAuthenticationOptions"/> with the settings to be used by the <see cref="ImgurAuthenticationMiddleware"/>.</param>
|
||||
/// <returns>The configured <see cref="IAppBuilder"/>.</returns>
|
||||
public static IAppBuilder UseImgurAuthentication(this IAppBuilder appBuilder, ImgurAuthenticationOptions options)
|
||||
{
|
||||
return appBuilder.Use<ImgurAuthenticationMiddleware>(appBuilder, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
387
Owin.Security.Providers/Imgur/ImgurAuthenticationHandler.cs
Normal file
387
Owin.Security.Providers/Imgur/ImgurAuthenticationHandler.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
namespace Owin.Security.Providers.Imgur
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
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 Microsoft.Owin.Security.Provider;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Owin.Security.Providers.Imgur.Provider;
|
||||
|
||||
/// <summary></summary>
|
||||
public class ImgurAuthenticationHandler : AuthenticationHandler<ImgurAuthenticationOptions>
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger logger;
|
||||
|
||||
/// <summary>Creates a new <see cref="ImgurAuthenticationHandler"/>.</summary>
|
||||
/// <param name="httpClient">The <see cref="HttpClient"/> to be used for back channel calls.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> to be used by the <see cref="ImgurAuthenticationHandler"/>.</param>
|
||||
public ImgurAuthenticationHandler(HttpClient httpClient, ILogger logger)
|
||||
{
|
||||
if (httpClient == null)
|
||||
{
|
||||
throw new ArgumentNullException("httpClient");
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
|
||||
this.httpClient = httpClient;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>Is called once by common code after initialization.</summary>
|
||||
/// <returns>Return true if the request is handled by this <see cref="AuthenticationMiddleware{TOptions}"/>, returns false if the request should be passed to the next <see cref="AuthenticationMiddleware{TOptions}"/>.</returns>
|
||||
public override async Task<bool> InvokeAsync()
|
||||
{
|
||||
if (!this.Options.CallbackPath.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.Options.CallbackPath.Value.Equals(this.Request.Path.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ticket = await this.AuthenticateAsync();
|
||||
|
||||
if (ticket == null)
|
||||
{
|
||||
throw new Exception(ImgurAuthenticationDefaults.InvalidAuthenticationTicketMessage);
|
||||
}
|
||||
|
||||
var context = this.GetImgurReturnEndpointContext(ticket);
|
||||
|
||||
await this.Options.Provider.ReturnEndpoint(context);
|
||||
|
||||
this.SignIn(context);
|
||||
|
||||
if (context.IsRequestCompleted || context.RedirectUri == null)
|
||||
{
|
||||
return context.IsRequestCompleted;
|
||||
}
|
||||
|
||||
var location = GetRedirectLocation(context);
|
||||
|
||||
this.Response.Redirect(location);
|
||||
|
||||
context.RequestCompleted();
|
||||
|
||||
return context.IsRequestCompleted;
|
||||
}
|
||||
|
||||
/// <summary>Handles authentication challenges by intercepting 401 responses.</summary>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
protected override Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if (this.Response.StatusCode != 401)
|
||||
{
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
var challenge = this.Helper.LookupChallenge(this.Options.AuthenticationType, this.Options.AuthenticationMode);
|
||||
|
||||
if (challenge == null)
|
||||
{
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(challenge.Properties.RedirectUri))
|
||||
{
|
||||
challenge.Properties.RedirectUri = this.Request.Uri.AbsoluteUri;
|
||||
}
|
||||
|
||||
this.GenerateCorrelationId(challenge.Properties);
|
||||
|
||||
var state = this.Options.StateDataFormat.Protect(challenge.Properties);
|
||||
var authorizationUri = this.GetAuthorizationUri(state);
|
||||
|
||||
this.Response.Redirect(authorizationUri);
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
/// <summary>The core authentication logic which must be provided by the <see cref="AuthenticationHandler{TOptions}"/>.</summary>
|
||||
/// <returns>The ticket data provided by the authentication logic.</returns>
|
||||
/// <remarks>Will be invoked at most once per request. Do not call directly, call the wrapping Authenticate method instead.</remarks>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
if (this.Request.Query.Get(ImgurAuthenticationDefaults.ErrorParameter) != null)
|
||||
{
|
||||
return new AuthenticationTicket(null, null);
|
||||
}
|
||||
|
||||
var code = this.Request.Query.Get(ImgurAuthenticationDefaults.CodeParameter);
|
||||
var state = this.Request.Query.Get(ImgurAuthenticationDefaults.StateParameter);
|
||||
var properties = this.Options.StateDataFormat.Unprotect(state);
|
||||
|
||||
if (properties == null)
|
||||
{
|
||||
return new AuthenticationTicket(null, null);
|
||||
}
|
||||
|
||||
if (!this.ValidateCorrelationId(properties, this.logger))
|
||||
{
|
||||
return new AuthenticationTicket(null, properties);
|
||||
}
|
||||
|
||||
var authenticationResponse = await this.GetAuthenticationResponseAsync(code);
|
||||
|
||||
if (authenticationResponse == null)
|
||||
{
|
||||
throw new Exception(ImgurAuthenticationDefaults.DeserializationFailureMessage);
|
||||
}
|
||||
|
||||
var identity = this.GetIdentity(authenticationResponse);
|
||||
var context = this.GetImgurAuthenticatedContext(authenticationResponse, identity, properties);
|
||||
|
||||
await this.Options.Provider.Authenticated(context);
|
||||
|
||||
return new AuthenticationTicket(context.Identity, context.Properties);
|
||||
}
|
||||
|
||||
/// <summary>Gets the payload for the back channel authentication request.</summary>
|
||||
/// <param name="code">The authorization code supplied by imgur.</param>
|
||||
/// <returns>The <see cref="HttpContent"/> with the payload for the back channel authentication request.</returns>
|
||||
private HttpContent GetAuthenticationRequestContent(string code)
|
||||
{
|
||||
return
|
||||
new FormUrlEncodedContent(
|
||||
new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(
|
||||
ImgurAuthenticationDefaults.ClientIdParameter,
|
||||
this.Options.ClientId),
|
||||
|
||||
new KeyValuePair<string, string>(
|
||||
ImgurAuthenticationDefaults.ClientSecretParameter,
|
||||
this.Options.ClientSecret),
|
||||
|
||||
new KeyValuePair<string, string>(
|
||||
ImgurAuthenticationDefaults.GrantTypeParameter,
|
||||
ImgurAuthenticationDefaults.AuthorizationCodeGrantType),
|
||||
|
||||
new KeyValuePair<string, string>(
|
||||
ImgurAuthenticationDefaults.CodeParameter,
|
||||
code)
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="AuthenticationResponse"/> from imgur.</summary>
|
||||
/// <param name="code">The authorization code supplied by imgur.</param>
|
||||
/// <returns>The <see cref="AuthenticationResponse"/> from imgur.</returns>
|
||||
private async Task<AuthenticationResponse> GetAuthenticationResponseAsync(string code)
|
||||
{
|
||||
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, ImgurAuthenticationDefaults.TokenUrl))
|
||||
{
|
||||
httpRequestMessage.Content = this.GetAuthenticationRequestContent(code);
|
||||
|
||||
using (var httpResponseMessage = await this.httpClient.SendAsync(httpRequestMessage, this.Request.CallCancelled))
|
||||
{
|
||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception(ImgurAuthenticationDefaults.CommunicationFailureMessage);
|
||||
}
|
||||
|
||||
using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
|
||||
{
|
||||
var jsonSerializer = new JsonSerializer();
|
||||
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
using (var jsonTextReader = new JsonTextReader(streamReader))
|
||||
{
|
||||
return jsonSerializer.Deserialize<AuthenticationResponse>(jsonTextReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the authorization URL for the back channel call.</summary>
|
||||
/// <param name="state">The encrypted <see cref="AuthenticationProperties"/> for the current authentication session.</param>
|
||||
/// <returns>The authorization URL for the back channel call.</returns>
|
||||
private string GetAuthorizationUri(string state)
|
||||
{
|
||||
var authorizationUri = ImgurAuthenticationDefaults.AuthorizationUrl;
|
||||
|
||||
authorizationUri =
|
||||
WebUtilities.AddQueryString(
|
||||
authorizationUri,
|
||||
ImgurAuthenticationDefaults.ClientIdParameter,
|
||||
Uri.EscapeDataString(this.Options.ClientId));
|
||||
|
||||
authorizationUri =
|
||||
WebUtilities.AddQueryString(
|
||||
authorizationUri,
|
||||
ImgurAuthenticationDefaults.ResponseTypeParameter,
|
||||
ImgurAuthenticationDefaults.CodeResponseType);
|
||||
|
||||
authorizationUri =
|
||||
WebUtilities.AddQueryString(
|
||||
authorizationUri,
|
||||
ImgurAuthenticationDefaults.StateParameter,
|
||||
Uri.EscapeDataString(state));
|
||||
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="ClaimsIdentity"/> for the identity of the user.</summary>
|
||||
/// <param name="authenticationResponse">The <see cref="AuthenticationResponse"/> returned by imgur.</param>
|
||||
/// <returns>The <see cref="ClaimsIdentity"/> for the identity of the user.</returns>
|
||||
private ClaimsIdentity GetIdentity(AuthenticationResponse authenticationResponse)
|
||||
{
|
||||
var identity =
|
||||
new ClaimsIdentity(
|
||||
this.Options.AuthenticationType,
|
||||
ClaimsIdentity.DefaultNameClaimType,
|
||||
ClaimsIdentity.DefaultRoleClaimType);
|
||||
|
||||
identity.AddClaim(
|
||||
new Claim(
|
||||
ClaimTypes.Name,
|
||||
authenticationResponse.AccountUsername,
|
||||
ImgurAuthenticationDefaults.XmlSchemaString,
|
||||
this.Options.AuthenticationType));
|
||||
|
||||
identity.AddClaim(
|
||||
new Claim(
|
||||
ClaimTypes.NameIdentifier,
|
||||
authenticationResponse.AccountId.ToString(ImgurAuthenticationDefaults.Int32Format, CultureInfo.InvariantCulture),
|
||||
ImgurAuthenticationDefaults.XmlSchemaString,
|
||||
this.Options.AuthenticationType));
|
||||
|
||||
identity.AddClaim(
|
||||
new Claim(
|
||||
ClaimsIdentity.DefaultNameClaimType,
|
||||
authenticationResponse.AccountUsername,
|
||||
ImgurAuthenticationDefaults.XmlSchemaString,
|
||||
this.Options.AuthenticationType));
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="ImgurAuthenticatedContext"/> for the current authentication session.</summary>
|
||||
/// <param name="authenticationResponse">The <see cref="AuthenticationResponse"/> returned by imgur.</param>
|
||||
/// <param name="identity">The <see cref="ClaimsIdentity"/> for the identity of the user.</param>
|
||||
/// <param name="properties">The <see cref="AuthenticationProperties"/> for the current authentication session.</param>
|
||||
/// <returns>The <see cref="ImgurAuthenticatedContext"/> for the current authentication session.</returns>
|
||||
private ImgurAuthenticatedContext GetImgurAuthenticatedContext(AuthenticationResponse authenticationResponse, ClaimsIdentity identity, AuthenticationProperties properties)
|
||||
{
|
||||
var context = new ImgurAuthenticatedContext(this.Context, this.Options);
|
||||
context.AccessToken = authenticationResponse.AccessToken;
|
||||
context.AccountId = authenticationResponse.AccountId;
|
||||
context.AccountUsername = authenticationResponse.AccountUsername;
|
||||
context.ExpiresIn = authenticationResponse.ExpiresIn;
|
||||
context.Identity = identity;
|
||||
context.Properties = properties;
|
||||
context.RefreshToken = authenticationResponse.RefreshToken;
|
||||
context.Scope = authenticationResponse.Scope;
|
||||
context.TokenType = authenticationResponse.TokenType;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="ImgurReturnEndpointContext"/> for the current authentication session.</summary>
|
||||
/// <param name="ticket">The <see cref="AuthenticationTicket"/> for the current authentication session.</param>
|
||||
/// <returns>The <see cref="ImgurReturnEndpointContext"/> for the current authentication session.</returns>
|
||||
private ImgurReturnEndpointContext GetImgurReturnEndpointContext(AuthenticationTicket ticket)
|
||||
{
|
||||
var context = new ImgurReturnEndpointContext(this.Context, ticket);
|
||||
context.SignInAsAuthenticationType = this.Options.SignInAsAuthenticationType;
|
||||
context.RedirectUri = ticket.Properties.RedirectUri;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>Adds authentication information to the OWIN context to let the appropriate <see cref="AuthenticationMiddleware{TOptions}"/> authenticate the user.</summary>
|
||||
/// <param name="context">The <see cref="ReturnEndpointContext"/> for the current authentication session.</param>
|
||||
private void SignIn(ReturnEndpointContext context)
|
||||
{
|
||||
if (context.SignInAsAuthenticationType == null || context.Identity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var identity = context.Identity;
|
||||
|
||||
if (!identity.AuthenticationType.Equals(context.SignInAsAuthenticationType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
identity =
|
||||
new ClaimsIdentity(
|
||||
identity.Claims,
|
||||
context.SignInAsAuthenticationType,
|
||||
identity.NameClaimType,
|
||||
identity.RoleClaimType);
|
||||
}
|
||||
|
||||
this.Context.Authentication.SignIn(context.Properties, identity);
|
||||
}
|
||||
|
||||
/// <summary>Gets the URL where the user should be redirect to.</summary>
|
||||
/// <param name="context">The <see cref="ReturnEndpointContext"/> for the current authentication session.</param>
|
||||
/// <returns>The URL where the user should be redirect to.</returns>
|
||||
private static string GetRedirectLocation(ReturnEndpointContext context)
|
||||
{
|
||||
var location = context.RedirectUri;
|
||||
|
||||
if (context.Identity == null)
|
||||
{
|
||||
location =
|
||||
WebUtilities.AddQueryString(
|
||||
location,
|
||||
ImgurAuthenticationDefaults.ErrorParameter,
|
||||
ImgurAuthenticationDefaults.AccessDeniedErrorMessage);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
/// <summary>Response payload returned by imgur with the information of the authenticated user.</summary>
|
||||
private class AuthenticationResponse
|
||||
{
|
||||
/// <summary>Gets or sets the access token for the authenticated user.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.AccessTokenPropertyName)]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the account id of the authenticated user.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.AccountIdPropertyName)]
|
||||
public int AccountId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the account username of the authenticated user.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.AccountUsernamePropertyName)]
|
||||
public string AccountUsername { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the duration of the access token.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.ExpiresInPropertyName)]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the refresh token for the authenticated user.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.RefreshInPropertyName)]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the scope of the access token.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.ScopePropertyName)]
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the type of the access token.</summary>
|
||||
[JsonProperty(PropertyName = ImgurAuthenticationDefaults.TokenTypePropertyName)]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
160
Owin.Security.Providers/Imgur/ImgurAuthenticationMiddleware.cs
Normal file
160
Owin.Security.Providers/Imgur/ImgurAuthenticationMiddleware.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
namespace Owin.Security.Providers.Imgur
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Logging;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.DataHandler;
|
||||
using Microsoft.Owin.Security.DataProtection;
|
||||
using Microsoft.Owin.Security.Infrastructure;
|
||||
|
||||
using Owin.Security.Providers.Imgur.Provider;
|
||||
using Owin.Security.Providers.Properties;
|
||||
|
||||
/// <summary>OWIN authentication middleware for imgur.</summary>
|
||||
public class ImgurAuthenticationMiddleware : AuthenticationMiddleware<ImgurAuthenticationOptions>
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly static string TypeFullName = typeof(ImgurAuthenticationMiddleware).FullName;
|
||||
|
||||
/// <summary>Creates a new <see cref="ImgurAuthenticationMiddleware"/>.</summary>
|
||||
/// <param name="next">The next <see cref="OwinMiddleware"/> in the configuration chain.</param>
|
||||
/// <param name="appBuilder">The OWIN <see cref="IAppBuilder"/> being configured.</param>
|
||||
/// <param name="options">The <see cref="ImgurAuthenticationOptions"/> to be used to set up the <see cref="ImgurAuthenticationMiddleware"/>.</param>
|
||||
public ImgurAuthenticationMiddleware(OwinMiddleware next, IAppBuilder appBuilder, ImgurAuthenticationOptions options)
|
||||
: base(next, options)
|
||||
{
|
||||
if (appBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException("appBuilder");
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException("options");
|
||||
}
|
||||
|
||||
this.CheckClientId();
|
||||
this.CheckClientSecret();
|
||||
this.SetProvider();
|
||||
this.SetSignInAsAuthenticationType(appBuilder);
|
||||
this.SetStateDataFormat(appBuilder);
|
||||
|
||||
var httpMessageHandler = ResolveHttpMessageHandler(this.Options);
|
||||
|
||||
this.httpClient = new HttpClient(httpMessageHandler);
|
||||
this.logger = appBuilder.CreateLogger<ImgurAuthenticationMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>Creates the <see cref="AuthenticationHandler{TOptions}"/> to be used by the <see cref="ImgurAuthenticationMiddleware"/>.</summary>
|
||||
/// <returns>The <see cref="AuthenticationHandler{TOptions}"/> to be used by the <see cref="ImgurAuthenticationMiddleware"/>.</returns>
|
||||
protected override AuthenticationHandler<ImgurAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new ImgurAuthenticationHandler(this.httpClient, this.logger);
|
||||
}
|
||||
|
||||
/// <summary>Checks that the imgur application client id has been set.</summary>
|
||||
private void CheckClientId()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(this.Options.ClientId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message =
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
Resources.Exception_OptionMustBeProvided,
|
||||
"ClientId");
|
||||
|
||||
throw new ArgumentException(message, "options");
|
||||
}
|
||||
|
||||
/// <summary>Checks that the imgur application client secret has been set.</summary>
|
||||
private void CheckClientSecret()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(this.Options.ClientSecret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message =
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
Resources.Exception_OptionMustBeProvided,
|
||||
"ClientSecret");
|
||||
|
||||
throw new ArgumentException(message, "options");
|
||||
}
|
||||
|
||||
/// <summary>Sets the provider to <see cref="ImgurAuthenticationProvider"/> if it hasn't been set.</summary>
|
||||
private void SetProvider()
|
||||
{
|
||||
if (this.Options.Provider != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Options.Provider = new ImgurAuthenticationProvider();
|
||||
}
|
||||
|
||||
/// <summary>Sets the name authentication middleware responsible for signing in the user if it hasn't been set.</summary>
|
||||
/// <param name="appBuilder">The OWIN <see cref="IAppBuilder"/> being configured.</param>
|
||||
private void SetSignInAsAuthenticationType(IAppBuilder appBuilder)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(this.Options.SignInAsAuthenticationType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Options.SignInAsAuthenticationType = appBuilder.GetDefaultSignInAsAuthenticationType();
|
||||
}
|
||||
|
||||
/// <summary>Sets the data protector to <see cref="PropertiesDataFormat"/> if it hasn't been set.</summary>
|
||||
/// <param name="appBuilder">The OWIN <see cref="IAppBuilder"/> being configured.</param>
|
||||
private void SetStateDataFormat(IAppBuilder appBuilder)
|
||||
{
|
||||
if (this.Options.StateDataFormat != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dataProtector =
|
||||
appBuilder.CreateDataProtector(
|
||||
TypeFullName,
|
||||
this.Options.AuthenticationType,
|
||||
ImgurAuthenticationDefaults.Version);
|
||||
|
||||
this.Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="HttpMessageHandler"/> to be used for the back channel calls.</summary>
|
||||
/// <param name="options">The <see cref="ImgurAuthenticationOptions"/> used to configure the <see cref="ImgurAuthenticationMiddleware"/>.</param>
|
||||
/// <returns>The <see cref="HttpMessageHandler"/> to be used for the back channel calls.</returns>
|
||||
private static HttpMessageHandler ResolveHttpMessageHandler(ImgurAuthenticationOptions options)
|
||||
{
|
||||
var httpMessageHandler = options.BackchannelHttpHandler ?? new WebRequestHandler();
|
||||
|
||||
if (options.BackchannelCertificateValidator == null)
|
||||
{
|
||||
return httpMessageHandler;
|
||||
}
|
||||
|
||||
var webRequestHandler = httpMessageHandler as WebRequestHandler;
|
||||
|
||||
if (webRequestHandler == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
|
||||
}
|
||||
|
||||
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
|
||||
|
||||
return webRequestHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Owin.Security.Providers/Imgur/ImgurAuthenticationOptions.cs
Normal file
68
Owin.Security.Providers/Imgur/ImgurAuthenticationOptions.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace Owin.Security.Providers.Imgur
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
|
||||
using Owin.Security.Providers.Imgur.Provider;
|
||||
|
||||
/// <summary>Configuration options for the <see cref="ImgurAuthenticationMiddleware"/>.</summary>
|
||||
public class ImgurAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>Creates a new <see cref="ImgurAuthenticationOptions"/>.</summary>
|
||||
public ImgurAuthenticationOptions()
|
||||
: base(ImgurAuthenticationDefaults.AuthenticationType)
|
||||
{
|
||||
this.AuthenticationMode = AuthenticationMode.Passive;
|
||||
this.BackchannelTimeout = TimeSpan.FromSeconds(60);
|
||||
this.CallbackPath = new PathString(ImgurAuthenticationDefaults.CallbackPath);
|
||||
this.Caption = ImgurAuthenticationDefaults.AuthenticationType;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the a pinned certificate validator to use to validate the endpoints used in back channel communications belong to StackExchange.</summary>
|
||||
/// <remarks>If this property is null then the default certificate checks are performed, validating the subject name and if the signing chain is a trusted party.</remarks>
|
||||
public ICertificateValidator BackchannelCertificateValidator { get; set; }
|
||||
|
||||
/// <summary>The HttpMessageHandler used to communicate with StackExchange.</summary>
|
||||
/// <remarks>This cannot be set at the same time as BackchannelCertificateValidator unless the value can be downcast to a WebRequestHandler.</remarks>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>Gets or sets timeout value in milliseconds for back channel communications with imgur.</summary>
|
||||
public TimeSpan BackchannelTimeout { get; set; }
|
||||
|
||||
/// <summary>The request path within the application's base path where the user-agent will be returned. The middleware will process this request when it arrives.</summary>
|
||||
/// <remarks>The default value is "/signin-imgur".</remarks>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
/// <summary>Get or sets the text that the user can display on a sign in user interface.</summary>
|
||||
public string Caption
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Description.Caption;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.Description.Caption = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the imgur application client id.</summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the imgur application client secret.</summary>
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the <see cref="IImgurAuthenticationProvider" /> used in the authentication events.</summary>
|
||||
public IImgurAuthenticationProvider Provider { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user.</summary>
|
||||
public string SignInAsAuthenticationType { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the type used to secure the data handled by the middleware.</summary>
|
||||
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Owin.Security.Providers.Imgur.Provider
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>Specifies callback methods which the <see cref="ImgurAuthenticationMiddleware"/> invokes to enable developers control over the authentication process.</summary>
|
||||
public interface IImgurAuthenticationProvider
|
||||
{
|
||||
/// <summary>Invoked whenever imgur succesfully authenticates a user.</summary>
|
||||
/// <param name="context">The <see cref="ImgurAuthenticatedContext"/> that contains information about the login session and the user's <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task Authenticated(ImgurAuthenticatedContext context);
|
||||
|
||||
/// <summary>Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.</summary>
|
||||
/// <param name="context">The <see cref="ImgurReturnEndpointContext"/> of the authentication request.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task ReturnEndpoint(ImgurReturnEndpointContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Owin.Security.Providers.Imgur.Provider
|
||||
{
|
||||
using System.Security.Claims;
|
||||
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Provider;
|
||||
|
||||
/// <summary>Contains information about the login session and the user's <see cref="System.Security.Claims.ClaimsIdentity"/>.</summary>
|
||||
public class ImgurAuthenticatedContext : BaseContext<ImgurAuthenticationOptions>
|
||||
{
|
||||
/// <summary>Creates a new <see cref="ImgurAuthenticatedContext"/>.</summary>
|
||||
/// <param name="context">The OWIN context of the autentication request.</param>
|
||||
/// <param name="options">The <see cref="ImgurAuthenticationOptions"/> used to set up the <see cref="ImgurAuthenticationMiddleware"/>.</param>
|
||||
public ImgurAuthenticatedContext(IOwinContext context, ImgurAuthenticationOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the access token for the authenticated user.</summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the account id of the authenticated user.</summary>
|
||||
public int AccountId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the account username of the authenticated user.</summary>
|
||||
public string AccountUsername { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the duration of the access token.</summary>
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the <see cref="ClaimsIdentity"/> for the authenticated user.</summary>
|
||||
public ClaimsIdentity Identity { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the <see cref="AuthenticationProperties"/> of the authentication request.</summary>
|
||||
public AuthenticationProperties Properties { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the refresh token for the authenticated user.</summary>
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the scope of the access token.</summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the type of the access token.</summary>
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Owin.Security.Providers.Imgur.Provider
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>Default <see cref="IImgurAuthenticationProvider"/> implementation.</summary>
|
||||
public class ImgurAuthenticationProvider : IImgurAuthenticationProvider
|
||||
{
|
||||
/// <summary>Creates a new <see cref="ImgurAuthenticationProvider"/>.</summary>
|
||||
public ImgurAuthenticationProvider()
|
||||
{
|
||||
this.OnAuthenticated = context => Task.FromResult<object>(null);
|
||||
this.OnReturnEndpoint = context => Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the function that is invoked when the Authenticated method is invoked.</summary>
|
||||
public Func<ImgurAuthenticatedContext, Task> OnAuthenticated { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.</summary>
|
||||
public Func<ImgurReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
|
||||
|
||||
/// <summary>Invoked whenever imgur succesfully authenticates a user.</summary>
|
||||
/// <param name="context">The <see cref="ImgurAuthenticatedContext"/> that contains information about the login session and the user's <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public Task Authenticated(ImgurAuthenticatedContext context)
|
||||
{
|
||||
return this.OnAuthenticated(context);
|
||||
}
|
||||
|
||||
/// <summary>Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.</summary>
|
||||
/// <param name="context">The <see cref="ImgurReturnEndpointContext"/> of the authentication request.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
public Task ReturnEndpoint(ImgurReturnEndpointContext context)
|
||||
{
|
||||
return this.OnReturnEndpoint(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Owin.Security.Providers.Imgur.Provider
|
||||
{
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Provider;
|
||||
|
||||
/// <summary>Provide context information to the middleware provider.</summary>
|
||||
public class ImgurReturnEndpointContext : ReturnEndpointContext
|
||||
{
|
||||
/// <summary>Creates a new <see cref="ImgurReturnEndpointContext"/>.</summary>
|
||||
/// <param name="context">The OWIN context of the authentication request.</param>
|
||||
/// <param name="ticket">The <see cref="AuthenticationTicket" /> of the authentication request.</param>
|
||||
public ImgurReturnEndpointContext(IOwinContext context, AuthenticationTicket ticket)
|
||||
: base(context, ticket)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +178,15 @@
|
||||
<Compile Include="HealthGraph\Provider\HealthGraphAuthenticationProvider.cs" />
|
||||
<Compile Include="HealthGraph\Provider\HealthGraphReturnEndpointContext.cs" />
|
||||
<Compile Include="HealthGraph\Provider\IHealthGraphAuthenticationProvider.cs" />
|
||||
<Compile Include="Imgur\ImgurAuthenticationDefaults.cs" />
|
||||
<Compile Include="Imgur\ImgurAuthenticationExtensions.cs" />
|
||||
<Compile Include="Imgur\ImgurAuthenticationHandler.cs" />
|
||||
<Compile Include="Imgur\ImgurAuthenticationMiddleware.cs" />
|
||||
<Compile Include="Imgur\ImgurAuthenticationOptions.cs" />
|
||||
<Compile Include="Imgur\Provider\IImgurAuthenticationProvider.cs" />
|
||||
<Compile Include="Imgur\Provider\ImgurAuthenticationProvider.cs" />
|
||||
<Compile Include="Imgur\Provider\ImgurReturnEndpointContext.cs" />
|
||||
<Compile Include="Imgur\Provider\ImgurAuthenticatedContext.cs" />
|
||||
<Compile Include="Instagram\Constants.cs" />
|
||||
<Compile Include="Instagram\InstagramAuthenticationExtensions.cs" />
|
||||
<Compile Include="Instagram\InstagramAuthenticationHandler.cs" />
|
||||
|
||||
@@ -18,6 +18,7 @@ using Owin.Security.Providers.Gitter;
|
||||
using Owin.Security.Providers.GooglePlus;
|
||||
using Owin.Security.Providers.GooglePlus.Provider;
|
||||
using Owin.Security.Providers.HealthGraph;
|
||||
using Owin.Security.Providers.Imgur;
|
||||
using Owin.Security.Providers.Instagram;
|
||||
using Owin.Security.Providers.LinkedIn;
|
||||
using Owin.Security.Providers.OpenID;
|
||||
@@ -248,6 +249,13 @@ namespace OwinOAuthProvidersDemo
|
||||
// clientId: "",
|
||||
// clientSecret: ""
|
||||
//);
|
||||
|
||||
//app.UseImgurAuthentication(
|
||||
// new ImgurAuthenticationOptions
|
||||
// {
|
||||
// ClientId = "",
|
||||
// ClientSecret = ""
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user