ClaimsXform and RIP AutoAuthHandler
- Initial support for ClaimsTransformation - merge automatic auth handler back into base
This commit is contained in:
parent
bd7f07052e
commit
14d1b467c6
|
|
@ -12,7 +12,7 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>22571</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>36505</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>22570</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>36504</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.DataProtection;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
@ -28,6 +29,13 @@ namespace CookieSample
|
|||
{
|
||||
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
});
|
||||
services.ConfigureClaimsTransformation(p =>
|
||||
{
|
||||
var id = new ClaimsIdentity("xform");
|
||||
id.AddClaim(new Claim("ClaimsTransformation", "TransformAddedClaim"));
|
||||
p.AddIdentity(id);
|
||||
return p;
|
||||
});
|
||||
});
|
||||
|
||||
app.UseCookieAuthentication(options =>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Microsoft.Framework.Logging;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.Cookies
|
||||
{
|
||||
internal class CookieAuthenticationHandler : AutomaticAuthenticationHandler<CookieAuthenticationOptions>
|
||||
internal class CookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>
|
||||
{
|
||||
private const string HeaderNameCacheControl = "Cache-Control";
|
||||
private const string HeaderNamePragma = "Pragma";
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CookieAuthenticationMiddleware(RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<CookieAuthenticationOptions> options,
|
||||
public CookieAuthenticationMiddleware(
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<CookieAuthenticationOptions> options,
|
||||
ConfigureOptions<CookieAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// <summary>
|
||||
/// Contains the options used by the CookiesAuthenticationMiddleware
|
||||
/// </summary>
|
||||
public class CookieAuthenticationOptions : AutomaticAuthenticationOptions
|
||||
public class CookieAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private string _cookieName;
|
||||
|
||||
|
|
@ -20,7 +20,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
/// </summary>
|
||||
public CookieAuthenticationOptions()
|
||||
{
|
||||
AutomaticAuthentication = true;
|
||||
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
|
||||
ExpireTimeSpan = TimeSpan.FromDays(14);
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
|||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="options">Configuration options for the middleware.</param>
|
||||
public FacebookAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<FacebookAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<FacebookAuthenticationOptions> options,
|
||||
ConfigureOptions<FacebookAuthenticationOptions> configureOptions = null)
|
||||
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="options">Configuration options for the middleware.</param>
|
||||
public GoogleAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<GoogleAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<GoogleAuthenticationOptions> options,
|
||||
ConfigureOptions<GoogleAuthenticationOptions> configureOptions = null)
|
||||
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
|
|||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="options">Configuration options for the middleware.</param>
|
||||
public MicrosoftAccountAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<MicrosoftAccountAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<MicrosoftAccountAuthenticationOptions> options,
|
||||
ConfigureOptions<MicrosoftAccountAuthenticationOptions> configureOptions = null)
|
||||
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -176,13 +176,19 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
if (ShouldConvertChallengeToForbidden())
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Response.StatusCode != 401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only redirect on challenges
|
||||
if (ChallengeContext == null)
|
||||
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
|
||||
if (ChallengeContext == null && !Options.AutomaticAuthentication)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
|||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="options">Configuration options for the middleware.</param>
|
||||
public OAuthAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<TOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<TOptions> options,
|
||||
ConfigureOptions<TOptions> configureOptions = null)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ using Microsoft.IdentityModel.Protocols;
|
|||
|
||||
namespace Microsoft.AspNet.Authentication.OAuthBearer
|
||||
{
|
||||
public class OAuthBearerAuthenticationHandler : AutomaticAuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
public class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private OpenIdConnectConfiguration _configuration;
|
||||
|
|
@ -197,7 +197,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
|
|||
return;
|
||||
}
|
||||
|
||||
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
||||
if ((Response.StatusCode != 401) || (ChallengeContext == null && !Options.AutomaticAuthentication))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
|
|||
/// extension method.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
|
|||
/// <summary>
|
||||
/// Options class provides information needed to control Bearer Authentication middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationOptions : AutomaticAuthenticationOptions
|
||||
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private ICollection<ISecurityTokenValidator> _securityTokenValidators;
|
||||
private TokenValidationParameters _tokenValidationParameters;
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <param name="options">Configuration options for the middleware</param>
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
public OpenIdConnectAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<OpenIdConnectAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<OpenIdConnectAuthenticationOptions> options,
|
||||
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -117,13 +117,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
|||
/// <returns></returns>
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if (ShouldConvertChallengeToForbidden())
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Response.StatusCode != 401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only redirect on challenges
|
||||
if (ChallengeContext == null)
|
||||
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
|
||||
if (ChallengeContext == null && !Options.AutomaticAuthentication)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,13 +131,19 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if (ShouldConvertChallengeToForbidden())
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Response.StatusCode != 401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only redirect on challenges
|
||||
if (ChallengeContext == null)
|
||||
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
|
||||
if (ChallengeContext == null && !Options.AutomaticAuthentication)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
|||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="options">Configuration options for the middleware</param>
|
||||
public TwitterAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<TwitterAuthenticationOptions> options,
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IDataProtectionProvider dataProtectionProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
[NotNull] IOptions<TwitterAuthenticationOptions> options,
|
||||
ConfigureOptions<TwitterAuthenticationOptions> configureOptions = null)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -72,6 +72,15 @@ namespace Microsoft.AspNet.Authentication
|
|||
Response.OnSendingHeaders(OnSendingHeaderCallback, this);
|
||||
|
||||
await InitializeCoreAsync();
|
||||
|
||||
if (BaseOptions.AutomaticAuthentication)
|
||||
{
|
||||
var ticket = await AuthenticateAsync();
|
||||
if (ticket?.Principal != null)
|
||||
{
|
||||
SecurityHelper.AddUserPrincipal(Context, ticket.Principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSendingHeaderCallback(object state)
|
||||
|
|
@ -128,7 +137,7 @@ namespace Microsoft.AspNet.Authentication
|
|||
/// pipeline.</returns>
|
||||
public virtual Task<bool> InvokeAsync()
|
||||
{
|
||||
return Task.FromResult<bool>(false);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public virtual void GetDescriptions(IDescribeSchemesContext describeContext)
|
||||
|
|
@ -143,17 +152,17 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public virtual void Authenticate(IAuthenticateContext context)
|
||||
{
|
||||
if (context.AuthenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal))
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
{
|
||||
AuthenticationTicket ticket = Authenticate();
|
||||
if (ticket != null && ticket.Principal != null)
|
||||
var ticket = Authenticate();
|
||||
if (ticket?.Principal != null)
|
||||
{
|
||||
AuthenticateCalled = true;
|
||||
context.Authenticated(ticket.Principal, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NotAuthenticated(BaseOptions.AuthenticationScheme, properties: null, description: BaseOptions.Description.Dictionary);
|
||||
context.NotAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,17 +174,17 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public virtual async Task AuthenticateAsync(IAuthenticateContext context)
|
||||
{
|
||||
if (context.AuthenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal))
|
||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||
{
|
||||
AuthenticationTicket ticket = await AuthenticateAsync();
|
||||
if (ticket != null && ticket.Principal != null)
|
||||
var ticket = await AuthenticateAsync();
|
||||
if (ticket?.Principal != null)
|
||||
{
|
||||
AuthenticateCalled = true;
|
||||
context.Authenticated(ticket.Principal, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NotAuthenticated(BaseOptions.AuthenticationScheme, properties: null, description: BaseOptions.Description.Dictionary);
|
||||
context.NotAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -360,14 +369,26 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
public virtual bool ShouldHandleScheme(IEnumerable<string> authenticationSchemes)
|
||||
{
|
||||
return authenticationSchemes != null &&
|
||||
authenticationSchemes.Any() &&
|
||||
authenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal);
|
||||
// If there are any schemes asked for, need to match, otherwise automatic authentication matches
|
||||
return authenticationSchemes != null && authenticationSchemes.Any()
|
||||
? authenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal)
|
||||
: BaseOptions.AutomaticAuthentication;
|
||||
}
|
||||
|
||||
public virtual bool ShouldHandleScheme(string authenticationScheme)
|
||||
{
|
||||
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal);
|
||||
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
|
||||
(BaseOptions.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme));
|
||||
}
|
||||
|
||||
public virtual bool ShouldConvertChallengeToForbidden()
|
||||
{
|
||||
// Return 403 iff 401 and this handler's authenticate was called
|
||||
// and the challenge is for the authentication type
|
||||
return Response.StatusCode == 401 &&
|
||||
AuthenticateCalled &&
|
||||
ChallengeContext != null &&
|
||||
ShouldHandleScheme(ChallengeContext.AuthenticationSchemes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -384,11 +405,11 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
protected void GenerateCorrelationId([NotNull] AuthenticationProperties properties)
|
||||
{
|
||||
string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
|
||||
var correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
|
||||
|
||||
var nonceBytes = new byte[32];
|
||||
CryptoRandom.GetBytes(nonceBytes);
|
||||
string correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
|
||||
var correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
|
||||
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
|
|
@ -403,9 +424,8 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
protected bool ValidateCorrelationId([NotNull] AuthenticationProperties properties, [NotNull] ILogger logger)
|
||||
{
|
||||
string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
|
||||
|
||||
string correlationCookie = Request.Cookies[correlationKey];
|
||||
var correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
|
||||
var correlationCookie = Request.Cookies[correlationKey];
|
||||
if (string.IsNullOrWhiteSpace(correlationCookie))
|
||||
{
|
||||
logger.LogWarning("{0} cookie not found.", correlationKey);
|
||||
|
|
@ -453,3 +473,4 @@ namespace Microsoft.AspNet.Authentication
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ namespace Microsoft.AspNet.Authentication
|
|||
private readonly RequestDelegate _next;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
protected AuthenticationMiddleware([NotNull] RequestDelegate next, [NotNull] IServiceProvider services, [NotNull] IOptions<TOptions> options, ConfigureOptions<TOptions> configureOptions)
|
||||
protected AuthenticationMiddleware(
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IServiceProvider services,
|
||||
[NotNull] IOptions<TOptions> options,
|
||||
ConfigureOptions<TOptions> configureOptions)
|
||||
{
|
||||
if (configureOptions != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ namespace Microsoft.AspNet.Authentication
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true the authentication middleware alter the request user coming in and
|
||||
/// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide
|
||||
/// identity and alter responses when explicitly indicated by the AuthenticationScheme.
|
||||
/// </summary>
|
||||
public bool AutomaticAuthentication { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the authentication type which is made available to the application.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http.Core.Authentication;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
{
|
||||
public static class AuthenticationServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action<ClaimsTransformationOptions> configure)
|
||||
{
|
||||
return services.Configure(configure);
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Func<ClaimsPrincipal, ClaimsPrincipal> transform)
|
||||
{
|
||||
return services.Configure<ClaimsTransformationOptions>(o => o.Transformation = transform);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for the per-request work performed by automatic authentication middleware.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOptions">Specifies which type for of AutomaticAuthenticationOptions property</typeparam>
|
||||
public abstract class AutomaticAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : AutomaticAuthenticationOptions
|
||||
{
|
||||
public virtual bool ShouldConvertChallengeToForbidden()
|
||||
{
|
||||
// Return 403 iff 401 and this handler's authenticate was called
|
||||
// and the challenge is for the authentication type
|
||||
return Response.StatusCode == 401 &&
|
||||
AuthenticateCalled &&
|
||||
ChallengeContext != null &&
|
||||
ShouldHandleScheme(ChallengeContext.AuthenticationSchemes);
|
||||
}
|
||||
|
||||
protected async override Task InitializeCoreAsync()
|
||||
{
|
||||
if (Options.AutomaticAuthentication)
|
||||
{
|
||||
AuthenticationTicket ticket = await AuthenticateAsync();
|
||||
if (ticket != null && ticket.Principal != null)
|
||||
{
|
||||
SecurityHelper.AddUserPrincipal(Context, ticket.Principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SignOut(ISignOutContext context)
|
||||
{
|
||||
// Empty or null auth scheme is allowed for automatic Authentication
|
||||
if (Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(context.AuthenticationScheme))
|
||||
{
|
||||
SignInContext = null;
|
||||
SignOutContext = context;
|
||||
context.Accept();
|
||||
}
|
||||
|
||||
base.SignOut(context);
|
||||
}
|
||||
|
||||
public override void Challenge(IChallengeContext context)
|
||||
{
|
||||
// Null or Empty scheme allowed for automatic authentication
|
||||
if (Options.AutomaticAuthentication &&
|
||||
(context.AuthenticationSchemes == null || !context.AuthenticationSchemes.Any()))
|
||||
{
|
||||
ChallengeContext = context;
|
||||
context.Accept(BaseOptions.AuthenticationScheme, BaseOptions.Description.Dictionary);
|
||||
}
|
||||
|
||||
base.Challenge(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatic Authentication Handlers can handle empty authentication schemes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool ShouldHandleScheme(IEnumerable<string> authenticationSchemes)
|
||||
{
|
||||
if (base.ShouldHandleScheme(authenticationSchemes))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Options.AutomaticAuthentication &&
|
||||
(authenticationSchemes == null || !authenticationSchemes.Any());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatic Authentication Handlers can handle empty authentication schemes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool ShouldHandleScheme(string authenticationScheme)
|
||||
{
|
||||
if (base.ShouldHandleScheme(authenticationScheme))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Base Options for all automatic authentication middleware
|
||||
/// </summary>
|
||||
public abstract class AutomaticAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// If true the authentication middleware alter the request user coming in and
|
||||
/// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide
|
||||
/// identity and alter responses when explicitly indicated by the AuthenticationScheme.
|
||||
/// </summary>
|
||||
public bool AutomaticAuthentication { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods provided by the claims transformation authentication middleware
|
||||
/// </summary>
|
||||
public static class ClaimsTransformationAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a claims transformation middleware to your web application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="app">The IApplicationBuilder passed to your configuration method</param>
|
||||
/// <param name="configureOptions">Used to configure the options for the middleware</param>
|
||||
/// <param name="optionsName">The name of the options class that controls the middleware behavior, null will use the default options</param>
|
||||
/// <returns>The original app parameter</returns>
|
||||
public static IApplicationBuilder UseClaimsTransformation(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseMiddleware<ClaimsTransformationMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.Http.Core.Authentication;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler that applies ClaimsTransformation to authentication
|
||||
/// </summary>
|
||||
public class ClaimsTransformationAuthenticationHandler : IAuthenticationHandler
|
||||
{
|
||||
private readonly Func<ClaimsPrincipal, ClaimsPrincipal> _transform;
|
||||
|
||||
public ClaimsTransformationAuthenticationHandler(Func<ClaimsPrincipal, ClaimsPrincipal> transform)
|
||||
{
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
public IAuthenticationHandler PriorHandler { get; set; }
|
||||
|
||||
private void ApplyTransform(IAuthenticateContext context)
|
||||
{
|
||||
if (_transform != null)
|
||||
{
|
||||
// REVIEW: this cast seems really bad (missing interface way to get the result back out?)
|
||||
var authContext = context as AuthenticateContext;
|
||||
if (authContext?.Result?.Principal != null)
|
||||
{
|
||||
context.Authenticated(
|
||||
_transform.Invoke(authContext.Result.Principal),
|
||||
authContext.Result.Properties.Dictionary,
|
||||
authContext.Result.Description.Dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Authenticate(IAuthenticateContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
PriorHandler.Authenticate(context);
|
||||
ApplyTransform(context);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AuthenticateAsync(IAuthenticateContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
await PriorHandler.AuthenticateAsync(context);
|
||||
ApplyTransform(context);
|
||||
}
|
||||
}
|
||||
|
||||
public void Challenge(IChallengeContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
PriorHandler.Challenge(context);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDescriptions(IDescribeSchemesContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
PriorHandler.GetDescriptions(context);
|
||||
}
|
||||
}
|
||||
|
||||
public void SignIn(ISignInContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
PriorHandler.SignIn(context);
|
||||
}
|
||||
}
|
||||
|
||||
public void SignOut(ISignOutContext context)
|
||||
{
|
||||
if (PriorHandler != null)
|
||||
{
|
||||
PriorHandler.SignOut(context);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterAuthenticationHandler(IHttpAuthenticationFeature auth)
|
||||
{
|
||||
PriorHandler = auth.Handler;
|
||||
auth.Handler = this;
|
||||
}
|
||||
|
||||
public void UnregisterAuthenticationHandler(IHttpAuthenticationFeature auth)
|
||||
{
|
||||
auth.Handler = PriorHandler;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public class ClaimsTransformationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public ClaimsTransformationMiddleware(
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] IOptions<ClaimsTransformationOptions> options)
|
||||
{
|
||||
// REVIEW: do we need to take ConfigureOptions<ClaimsTransformationOptions>??
|
||||
Options = options.Options;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public ClaimsTransformationOptions Options { get; set; }
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformation);
|
||||
handler.RegisterAuthenticationHandler(context.GetAuthentication());
|
||||
try {
|
||||
if (Options.Transformation != null)
|
||||
{
|
||||
context.User = Options.Transformation.Invoke(context.User);
|
||||
}
|
||||
await _next(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler.UnregisterAuthenticationHandler(context.GetAuthentication());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Authorization
|
||||
namespace Microsoft.AspNet.Authentication
|
||||
{
|
||||
public class ClaimsTransformationOptions
|
||||
{
|
||||
public Func<ClaimsPrincipal, Task<ClaimsPrincipal>> TransformAsync { get; set; }
|
||||
public Func<ClaimsPrincipal, ClaimsPrincipal> Transformation { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,6 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action<ClaimsTransformationOptions> configure)
|
||||
{
|
||||
return services.Configure(configure);
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureAuthorization([NotNull] this IServiceCollection services, [NotNull] Action<AuthorizationOptions> configure)
|
||||
{
|
||||
return services.Configure(configure);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -79,9 +78,15 @@ namespace Microsoft.AspNet.Authentication
|
|||
|
||||
private class TestOptions : AuthenticationOptions { }
|
||||
|
||||
private class TestAutoOptions : AutomaticAuthenticationOptions { }
|
||||
private class TestAutoOptions : AuthenticationOptions
|
||||
{
|
||||
public TestAutoOptions()
|
||||
{
|
||||
AutomaticAuthentication = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestAutoHandler : AutomaticAuthenticationHandler<AutomaticAuthenticationOptions>
|
||||
private class TestAutoHandler : AuthenticationHandler<TestAutoOptions>
|
||||
{
|
||||
public TestAutoHandler(string scheme, bool auto)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,37 +34,47 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProtectedRequestShouldRedirectToLogin()
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ProtectedRequestShouldRedirectToLoginOnlyWhenAutomatic(bool auto)
|
||||
{
|
||||
TestServer server = CreateServer(options =>
|
||||
{
|
||||
options.LoginPath = new PathString("/login");
|
||||
options.AutomaticAuthentication = auto;
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected");
|
||||
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.LocalPath.ShouldBe("/login");
|
||||
location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
|
||||
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
|
||||
if (auto)
|
||||
{
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.LocalPath.ShouldBe("/login");
|
||||
location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProtectedCustomRequestShouldRedirectToCustomLogin()
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ProtectedCustomRequestShouldRedirectToCustomLogin(bool auto)
|
||||
{
|
||||
TestServer server = CreateServer(options =>
|
||||
{
|
||||
options.LoginPath = new PathString("/login");
|
||||
options.AutomaticAuthentication = auto;
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
|
||||
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.ToString().ShouldBe("/CustomRedirect");
|
||||
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
|
||||
if (auto)
|
||||
{
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.ToString().ShouldBe("/CustomRedirect");
|
||||
}
|
||||
}
|
||||
|
||||
private Task SignInAsAlice(HttpContext context)
|
||||
|
|
@ -221,6 +231,37 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieAppliesClaimsTransform()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(options =>
|
||||
{
|
||||
options.SystemClock = clock;
|
||||
},
|
||||
SignInAsAlice,
|
||||
baseAddress: null,
|
||||
claimsTransform: o => o.Transformation = (p =>
|
||||
{
|
||||
if (!p.Identities.Any(i => i.AuthenticationType == "xform"))
|
||||
{
|
||||
// REVIEW: Xform runs twice, once on Authenticate, and then once from the middleware
|
||||
var id = new ClaimsIdentity("xform");
|
||||
id.AddClaim(new Claim("xform", "yup"));
|
||||
p.AddIdentity(id);
|
||||
}
|
||||
return p;
|
||||
}));
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
FindClaimValue(transaction2, "xform").ShouldBe("yup");
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieStopsWorkingAfterExpiration()
|
||||
{
|
||||
|
|
@ -372,6 +413,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
TestServer server = CreateServer(options =>
|
||||
{
|
||||
options.LoginPath = new PathString("/login");
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected", ajaxRequest: true);
|
||||
|
|
@ -478,12 +520,26 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
|||
return me;
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<CookieAuthenticationOptions> configureOptions, Func<HttpContext, Task> testpath = null, Uri baseAddress = null)
|
||||
private static TestServer CreateServer(Action<CookieAuthenticationOptions> configureOptions, Func<HttpContext, Task> testpath = null, Uri baseAddress = null, Action<ClaimsTransformationOptions> claimsTransform = null)
|
||||
{
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddDataProtection());
|
||||
if (claimsTransform != null)
|
||||
{
|
||||
app.UseServices(services => {
|
||||
services.AddDataProtection();
|
||||
services.ConfigureClaimsTransformation(claimsTransform);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseServices(services => services.AddDataProtection());
|
||||
}
|
||||
app.UseCookieAuthentication(configureOptions);
|
||||
if (claimsTransform != null)
|
||||
{
|
||||
app.UseClaimsTransformation();
|
||||
}
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
|||
services.ConfigureCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = "External";
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
services.Configure<ExternalAuthenticationOptions>(options =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
|
@ -18,6 +19,7 @@ using Microsoft.AspNet.Http;
|
|||
using Microsoft.AspNet.Http.Authentication;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
|
@ -50,6 +52,25 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
location.ShouldNotContain("login_hint=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge401WillTriggerRedirection()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/401");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var location = transaction.Response.Headers.Location.ToString();
|
||||
location.ShouldContain("https://accounts.google.com/o/oauth2/auth?response_type=code");
|
||||
location.ShouldContain("&client_id=");
|
||||
location.ShouldContain("&redirect_uri=");
|
||||
location.ShouldContain("&scope=");
|
||||
location.ShouldContain("&state=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetCorrelationCookie()
|
||||
{
|
||||
|
|
@ -63,6 +84,20 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
transaction.SetCookie.Single().ShouldContain(".AspNet.Correlation.Google=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Challenge401WillSetCorrelationCookie()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/401");
|
||||
Console.WriteLine(transaction.SetCookie);
|
||||
transaction.SetCookie.Single().ShouldContain(".AspNet.Correlation.Google=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetDefaultScope()
|
||||
{
|
||||
|
|
@ -78,18 +113,18 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
}
|
||||
|
||||
[Fact(Skip = "Failing due to : https://github.com/aspnet/HttpAbstractions/issues/231")]
|
||||
public async Task ChallengeWillUseOptionsScope()
|
||||
public async Task Challenge401WillSetDefaultScope()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.Scope.Add("https://www.googleapis.com/auth/plus.login");
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
var transaction = await SendAsync(server, "https://example.com/401");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var query = transaction.Response.Headers.Location.Query;
|
||||
query.ShouldContain("&scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
|
||||
query.ShouldContain("&scope=" + Uri.EscapeDataString("openid profile email"));
|
||||
}
|
||||
|
||||
[Fact(Skip = "Failing due to : https://github.com/aspnet/HttpAbstractions/issues/231")]
|
||||
|
|
@ -99,6 +134,7 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.ClientId = "Test Id";
|
||||
options.ClientSecret = "Test Secret";
|
||||
options.AutomaticAuthentication = true;
|
||||
},
|
||||
context =>
|
||||
{
|
||||
|
|
@ -122,10 +158,10 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
var transaction = await SendAsync(server, "https://example.com/challenge2");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var query = transaction.Response.Headers.Location.Query;
|
||||
query.ShouldContain("scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
|
||||
query.ShouldContain("scope=" + UrlEncoder.Default.UrlEncode("https://www.googleapis.com/auth/plus.login"));
|
||||
query.ShouldContain("access_type=offline");
|
||||
query.ShouldContain("approval_prompt=force");
|
||||
query.ShouldContain("login_hint=" + Uri.EscapeDataString("test@example.com"));
|
||||
query.ShouldContain("login_hint=" + UrlEncoder.Default.UrlEncode("test@example.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -149,6 +185,35 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
query.ShouldContain("custom=test");
|
||||
}
|
||||
|
||||
// TODO: Fix these tests to path (Need some test logic for Authenticate("Google") to return a ticket still
|
||||
//[Fact]
|
||||
//public async Task GoogleTurns401To403WhenAuthenticated()
|
||||
//{
|
||||
// TestServer server = CreateServer(options =>
|
||||
// {
|
||||
// options.ClientId = "Test Id";
|
||||
// options.ClientSecret = "Test Secret";
|
||||
// });
|
||||
|
||||
// Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorized");
|
||||
// transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
|
||||
//}
|
||||
|
||||
//[Fact]
|
||||
//public async Task GoogleTurns401To403WhenAutomatic()
|
||||
//{
|
||||
// TestServer server = CreateServer(options =>
|
||||
// {
|
||||
// options.ClientId = "Test Id";
|
||||
// options.ClientSecret = "Test Secret";
|
||||
// options.AutomaticAuthentication = true;
|
||||
// });
|
||||
|
||||
// Debugger.Launch();
|
||||
// Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorizedAuto");
|
||||
// transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
|
||||
//}
|
||||
|
||||
[Fact]
|
||||
public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
|
||||
{
|
||||
|
|
@ -233,6 +298,9 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
transaction.FindClaimValue(ClaimTypes.GivenName).ShouldBe("Test Given Name");
|
||||
transaction.FindClaimValue(ClaimTypes.Surname).ShouldBe("Test Family Name");
|
||||
transaction.FindClaimValue(ClaimTypes.Email).ShouldBe("Test email");
|
||||
|
||||
// Ensure claims transformation
|
||||
transaction.FindClaimValue("xform").ShouldBe("yup");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -421,9 +489,21 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
options.SignInScheme = CookieAuthenticationScheme;
|
||||
});
|
||||
services.ConfigureClaimsTransformation(p =>
|
||||
{
|
||||
var id = new ClaimsIdentity("xform");
|
||||
id.AddClaim(new Claim("xform", "yup"));
|
||||
p.AddIdentity(id);
|
||||
return p;
|
||||
});
|
||||
});
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = CookieAuthenticationScheme;
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
app.UseCookieAuthentication(options => options.AuthenticationScheme = CookieAuthenticationScheme);
|
||||
app.UseGoogleAuthentication(configureOptions);
|
||||
app.UseClaimsTransformation();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
|
|
@ -437,6 +517,18 @@ namespace Microsoft.AspNet.Authentication.Google
|
|||
{
|
||||
Describe(res, context.User);
|
||||
}
|
||||
else if (req.Path == new PathString("/unauthorized"))
|
||||
{
|
||||
// Simulate Authorization failure
|
||||
var result = await context.AuthenticateAsync("Google");
|
||||
res.Challenge("Google");
|
||||
}
|
||||
else if (req.Path == new PathString("/unauthorizedAuto"))
|
||||
{
|
||||
var result = await context.AuthenticateAsync("Google");
|
||||
res.StatusCode = 401;
|
||||
res.Challenge();
|
||||
}
|
||||
else if (req.Path == new PathString("/401"))
|
||||
{
|
||||
res.StatusCode = 401;
|
||||
|
|
|
|||
|
|
@ -169,7 +169,11 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
|
|||
options.SignInScheme = "External";
|
||||
});
|
||||
});
|
||||
app.UseCookieAuthentication(options => options.AuthenticationScheme = "External");
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationScheme = "External";
|
||||
options.AutomaticAuthentication = true;
|
||||
});
|
||||
app.UseMicrosoftAccountAuthentication(configureOptions);
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue