diff --git a/src/Microsoft.AspNet.Security/AuthorizationContext.cs b/src/Microsoft.AspNet.Security/AuthorizationContext.cs index 0c6dc06197..81d00bdc9d 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationContext.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationContext.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Security { @@ -13,27 +12,23 @@ namespace Microsoft.AspNet.Security /// public class AuthorizationContext { - private HashSet _pendingRequirements = new HashSet(); + private HashSet _pendingRequirements; private bool _failCalled; private bool _succeedCalled; public AuthorizationContext( - [NotNull] AuthorizationPolicy policy, - HttpContext context, + [NotNull] IEnumerable requirements, + ClaimsPrincipal user, object resource) { - Policy = policy; - Context = context; + Requirements = requirements; + User = user; Resource = resource; - foreach (var req in Policy.Requirements) - { - _pendingRequirements.Add(req); - } + _pendingRequirements = new HashSet(requirements); } - public AuthorizationPolicy Policy { get; private set; } - public ClaimsPrincipal User { get { return Context.User; } } - public HttpContext Context { get; private set; } + public IEnumerable Requirements { get; private set; } + public ClaimsPrincipal User { get; private set; } public object Resource { get; private set; } public IEnumerable PendingRequirements { get { return _pendingRequirements; } } diff --git a/src/Microsoft.AspNet.Security/AuthorizationHandler.cs b/src/Microsoft.AspNet.Security/AuthorizationHandler.cs index d913318700..e1b27e8633 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationHandler.cs @@ -1,58 +1,68 @@ // 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.Linq; using System.Threading.Tasks; namespace Microsoft.AspNet.Security { - // Music store use case - - // await AuthorizeAsync(user, "Edit", albumInstance); - - // No policy name needed because this is auto based on resource (operation is the policy name) - //RegisterOperation which auto generates the policy for Authorize - //bool AuthorizeAsync(ClaimsPrincipal, string operation, TResource instance) - //bool AuthorizeAsync(IAuthorization, ClaimsPrincipal, string operation, TResource instance) public abstract class AuthorizationHandler : IAuthorizationHandler where TRequirement : IAuthorizationRequirement { - public async Task HandleAsync(AuthorizationContext context) + public void Handle(AuthorizationContext context) { - foreach (var req in context.Policy.Requirements.OfType()) + foreach (var req in context.Requirements.OfType()) { - if (await CheckAsync(context, req)) + Handle(context, req); + } + } + + public virtual Task HandleAsync(AuthorizationContext context) + { + Handle(context); + return Task.FromResult(0); + } + + // REVIEW: do we need an async hook too? + public abstract void Handle(AuthorizationContext context, TRequirement requirement); + } + + public abstract class AuthorizationHandler : IAuthorizationHandler + where TResource : class + where TRequirement : IAuthorizationRequirement + { + public virtual async Task HandleAsync(AuthorizationContext context) + { + var resource = context.Resource as TResource; + // REVIEW: should we allow null resources? + if (resource != null) + { + foreach (var req in context.Requirements.OfType()) { - context.Succeed(req); - } - else - { - context.Fail(); + await HandleAsync(context, req, resource); } } } - public abstract Task CheckAsync(AuthorizationContext context, TRequirement requirement); + public virtual Task HandleAsync(AuthorizationContext context, TRequirement requirement, TResource resource) + { + Handle(context, requirement, resource); + return Task.FromResult(0); + } + + public virtual void Handle(AuthorizationContext context) + { + var resource = context.Resource as TResource; + // REVIEW: should we allow null resources? + if (resource != null) + { + foreach (var req in context.Requirements.OfType()) + { + Handle(context, req, resource); + } + } + } + + public abstract void Handle(AuthorizationContext context, TRequirement requirement, TResource resource); } - - // TODO: - //public abstract class AuthorizationHandler : AuthorizationHandler - // where TResource : class - // where TRequirement : IAuthorizationRequirement - //{ - // public override Task HandleAsync(AuthorizationContext context) - // { - // var resource = context.Resource as TResource; - // if (resource != null) - // { - // return HandleAsync(context, resource); - // } - - // return Task.FromResult(0); - - // } - - // public abstract Task HandleAsync(AuthorizationContext context, TResource resource); - //} } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs index d142eb1b60..924f9dbd2b 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs @@ -1,7 +1,9 @@ // 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.Collections.Generic; +using System.Linq; namespace Microsoft.AspNet.Security { @@ -9,11 +11,67 @@ namespace Microsoft.AspNet.Security { public AuthorizationPolicy(IEnumerable requirements, IEnumerable activeAuthenticationTypes) { - Requirements = requirements; - ActiveAuthenticationTypes = activeAuthenticationTypes; + Requirements = new List(requirements).AsReadOnly(); + ActiveAuthenticationTypes = new List(activeAuthenticationTypes).AsReadOnly(); } - public IEnumerable Requirements { get; private set; } - public IEnumerable ActiveAuthenticationTypes { get; private set; } + public IReadOnlyList Requirements { get; private set; } + public IReadOnlyList ActiveAuthenticationTypes { get; private set; } + + public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies) + { + return Combine((IEnumerable)policies); + } + + // TODO: Add unit tests + public static AuthorizationPolicy Combine([NotNull] IEnumerable policies) + { + var builder = new AuthorizationPolicyBuilder(); + foreach (var policy in policies) + { + builder.Combine(policy); + } + return builder.Build(); + } + + public static AuthorizationPolicy Combine([NotNull] AuthorizationOptions options, [NotNull] IEnumerable attributes) + { + var policyBuilder = new AuthorizationPolicyBuilder(); + bool any = false; + foreach (var authorizeAttribute in attributes.OfType()) + { + any = true; + var requireAnyAuthenticated = true; + if (!string.IsNullOrWhiteSpace(authorizeAttribute.Policy)) + { + var policy = options.GetPolicy(authorizeAttribute.Policy); + if (policy == null) + { + throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeAttribute.Policy)); + } + policyBuilder.Combine(policy); + requireAnyAuthenticated = false; + } + var rolesSplit = authorizeAttribute.Roles?.Split(','); + if (rolesSplit != null && rolesSplit.Any()) + { + policyBuilder.RequiresRole(rolesSplit); + requireAnyAuthenticated = false; + } + string[] authTypesSplit = authorizeAttribute.ActiveAuthenticationTypes?.Split(','); + if (authTypesSplit != null && authTypesSplit.Any()) + { + foreach (var authType in authTypesSplit) + { + policyBuilder.ActiveAuthenticationTypes.Add(authType); + } + } + if (requireAnyAuthenticated) + { + policyBuilder.RequireAuthenticatedUser(); + } + } + return any ? policyBuilder.Build() : null; + } } } diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs index 7b8617f543..bf97fbae2c 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Security return this; } - public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy) + public AuthorizationPolicyBuilder Combine([NotNull] AuthorizationPolicy policy) { AddAuthenticationTypes(policy.ActiveAuthenticationTypes.ToArray()); AddRequirements(policy.Requirements.ToArray()); @@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Security } public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues) + { + return RequiresClaim(claimType, (IEnumerable)requiredValues); + } + + public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, IEnumerable requiredValues) { Requirements.Add(new ClaimsAuthorizationRequirement { @@ -68,6 +73,11 @@ namespace Microsoft.AspNet.Security } public AuthorizationPolicyBuilder RequiresRole([NotNull] params string[] roles) + { + return RequiresRole((IEnumerable)roles); + } + + public AuthorizationPolicyBuilder RequiresRole([NotNull] IEnumerable roles) { RequiresClaim(ClaimTypes.Role, roles); return this; @@ -81,7 +91,7 @@ namespace Microsoft.AspNet.Security public AuthorizationPolicy Build() { - return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes); + return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes.Distinct()); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs b/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs new file mode 100644 index 0000000000..e3e416c897 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs @@ -0,0 +1,49 @@ +// 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.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security +{ + public static class AuthorizationServiceExtensions + { + /// + /// Checks if a user meets a specific authorization policy + /// + /// The authorization service. + /// The user to check the policy against. + /// The resource the policy should be checked with. + /// The policy to check against a specific context. + /// true when the user fulfills the policy, false otherwise. + public static Task AuthorizeAsync([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy) + { + if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() && user != null) + { + // Filter the user to only contain the active authentication types + user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationTypes.Contains(i.AuthenticationType))); + } + return service.AuthorizeAsync(user, resource, policy.Requirements.ToArray()); + } + + /// + /// Checks if a user meets a specific authorization policy + /// + /// The authorization service. + /// The user to check the policy against. + /// The resource the policy should be checked with. + /// The policy to check against a specific context. + /// true when the user fulfills the policy, false otherwise. + public static bool Authorize([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy) + { + if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() && user != null) + { + // Filter the user to only contain the active authentication types + user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationTypes.Contains(i.AuthenticationType))); + } + return service.Authorize(user, resource, policy.Requirements.ToArray()); + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthorizeAttribute.cs b/src/Microsoft.AspNet.Security/AuthorizeAttribute.cs new file mode 100644 index 0000000000..bdaefafbf1 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizeAttribute.cs @@ -0,0 +1,25 @@ +// 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; + +namespace Microsoft.AspNet.Security +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class AuthorizeAttribute : Attribute + { + public AuthorizeAttribute() { } + + public AuthorizeAttribute(string policy) + { + Policy = policy; + } + + public string Policy { get; set; } + + // REVIEW: can we get rid of the , deliminated in Roles/AuthTypes + public string Roles { get; set; } + + public string ActiveAuthenticationTypes { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs index 9f3f58c3ac..9aa95ef606 100644 --- a/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs @@ -3,30 +3,30 @@ using System; using System.Linq; -using System.Threading.Tasks; namespace Microsoft.AspNet.Security { public class ClaimsAuthorizationHandler : AuthorizationHandler { - public override Task CheckAsync(AuthorizationContext context, ClaimsAuthorizationRequirement requirement) + public override void Handle(AuthorizationContext context, ClaimsAuthorizationRequirement requirement) { - if (context.Context.User == null) + if (context.User != null) { - return Task.FromResult(false); + bool 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); + } } - - bool found = false; - if (requirement.AllowedValues == null || !requirement.AllowedValues.Any()) - { - found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)); - } - else - { - found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase) - && requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal)); - } - return Task.FromResult(found); } } } diff --git a/src/Microsoft.AspNet.Security/ClaimsTransformationOptions.cs b/src/Microsoft.AspNet.Security/ClaimsTransformationOptions.cs new file mode 100644 index 0000000000..4684ad69d0 --- /dev/null +++ b/src/Microsoft.AspNet.Security/ClaimsTransformationOptions.cs @@ -0,0 +1,15 @@ +// 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.Security +{ + public class ClaimsTransformationOptions + { + public Func> TransformAsync { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs b/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs index d20cf65445..943fbce9fb 100644 --- a/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs +++ b/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Http; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Security @@ -21,47 +20,44 @@ namespace Microsoft.AspNet.Security _options = options.Options; } - public Task AuthorizeAsync([NotNull] string policyName, HttpContext context, object resource = null) + public bool Authorize(ClaimsPrincipal user, object resource, string policyName) + { + var policy = _options.GetPolicy(policyName); + if (policy == null) + { + return false; + } + return this.Authorize(user, resource, policy); + } + + public bool Authorize(ClaimsPrincipal user, object resource, params IAuthorizationRequirement[] requirements) + { + var authContext = new AuthorizationContext(requirements, user, resource); + foreach (var handler in _handlers) + { + handler.Handle(authContext); + } + return authContext.HasSucceeded; + } + + public async Task AuthorizeAsync(ClaimsPrincipal user, object resource, params IAuthorizationRequirement[] requirements) + { + var authContext = new AuthorizationContext(requirements, user, resource); + foreach (var handler in _handlers) + { + await handler.HandleAsync(authContext); + } + return authContext.HasSucceeded; + } + + public Task AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName) { var policy = _options.GetPolicy(policyName); if (policy == null) { return Task.FromResult(false); } - return AuthorizeAsync(policy, context, resource); - } - - public async Task AuthorizeAsync([NotNull] AuthorizationPolicy policy, [NotNull] HttpContext context, object resource = null) - { - var user = context.User; - try - { - // Generate the user identities if policy specified the AuthTypes - if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() ) - { - var principal = new ClaimsPrincipal(); - - var results = await context.AuthenticateAsync(policy.ActiveAuthenticationTypes); - // REVIEW: re requesting the identities fails for MVC currently, so we only request if not found - foreach (var result in results) - { - principal.AddIdentity(result.Identity); - } - context.User = principal; - } - - var authContext = new AuthorizationContext(policy, context, resource); - - foreach (var handler in _handlers) - { - await handler.HandleAsync(authContext); - } - return authContext.HasSucceeded; - } - finally - { - context.User = user; - } + return this.AuthorizeAsync(user, resource, policy); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs index 878a32ec92..952ed30b86 100644 --- a/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs @@ -7,14 +7,17 @@ namespace Microsoft.AspNet.Security { public class DenyAnonymousAuthorizationHandler : AuthorizationHandler { - public override Task CheckAsync(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement) + public override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement) { var user = context.User; var userIsAnonymous = user == null || user.Identity == null || !user.Identity.IsAuthenticated; - return Task.FromResult(!userIsAnonymous); + if (!userIsAnonymous) + { + context.Succeed(requirement); + } } } } diff --git a/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs index 975a305b86..82eea9ff2f 100644 --- a/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs @@ -8,6 +8,6 @@ namespace Microsoft.AspNet.Security public interface IAuthorizationHandler { Task HandleAsync(AuthorizationContext context); - //void Handle(AuthorizationContext context); + void Handle(AuthorizationContext context); } } diff --git a/src/Microsoft.AspNet.Security/IAuthorizationService.cs b/src/Microsoft.AspNet.Security/IAuthorizationService.cs index 8bcafda3d7..317e1ae283 100644 --- a/src/Microsoft.AspNet.Security/IAuthorizationService.cs +++ b/src/Microsoft.AspNet.Security/IAuthorizationService.cs @@ -1,8 +1,8 @@ // 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.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Security { @@ -12,21 +12,39 @@ namespace Microsoft.AspNet.Security public interface IAuthorizationService { /// - /// Checks if a user meets a specific authorization policy + /// Checks if a user meets a specific set of requirements for the specified resource /// - /// The policy to check against a specific context. - /// The HttpContext to check the policy against. - /// The resource the policy should be checked with. - /// true when the user fulfills the policy, false otherwise. - Task AuthorizeAsync(string policyName, HttpContext context, object resource = null); + /// + /// + /// + /// + Task AuthorizeAsync(ClaimsPrincipal user, object resource, params IAuthorizationRequirement[] requirements); + + /// + /// Checks if a user meets a specific set of requirements for the specified resource + /// + /// + /// + /// + /// + bool Authorize(ClaimsPrincipal user, object resource, params IAuthorizationRequirement[] requirements); /// /// Checks if a user meets a specific authorization policy /// - /// The policy to check against a specific context. - /// The HttpContext to check the policy against. + /// The user to check the policy against. /// The resource the policy should be checked with. + /// The name of the policy to check against a specific context. /// true when the user fulfills the policy, false otherwise. - Task AuthorizeAsync(AuthorizationPolicy policy, HttpContext context, object resource = null); + Task AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName); + + /// + /// Checks if a user meets a specific authorization policy + /// + /// The user to check the policy against. + /// The resource the policy should be checked with. + /// The name of the policy to check against a specific context. + /// true when the user fulfills the policy, false otherwise. + bool Authorize(ClaimsPrincipal user, object resource, string policyName); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/OperationAuthorizationRequirement.cs b/src/Microsoft.AspNet.Security/OperationAuthorizationRequirement.cs new file mode 100644 index 0000000000..e7e08d1959 --- /dev/null +++ b/src/Microsoft.AspNet.Security/OperationAuthorizationRequirement.cs @@ -0,0 +1,10 @@ +// 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. + +namespace Microsoft.AspNet.Security +{ + public class OperationAuthorizationRequirement : IAuthorizationRequirement + { + public string Name { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs index 837b2f2676..a2173f1e02 100644 --- a/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs +++ b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs @@ -10,10 +10,18 @@ namespace Microsoft.AspNet.Security { public async Task HandleAsync(AuthorizationContext context) { - foreach (var handler in context.Policy.Requirements.OfType()) + foreach (var handler in context.Requirements.OfType()) { await handler.HandleAsync(context); } } + + public void Handle(AuthorizationContext context) + { + foreach (var handler in context.Requirements.OfType()) + { + handler.Handle(context); + } + } } } diff --git a/src/Microsoft.AspNet.Security/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Security/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..3490d3ea71 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Properties/Resources.Designer.cs @@ -0,0 +1,94 @@ +// +namespace Microsoft.AspNet.Security +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNet.Security.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key. + /// + internal static string Exception_DefaultDpapiRequiresAppNameKey + { + get { return GetString("Exception_DefaultDpapiRequiresAppNameKey"); } + } + + /// + /// The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key. + /// + internal static string FormatException_DefaultDpapiRequiresAppNameKey() + { + return GetString("Exception_DefaultDpapiRequiresAppNameKey"); + } + + /// + /// The state passed to UnhookAuthentication may only be the return value from HookAuthentication. + /// + internal static string Exception_UnhookAuthenticationStateType + { + get { return GetString("Exception_UnhookAuthenticationStateType"); } + } + + /// + /// The state passed to UnhookAuthentication may only be the return value from HookAuthentication. + /// + internal static string FormatException_UnhookAuthenticationStateType() + { + return GetString("Exception_UnhookAuthenticationStateType"); + } + + /// + /// The AuthenticationTokenProvider's required synchronous events have not been registered. + /// + internal static string Exception_AuthenticationTokenDoesNotProvideSyncMethods + { + get { return GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods"); } + } + + /// + /// The AuthenticationTokenProvider's required synchronous events have not been registered. + /// + internal static string FormatException_AuthenticationTokenDoesNotProvideSyncMethods() + { + return GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods"); + } + + /// + /// The AuthorizationPolicy named: '{0}' was not found. + /// + internal static string Exception_AuthorizationPolicyNotFound + { + get { return GetString("Exception_AuthorizationPolicyNotFound"); } + } + + /// + /// The AuthorizationPolicy named: '{0}' was not found. + /// + internal static string FormatException_AuthorizationPolicyNotFound(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Exception_AuthorizationPolicyNotFound"), p0); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Resources.Designer.cs b/src/Microsoft.AspNet.Security/Resources.Designer.cs deleted file mode 100644 index 485da1825d..0000000000 --- a/src/Microsoft.AspNet.Security/Resources.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34003 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.AspNet.Security { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The AuthenticationTokenProvider's required synchronous events have not been registered.. - /// - internal static string Exception_AuthenticationTokenDoesNotProvideSyncMethods { - get { - return ResourceManager.GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The default data protection provider may only be used when the IApplicationBuilder.Properties contains an appropriate 'host.AppName' key.. - /// - internal static string Exception_DefaultDpapiRequiresAppNameKey { - get { - return ResourceManager.GetString("Exception_DefaultDpapiRequiresAppNameKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A default value for SignInAsAuthenticationType was not found in IApplicationBuilder Properties. This can happen if your authentication middleware are added in the wrong order, or if one is missing.. - /// - internal static string Exception_MissingDefaultSignInAsAuthenticationType { - get { - return ResourceManager.GetString("Exception_MissingDefaultSignInAsAuthenticationType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The state passed to UnhookAuthentication may only be the return value from HookAuthentication.. - /// - internal static string Exception_UnhookAuthenticationStateType { - get { - return ResourceManager.GetString("Exception_UnhookAuthenticationStateType", resourceCulture); - } - } - } -} diff --git a/src/Microsoft.AspNet.Security/Resources.resx b/src/Microsoft.AspNet.Security/Resources.resx index 77060045e0..3e72c4a2ce 100644 --- a/src/Microsoft.AspNet.Security/Resources.resx +++ b/src/Microsoft.AspNet.Security/Resources.resx @@ -126,4 +126,7 @@ The AuthenticationTokenProvider's required synchronous events have not been registered. + + The AuthorizationPolicy named: '{0}' was not found. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs index 41b0978cdf..2cb0f5aca5 100644 --- a/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs @@ -9,6 +9,11 @@ namespace Microsoft.Framework.DependencyInjection { public static class ServiceCollectionExtensions { + public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action configure) + { + return services.Configure(configure); + } + public static IServiceCollection ConfigureAuthorization([NotNull] this IServiceCollection services, [NotNull] Action configure) { return services.Configure(configure); diff --git a/test/Microsoft.AspNet.Security.Test/AuthorizationPolicyFacts.cs b/test/Microsoft.AspNet.Security.Test/AuthorizationPolicyFacts.cs new file mode 100644 index 0000000000..57c71dde40 --- /dev/null +++ b/test/Microsoft.AspNet.Security.Test/AuthorizationPolicyFacts.cs @@ -0,0 +1,37 @@ +// 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.Linq; +using Xunit; + +namespace Microsoft.AspNet.Security.Test +{ + public class AuthorizationPolicyFacts + { + [Fact] + public void CanCombineAuthorizeAttributes() + { + // Arrange + var attributes = new AuthorizeAttribute[] { + new AuthorizeAttribute(), + new AuthorizeAttribute("1") { ActiveAuthenticationTypes = "dupe" }, + new AuthorizeAttribute("2") { ActiveAuthenticationTypes = "dupe" }, + new AuthorizeAttribute { Roles = "r1,r2", ActiveAuthenticationTypes = "roles" }, + }; + var options = new AuthorizationOptions(); + options.AddPolicy("1", policy => policy.RequiresClaim("1")); + options.AddPolicy("2", policy => policy.RequiresClaim("2")); + + // Act + var combined = AuthorizationPolicy.Combine(options, attributes); + + // Assert + Assert.Equal(2, combined.ActiveAuthenticationTypes.Count()); + Assert.True(combined.ActiveAuthenticationTypes.Contains("dupe")); + Assert.True(combined.ActiveAuthenticationTypes.Contains("roles")); + Assert.Equal(4, combined.Requirements.Count()); + Assert.True(combined.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement)); + Assert.Equal(3, combined.Requirements.OfType().Count()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs index 1ba7054390..9beae1c6d6 100644 --- a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs +++ b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs @@ -3,13 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Security; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; -using Moq; using Xunit; namespace Microsoft.AspNet.Security.Test @@ -27,23 +25,12 @@ namespace Microsoft.AspNet.Security.Test return services.BuildServiceProvider().GetRequiredService(); } - private Mock SetupContext(params ClaimsIdentity[] ids) + [Fact] + public void AuthorizeCombineThrowsOnUnknownPolicy() { - var context = new Mock(); - context.SetupProperty(c => c.User); - var user = new ClaimsPrincipal(); - user.AddIdentities(ids); - context.Object.User = user; - if (ids != null) - { - var results = new List(); - foreach (var id in ids) - { - results.Add(new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription())); - } - context.Setup(c => c.AuthenticateAsync(It.IsAny>())).ReturnsAsync(results).Verifiable(); - } - return context; + Assert.Throws(() => AuthorizationPolicy.Combine(new AuthorizationOptions(), new AuthorizeAttribute[] { + new AuthorizeAttribute { Policy = "Wut" } + })); } [Fact] @@ -57,10 +44,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); + var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.True(allowed); @@ -77,10 +64,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); + var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.True(allowed); @@ -97,7 +84,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewPage"), @@ -107,7 +94,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.True(allowed); @@ -124,7 +111,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("SomethingElse", "CanViewPage"), @@ -133,7 +120,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -150,7 +137,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("SomethingElse", "CanViewPage"), @@ -159,7 +146,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -176,7 +163,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewComment"), @@ -185,7 +172,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -202,14 +189,14 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[0], "Basic") ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -226,11 +213,9 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext(); - context.Object.User = null; // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(null, null, "Basic"); // Assert Assert.False(allowed); @@ -247,10 +232,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext(new ClaimsIdentity()); + var user = new ClaimsPrincipal(new ClaimsIdentity()); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -267,7 +252,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewPage"), @@ -276,7 +261,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.True(allowed); @@ -287,7 +272,7 @@ namespace Microsoft.AspNet.Security.Test { // Arrange var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewComment"), @@ -296,7 +281,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -309,7 +294,7 @@ namespace Microsoft.AspNet.Security.Test var policy = new AuthorizationPolicyBuilder().RequiresRole("Administrator") .RequiresClaim(ClaimTypes.Role, "User"); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim(ClaimTypes.Role, "User"), @@ -319,7 +304,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.True(allowed); @@ -331,7 +316,7 @@ namespace Microsoft.AspNet.Security.Test // Arrange var policy = new AuthorizationPolicyBuilder().RequiresClaim(ClaimTypes.Role); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim(ClaimTypes.Role, ""), @@ -340,7 +325,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.True(allowed); @@ -352,12 +337,46 @@ namespace Microsoft.AspNet.Security.Test // Arrange var policy = new AuthorizationPolicyBuilder("AuthType").RequiresClaim(ClaimTypes.Name); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "Name") }, "AuthType") ); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task Authorize_PolicyWillFilterAuthenticationType() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("Bogus").RequiresClaim(ClaimTypes.Name); + var authorizationService = BuildAuthorizationService(); + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "Name") }, "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task Authorize_PolicyCanFilterMultipleAuthenticationType() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("One", "Two").RequiresClaim(ClaimTypes.Name, "one").RequiresClaim(ClaimTypes.Name, "two"); + var authorizationService = BuildAuthorizationService(); + var user = new ClaimsPrincipal(); + user.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "one") }, "One")); + user.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "two") }, "Two")); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.True(allowed); @@ -369,12 +388,12 @@ namespace Microsoft.AspNet.Security.Test // Arrange var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin"); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Admin") }, "AuthType") ); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.True(allowed); @@ -386,11 +405,11 @@ namespace Microsoft.AspNet.Security.Test // Arrange var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin", "Users"); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Users") }, "AuthType")); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.True(allowed); @@ -402,7 +421,7 @@ namespace Microsoft.AspNet.Security.Test // Arrange var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); var authorizationService = BuildAuthorizationService(); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim(ClaimTypes.Role, "Nope"), @@ -411,7 +430,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, policy.Build()); // Assert Assert.False(allowed); @@ -428,7 +447,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => policy.RequiresRole("Admin", "Users")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { }, @@ -436,7 +455,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -453,7 +472,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Basic", policy => { }); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim(ClaimTypes.Name, "Name"), @@ -462,7 +481,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Basic"); // Assert Assert.False(allowed); @@ -479,7 +498,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser()); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim(ClaimTypes.Name, "Name"), @@ -488,7 +507,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Any", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Any"); // Assert Assert.True(allowed); @@ -505,10 +524,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser()); }); }); - var context = SetupContext(new ClaimsIdentity()); + var user = new ClaimsPrincipal(new ClaimsIdentity()); // Act - var allowed = await authorizationService.AuthorizeAsync("Any", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Any"); // Assert Assert.False(allowed); @@ -517,9 +536,9 @@ namespace Microsoft.AspNet.Security.Test public class CustomRequirement : IAuthorizationRequirement { } public class CustomHandler : AuthorizationHandler { - public override Task CheckAsync(AuthorizationContext context, CustomRequirement requirement) + public override void Handle(AuthorizationContext context, CustomRequirement requirement) { - return Task.FromResult(true); + context.Succeed(requirement); } } @@ -534,10 +553,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement())); }); }); - var context = SetupContext(); + var user = new ClaimsPrincipal(); // Act - var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Custom"); // Assert Assert.False(allowed); @@ -555,10 +574,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement())); }); }); - var context = SetupContext(); + var user = new ClaimsPrincipal(); // Act - var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Custom"); // Assert Assert.True(allowed); @@ -573,9 +592,11 @@ namespace Microsoft.AspNet.Security.Test public bool Succeed { get; set; } - public override Task CheckAsync(AuthorizationContext context, PassThroughRequirement requirement) + public override void Handle(AuthorizationContext context, PassThroughRequirement requirement) { - return Task.FromResult(Succeed); + if (Succeed) { + context.Succeed(requirement); + } } } @@ -592,10 +613,10 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Passthrough", policy => policy.Requirements.Add(new PassThroughRequirement(shouldSucceed))); }); }); - var context = SetupContext(); + var user = new ClaimsPrincipal(); // Act - var allowed = await authorizationService.AuthorizeAsync("Passthrough", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Passthrough"); // Assert Assert.Equal(shouldSucceed, allowed); @@ -609,10 +630,10 @@ namespace Microsoft.AspNet.Security.Test services.ConfigureAuthorization(options => { var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build(); - options.AddPolicy("Combineed", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); + options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Base", "Value"), @@ -622,7 +643,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Combined"); // Assert Assert.True(allowed); @@ -639,7 +660,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Claim", "Exists") @@ -648,7 +669,7 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Combined"); // Assert Assert.False(allowed); @@ -665,7 +686,7 @@ namespace Microsoft.AspNet.Security.Test options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists")); }); }); - var context = SetupContext( + var user = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { new Claim("Base", "Value"), @@ -674,10 +695,88 @@ namespace Microsoft.AspNet.Security.Test ); // Act - var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object); + var allowed = await authorizationService.AuthorizeAsync(user, null, "Combined"); // Assert Assert.False(allowed); } + + public class ExpenseReport { } + + public static class Operations + { + public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" }; + public static OperationAuthorizationRequirement Create = new OperationAuthorizationRequirement { Name = "Create" }; + public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" }; + } + + public class ExpenseReportAuthorizationHandler : AuthorizationHandler + { + public ExpenseReportAuthorizationHandler(IEnumerable authorized) + { + _allowed = authorized; + } + + private IEnumerable _allowed; + + public override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement, ExpenseReport resource) + { + if (_allowed.Contains(requirement)) + { + context.Succeed(requirement); + } + } + } + + public class SuperUserHandler : AuthorizationHandler + { + public override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement) + { + if (context.User.HasClaim("SuperUser", "yes")) + { + context.Succeed(requirement); + } + } + } + + public async Task CanAuthorizeAllSuperuserOperations() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.AddInstance(new ExpenseReportAuthorizationHandler(new OperationAuthorizationRequirement[] { Operations.Edit })); + services.AddTransient(); + }); + var user = new ClaimsPrincipal( + new ClaimsIdentity( + new Claim[] { + new Claim("SuperUser", "yes"), + }, + "AuthType") + ); + + // Act + // Assert + Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Edit)); + Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Delete)); + Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Create)); + } + + public async Task CanAuthorizeOnlyAllowedOperations() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.AddInstance(new ExpenseReportAuthorizationHandler(new OperationAuthorizationRequirement[] { Operations.Edit })); + services.AddTransient(); + }); + var user = new ClaimsPrincipal(); + + // Act + // Assert + Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Edit)); + Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Delete)); + Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Create)); + } } } \ No newline at end of file