Support custom name and role claims

This commit is contained in:
Hao Kung 2015-05-06 14:24:20 -07:00
parent ce48c1fc7d
commit 434d158c76
12 changed files with 209 additions and 91 deletions

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Authorization
public static AuthorizationPolicy Combine([NotNull] AuthorizationOptions options, [NotNull] IEnumerable<AuthorizeAttribute> attributes)
{
var policyBuilder = new AuthorizationPolicyBuilder();
bool any = false;
var any = false;
foreach (var authorizeAttribute in attributes.OfType<AuthorizeAttribute>())
{
any = true;
@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Authorization
policyBuilder.RequireRole(rolesSplit);
requireAnyAuthenticated = false;
}
string[] authTypesSplit = authorizeAttribute.ActiveAuthenticationSchemes?.Split(',');
var authTypesSplit = authorizeAttribute.ActiveAuthenticationSchemes?.Split(',');
if (authTypesSplit != null && authTypesSplit.Any())
{
foreach (var authType in authTypesSplit)

View File

@ -55,21 +55,13 @@ namespace Microsoft.AspNet.Authorization
public AuthorizationPolicyBuilder RequireClaim([NotNull] string claimType, IEnumerable<string> requiredValues)
{
Requirements.Add(new ClaimsAuthorizationRequirement
{
ClaimType = claimType,
AllowedValues = requiredValues
});
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues));
return this;
}
public AuthorizationPolicyBuilder RequireClaim([NotNull] string claimType)
{
Requirements.Add(new ClaimsAuthorizationRequirement
{
ClaimType = claimType,
AllowedValues = null
});
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues: null));
return this;
}
@ -80,13 +72,13 @@ namespace Microsoft.AspNet.Authorization
public AuthorizationPolicyBuilder RequireRole([NotNull] IEnumerable<string> roles)
{
RequireClaim(ClaimTypes.Role, roles);
Requirements.Add(new RolesAuthorizationRequirement(roles));
return this;
}
public AuthorizationPolicyBuilder RequireUserName([NotNull] string userName)
{
RequireClaim(ClaimTypes.Name, userName);
Requirements.Add(new NameAuthorizationRequirement(userName));
return this;
}

View File

@ -20,12 +20,6 @@ namespace Microsoft.AspNet.Authorization
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
public static Task<bool> AuthorizeAsync([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy)
{
// TODO RENABLE
//if (policy.ActiveAuthenticationSchemes != null && policy.ActiveAuthenticationSchemes.Any() && user != null)
//{
// // Filter the user to only contain the active authentication types
// user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationSchemes.Contains(i.AuthenticationScheme)));
//}
return service.AuthorizeAsync(user, resource, policy.Requirements.ToArray());
}
@ -39,12 +33,6 @@ namespace Microsoft.AspNet.Authorization
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
public static bool Authorize([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy)
{
// TODO: REeanble
//if (policy.ActiveAuthenticationSchemes != null && policy.ActiveAuthenticationSchemes.Any() && user != null)
//{
// // Filter the user to only contain the active authentication types
// user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationSchemes.Contains(i.AuthenticationScheme)));
//}
return service.Authorize(user, resource, policy.Requirements.ToArray());
}

View File

@ -1,15 +1,45 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization
{
// Must contain a claim with the specified name, and at least one of the required values
// If AllowedValues is null or empty, that means any claim is valid
public class ClaimsAuthorizationRequirement : IAuthorizationRequirement
public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
public string ClaimType { get; set; }
public IEnumerable<string> AllowedValues { get; set; }
public ClaimsAuthorizationRequirement([NotNull] string claimType, IEnumerable<string> allowedValues)
{
ClaimType = claimType;
AllowedValues = allowedValues;
}
public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; }
public override void Handle(AuthorizationContext context, ClaimsAuthorizationRequirement requirement)
{
if (context.User != null)
{
var found = false;
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
}
else
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
}
if (found)
{
context.Succeed(requirement);
}
}
}
}
}

View File

@ -1,9 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Authorization;
using System.Linq;
namespace Microsoft.AspNet.Authorization
{
public class DenyAnonymousAuthorizationRequirement : IAuthorizationRequirement { }
public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{
public override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization
{
/// <summary>
/// Requirement that ensures a specific Name
/// </summary>
public class NameAuthorizationRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement
{
public NameAuthorizationRequirement([NotNull] string requiredName)
{
RequiredName = requiredName;
}
public string RequiredName { get; }
public override void Handle(AuthorizationContext context, NameAuthorizationRequirement requirement)
{
if (context.User != null)
{
// REVIEW: Do we need to do normalization? casing/loc?
if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName)))
{
context.Succeed(requirement);
}
}
}
}
}

View File

@ -10,54 +10,6 @@ namespace Microsoft.AspNet.Authorization
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Authorization.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key.
/// </summary>
internal static string Exception_DefaultDpapiRequiresAppNameKey
{
get { return GetString("Exception_DefaultDpapiRequiresAppNameKey"); }
}
/// <summary>
/// The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key.
/// </summary>
internal static string FormatException_DefaultDpapiRequiresAppNameKey()
{
return GetString("Exception_DefaultDpapiRequiresAppNameKey");
}
/// <summary>
/// The state passed to UnhookAuthentication may only be the return value from HookAuthentication.
/// </summary>
internal static string Exception_UnhookAuthenticationStateType
{
get { return GetString("Exception_UnhookAuthenticationStateType"); }
}
/// <summary>
/// The state passed to UnhookAuthentication may only be the return value from HookAuthentication.
/// </summary>
internal static string FormatException_UnhookAuthenticationStateType()
{
return GetString("Exception_UnhookAuthenticationStateType");
}
/// <summary>
/// The AuthenticationTokenProvider's required synchronous events have not been registered.
/// </summary>
internal static string Exception_AuthenticationTokenDoesNotProvideSyncMethods
{
get { return GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods"); }
}
/// <summary>
/// The AuthenticationTokenProvider's required synchronous events have not been registered.
/// </summary>
internal static string FormatException_AuthenticationTokenDoesNotProvideSyncMethods()
{
return GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods");
}
/// <summary>
/// The AuthorizationPolicy named: '{0}' was not found.
/// </summary>
@ -74,6 +26,22 @@ namespace Microsoft.AspNet.Authorization
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_AuthorizationPolicyNotFound"), p0);
}
/// <summary>
/// At least one role must be specified.
/// </summary>
internal static string Exception_RoleRequirementEmpty
{
get { return GetString("Exception_RoleRequirementEmpty"); }
}
/// <summary>
/// At least one role must be specified.
/// </summary>
internal static string FormatException_RoleRequirementEmpty()
{
return GetString("Exception_RoleRequirementEmpty");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -117,16 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Exception_DefaultDpapiRequiresAppNameKey" xml:space="preserve">
<value>The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key.</value>
</data>
<data name="Exception_UnhookAuthenticationStateType" xml:space="preserve">
<value>The state passed to UnhookAuthentication may only be the return value from HookAuthentication.</value>
</data>
<data name="Exception_AuthenticationTokenDoesNotProvideSyncMethods" xml:space="preserve">
<value>The AuthenticationTokenProvider's required synchronous events have not been registered.</value>
</data>
<data name="Exception_AuthorizationPolicyNotFound" xml:space="preserve">
<value>The AuthorizationPolicy named: '{0}' was not found.</value>
</data>
<data name="Exception_RoleRequirementEmpty" xml:space="preserve">
<value>At least one role must be specified.</value>
</data>
</root>

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization
{
// Must belong to with one of specified roles
// If AllowedRoles is null or empty, that means any role is valid
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public RolesAuthorizationRequirement([NotNull] IEnumerable<string> allowedRoles)
{
if (allowedRoles.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
}
AllowedRoles = allowedRoles;
}
public IEnumerable<string> AllowedRoles { get; }
public override void Handle(AuthorizationContext context, RolesAuthorizationRequirement requirement)
{
if (context.User != null)
{
bool found = false;
if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
{
// Review: What do we want to do here? No roles requested is auto success?
}
else
{
found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
}
if (found)
{
context.Succeed(requirement);
}
}
}
}
}

View File

@ -23,8 +23,6 @@ namespace Microsoft.Framework.DependencyInjection
{
services.AddOptions();
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
services.AddTransient<IAuthorizationHandler, ClaimsAuthorizationHandler>();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
services.AddTransient<IAuthorizationHandler, PassThroughAuthorizationHandler>();
return services;
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. 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.Linq;
using Microsoft.AspNet.Authorization;
using Xunit;
@ -9,6 +10,12 @@ namespace Microsoft.AspNet.Authroization.Test
{
public class AuthorizationPolicyFacts
{
[Fact]
public void RequireRoleThrowsIfEmpty()
{
Assert.Throws<InvalidOperationException>(() => new AuthorizationPolicyBuilder().RequireRole());
}
[Fact]
public void CanCombineAuthorizeAttributes()
{
@ -32,7 +39,8 @@ namespace Microsoft.AspNet.Authroization.Test
Assert.True(combined.ActiveAuthenticationSchemes.Contains("roles"));
Assert.Equal(4, combined.Requirements.Count());
Assert.True(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement));
Assert.Equal(3, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());
Assert.Equal(2, combined.Requirements.OfType<ClaimsAuthorizationRequirement>().Count());
Assert.Equal(1, combined.Requirements.OfType<RolesAuthorizationRequirement>().Count());
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
@ -504,6 +505,50 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.True(allowed);
}
[Fact]
public async Task CanRequireUserNameWithDiffClaimType()
{
// Arrange
var authorizationService = BuildAuthorizationService(services =>
{
services.ConfigureAuthorization(options =>
{
options.AddPolicy("Hao", policy => policy.RequireUserName("Hao"));
});
});
var identity = new ClaimsIdentity("AuthType", "Name", "Role");
identity.AddClaim(new Claim("Name", "Hao"));
var user = new ClaimsPrincipal(identity);
// Act
var allowed = await authorizationService.AuthorizeAsync(user, null, "Hao");
// Assert
Assert.True(allowed);
}
[Fact]
public async Task CanRequireRoleWithDiffClaimType()
{
// Arrange
var authorizationService = BuildAuthorizationService(services =>
{
services.ConfigureAuthorization(options =>
{
options.AddPolicy("Hao", policy => policy.RequireRole("Hao"));
});
});
var identity = new ClaimsIdentity("AuthType", "Name", "Role");
identity.AddClaim(new Claim("Role", "Hao"));
var user = new ClaimsPrincipal(identity);
// Act
var allowed = await authorizationService.AuthorizeAsync(user, null, "Hao");
// Assert
Assert.True(allowed);
}
[Fact]
public async Task CanApproveAnyAuthenticatedUser()
{