From 4a635835af1049679103fdca97716e814408addc Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 15 Jan 2015 23:37:35 -0800 Subject: [PATCH] Initial iteration of new Authorization Service --- .../AuthorizationContext.cs | 61 ++ .../AuthorizationHandler.cs | 58 ++ .../AuthorizationOptions.cs | 23 + .../AuthorizationPolicy.cs | 25 +- .../AuthorizationPolicyBuilder.cs | 58 ++ .../AuthorizationPolicyContext.cs | 60 -- .../AuthorizationServiceExtensions.cs | 82 --- .../ClaimsAuthorizationHandler.cs | 32 + .../ClaimsAuthorizationRequirement.cs | 15 + .../DefaultAuthorizationService.cs | 98 +-- .../DenyAnonymousAuthorizationHandler.cs | 20 + .../DenyAnonymousAuthorizationRequirement.cs | 9 + ...tionPolicy.cs => IAuthorizationHandler.cs} | 8 +- .../IAuthorizationRequirement.cs | 9 + .../IAuthorizationService.cs | 30 +- .../PassThroughAuthorizationHandler.cs | 20 + .../ServiceCollectionExtensions.cs | 32 + .../DefaultAuthorizationServiceTests.cs | 649 +++++++++++++----- .../FakePolicy.cs | 52 -- .../TestApplicationEnvironment.cs | 37 - 20 files changed, 857 insertions(+), 521 deletions(-) create mode 100644 src/Microsoft.AspNet.Security/AuthorizationContext.cs create mode 100644 src/Microsoft.AspNet.Security/AuthorizationHandler.cs create mode 100644 src/Microsoft.AspNet.Security/AuthorizationOptions.cs create mode 100644 src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs delete mode 100644 src/Microsoft.AspNet.Security/AuthorizationPolicyContext.cs delete mode 100644 src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs create mode 100644 src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs create mode 100644 src/Microsoft.AspNet.Security/ClaimsAuthorizationRequirement.cs create mode 100644 src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs create mode 100644 src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationRequirement.cs rename src/Microsoft.AspNet.Security/{IAuthorizationPolicy.cs => IAuthorizationHandler.cs} (50%) create mode 100644 src/Microsoft.AspNet.Security/IAuthorizationRequirement.cs create mode 100644 src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs create mode 100644 src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs delete mode 100644 test/Microsoft.AspNet.Security.Test/FakePolicy.cs delete mode 100644 test/Microsoft.AspNet.Security.Test/TestApplicationEnvironment.cs diff --git a/src/Microsoft.AspNet.Security/AuthorizationContext.cs b/src/Microsoft.AspNet.Security/AuthorizationContext.cs new file mode 100644 index 0000000000..0c6dc06197 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizationContext.cs @@ -0,0 +1,61 @@ +// 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.Security.Claims; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Security +{ + /// + /// Contains authorization information used by . + /// + public class AuthorizationContext + { + private HashSet _pendingRequirements = new HashSet(); + private bool _failCalled; + private bool _succeedCalled; + + public AuthorizationContext( + [NotNull] AuthorizationPolicy policy, + HttpContext context, + object resource) + { + Policy = policy; + Context = context; + Resource = resource; + foreach (var req in Policy.Requirements) + { + _pendingRequirements.Add(req); + } + } + + public AuthorizationPolicy Policy { get; private set; } + public ClaimsPrincipal User { get { return Context.User; } } + public HttpContext Context { get; private set; } + public object Resource { get; private set; } + + public IEnumerable PendingRequirements { get { return _pendingRequirements; } } + + public bool HasFailed { get { return _failCalled; } } + + public bool HasSucceeded { + get + { + return !_failCalled && _succeedCalled && !PendingRequirements.Any(); + } + } + + public void Fail() + { + _failCalled = true; + } + + public void Succeed(IAuthorizationRequirement requirement) + { + _succeedCalled = true; + _pendingRequirements.Remove(requirement); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthorizationHandler.cs b/src/Microsoft.AspNet.Security/AuthorizationHandler.cs new file mode 100644 index 0000000000..d913318700 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizationHandler.cs @@ -0,0 +1,58 @@ +// 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) + { + foreach (var req in context.Policy.Requirements.OfType()) + { + if (await CheckAsync(context, req)) + { + context.Succeed(req); + } + else + { + context.Fail(); + } + } + } + + public abstract Task CheckAsync(AuthorizationContext context, TRequirement requirement); + } + + // 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/AuthorizationOptions.cs b/src/Microsoft.AspNet.Security/AuthorizationOptions.cs new file mode 100644 index 0000000000..667ee4fcba --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizationOptions.cs @@ -0,0 +1,23 @@ +// 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; + +namespace Microsoft.AspNet.Security +{ + public class AuthorizationOptions + { + // TODO: make this case insensitive + private IDictionary PolicyMap { get; } = new Dictionary(); + + public void AddPolicy([NotNull] string name, [NotNull] AuthorizationPolicy policy) + { + PolicyMap[name] = policy; + } + + public AuthorizationPolicy GetPolicy([NotNull] string name) + { + return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs index a58ae858ae..d142eb1b60 100644 --- a/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs +++ b/src/Microsoft.AspNet.Security/AuthorizationPolicy.cs @@ -2,31 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; namespace Microsoft.AspNet.Security { - /// - /// This class provides a base implementation for - /// - public abstract class AuthorizationPolicy : IAuthorizationPolicy + public class AuthorizationPolicy { - public int Order { get; set; } - - public virtual Task ApplyingAsync(AuthorizationPolicyContext context) + public AuthorizationPolicy(IEnumerable requirements, IEnumerable activeAuthenticationTypes) { - return Task.FromResult(0); + Requirements = requirements; + ActiveAuthenticationTypes = activeAuthenticationTypes; } - public virtual Task ApplyAsync(AuthorizationPolicyContext context) - { - return Task.FromResult(0); - } - - public virtual Task AppliedAsync(AuthorizationPolicyContext context) - { - return Task.FromResult(0); - } + public IEnumerable Requirements { get; private set; } + public IEnumerable ActiveAuthenticationTypes { get; private set; } } } diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs new file mode 100644 index 0000000000..520bc77460 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs @@ -0,0 +1,58 @@ +// 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.Security.Claims; + +namespace Microsoft.AspNet.Security +{ + public class AuthorizationPolicyBuilder + { + public AuthorizationPolicyBuilder(params string[] activeAuthenticationTypes) + { + foreach (var authType in activeAuthenticationTypes) { + ActiveAuthenticationTypes.Add(authType); + } + } + + public IList Requirements { get; set; } = new List(); + public IList ActiveAuthenticationTypes { get; set; } = new List(); + + public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues) + { + Requirements.Add(new ClaimsAuthorizationRequirement + { + ClaimType = claimType, + AllowedValues = requiredValues + }); + return this; + } + + public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType) + { + Requirements.Add(new ClaimsAuthorizationRequirement + { + ClaimType = claimType, + AllowedValues = null + }); + return this; + } + + public AuthorizationPolicyBuilder RequiresRole([NotNull] params string[] roles) + { + RequiresClaim(ClaimTypes.Role, roles); + return this; + } + + public AuthorizationPolicyBuilder RequireAuthenticatedUser() + { + Requirements.Add(new DenyAnonymousAuthorizationRequirement()); + return this; + } + + public AuthorizationPolicy Build() + { + return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes); + } + } +} diff --git a/src/Microsoft.AspNet.Security/AuthorizationPolicyContext.cs b/src/Microsoft.AspNet.Security/AuthorizationPolicyContext.cs deleted file mode 100644 index b394bfcb68..0000000000 --- a/src/Microsoft.AspNet.Security/AuthorizationPolicyContext.cs +++ /dev/null @@ -1,60 +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.Security.Claims; -using System.Linq; - -namespace Microsoft.AspNet.Security -{ - /// - /// Contains authorization information used by . - /// - public class AuthorizationPolicyContext - { - public AuthorizationPolicyContext(IEnumerable claims, ClaimsPrincipal user, object resource ) - { - Claims = (claims ?? Enumerable.Empty()).ToList(); - User = user; - Resource = resource; - - // user claims are copied to a new and mutable list - UserClaims = user != null - ? user.Claims.ToList() - : new List(); - } - - /// - /// The list of claims the is checking. - /// - public IList Claims { get; private set; } - - /// - /// The user to check the claims against. - /// - public ClaimsPrincipal User { get; private set; } - - /// - /// The claims of the user. - /// - /// - /// This list can be modified by policies for retries. - /// - public IList UserClaims { get; private set; } - - /// - /// An optional resource associated to the check. - /// - public object Resource { get; private set; } - - /// - /// Gets or set whether the permission will be granted to the user. - /// - public bool Authorized { get; set; } - - /// - /// When set to true, the authorization check will be processed again. - /// - public bool Retry { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs b/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs deleted file mode 100644 index 17ec58196d..0000000000 --- a/src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs +++ /dev/null @@ -1,82 +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; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Microsoft.AspNet.Security -{ - public static class AuthorizationServiceExtensions - { - /// - /// Checks if a user has specific claims. - /// - /// The claim to check against a specific user. - /// The user to check claims against. - /// true when the user fulfills one of the claims, false otherwise. - public static Task AuthorizeAsync([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user) - { - return service.AuthorizeAsync(new Claim[] { claim }, user); - } - - /// - /// Checks if a user has specific claims. - /// - /// The claim to check against a specific user. - /// The user to check claims against. - /// true when the user fulfills one of the claims, false otherwise. - public static bool Authorize([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user) - { - return service.Authorize(new Claim[] { claim }, user); - } - - /// - /// Checks if a user has specific claims for a specific context obj. - /// - /// The claim to check against a specific user. - /// The user to check claims against. - /// The resource the claims should be check with. - /// true when the user fulfills one of the claims, false otherwise. - public static Task AuthorizeAsync([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user, object resource) - { - return service.AuthorizeAsync(new Claim[] { claim }, user, resource); - } - - /// - /// Checks if a user has specific claims for a specific context obj. - /// - /// The claimsto check against a specific user. - /// The user to check claims against. - /// The resource the claims should be check with. - /// true when the user fulfills one of the claims, false otherwise. - public static bool Authorize([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user, object resource) - { - return service.Authorize(new Claim[] { claim }, user, resource); - } - - /// - /// Checks if a user has specific claims. - /// - /// The claims to check against a specific user. - /// The user to check claims against. - /// true when the user fulfills one of the claims, false otherwise. - public static Task AuthorizeAsync([NotNull] this IAuthorizationService service, IEnumerable claims, ClaimsPrincipal user) - { - return service.AuthorizeAsync(claims, user, null); - } - - /// - /// Checks if a user has specific claims. - /// - /// The claims to check against a specific user. - /// The user to check claims against. - /// true when the user fulfills one of the claims, false otherwise. - public static bool Authorize([NotNull] this IAuthorizationService service, IEnumerable claims, ClaimsPrincipal user) - { - return service.Authorize(claims, user, null); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs new file mode 100644 index 0000000000..9f3f58c3ac --- /dev/null +++ b/src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs @@ -0,0 +1,32 @@ +// 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 +{ + public class ClaimsAuthorizationHandler : AuthorizationHandler + { + public override Task CheckAsync(AuthorizationContext context, ClaimsAuthorizationRequirement requirement) + { + if (context.Context.User == null) + { + return Task.FromResult(false); + } + + 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/ClaimsAuthorizationRequirement.cs b/src/Microsoft.AspNet.Security/ClaimsAuthorizationRequirement.cs new file mode 100644 index 0000000000..8ec5e7c7e1 --- /dev/null +++ b/src/Microsoft.AspNet.Security/ClaimsAuthorizationRequirement.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.Collections.Generic; + +namespace Microsoft.AspNet.Security +{ + // 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 string ClaimType { get; set; } + public IEnumerable AllowedValues { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs b/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs index 0b05a6d3dd..d20cf65445 100644 --- a/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs +++ b/src/Microsoft.AspNet.Security/DefaultAuthorizationService.cs @@ -1,101 +1,67 @@ // 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; using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Security { public class DefaultAuthorizationService : IAuthorizationService { - private readonly IList _policies; - public int MaxRetries = 99; + private readonly IList _handlers; + private readonly AuthorizationOptions _options; - public DefaultAuthorizationService(IEnumerable policies) + public DefaultAuthorizationService(IOptions options, IEnumerable handlers) { - if (policies == null) - { - _policies = Enumerable.Empty().ToArray(); - } - else - { - _policies = policies.OrderBy(x => x.Order).ToArray(); - } + _handlers = handlers.ToArray(); + _options = options.Options; } - public async Task AuthorizeAsync(IEnumerable claims, ClaimsPrincipal user, object resource) + public Task AuthorizeAsync([NotNull] string policyName, HttpContext context, object resource = null) { - var context = new AuthorizationPolicyContext(claims, user, resource); - - foreach (var policy in _policies) + var policy = _options.GetPolicy(policyName); + if (policy == null) { - await policy.ApplyingAsync(context); + return Task.FromResult(false); } + return AuthorizeAsync(policy, context, resource); + } - // we only apply the policies for a limited number of times to prevent - // infinite loops - - int retries; - for (retries = 0; retries < MaxRetries; retries++) + public async Task AuthorizeAsync([NotNull] AuthorizationPolicy policy, [NotNull] HttpContext context, object resource = null) + { + var user = context.User; + try { - // we don't need to check for owned claims if the permission is already granted - if (!context.Authorized) + // Generate the user identities if policy specified the AuthTypes + if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() ) { - if (context.User != null) + 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) { - if (ClaimsMatch(context.Claims, context.UserClaims)) - { - context.Authorized = true; - } + principal.AddIdentity(result.Identity); } + context.User = principal; } - // reset the retry flag - context.Retry = false; + var authContext = new AuthorizationContext(policy, context, resource); - // give a chance for policies to change claims or the grant - foreach (var policy in _policies) + foreach (var handler in _handlers) { - await policy.ApplyAsync(context); - } - - // if no policies have changed the context, stop checking - if (!context.Retry) - { - break; + await handler.HandleAsync(authContext); } + return authContext.HasSucceeded; } - - if (retries == MaxRetries) + finally { - throw new InvalidOperationException("Too many authorization retries."); + context.User = user; } - - foreach (var policy in _policies) - { - await policy.AppliedAsync(context); - } - - return context.Authorized; - } - - public bool Authorize(IEnumerable claims, ClaimsPrincipal user, object resource) - { - return AuthorizeAsync(claims, user, resource).GetAwaiter().GetResult(); - } - - private bool ClaimsMatch([NotNull] IEnumerable x, [NotNull] IEnumerable y) - { - return x.Any(claim => - y.Any(userClaim => - string.Equals(claim.Type, userClaim.Type, StringComparison.OrdinalIgnoreCase) && - string.Equals(claim.Value, userClaim.Value, StringComparison.Ordinal) - ) - ); - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs new file mode 100644 index 0000000000..878a32ec92 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationHandler.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.AspNet.Security +{ + public class DenyAnonymousAuthorizationHandler : AuthorizationHandler + { + public override Task CheckAsync(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement) + { + var user = context.User; + var userIsAnonymous = + user == null || + user.Identity == null || + !user.Identity.IsAuthenticated; + return Task.FromResult(!userIsAnonymous); + } + } +} diff --git a/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationRequirement.cs b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationRequirement.cs new file mode 100644 index 0000000000..286d5fd69a --- /dev/null +++ b/src/Microsoft.AspNet.Security/DenyAnonymousAuthorizationRequirement.cs @@ -0,0 +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 Microsoft.AspNet.Security; + +namespace Microsoft.AspNet.Security +{ + public class DenyAnonymousAuthorizationRequirement : IAuthorizationRequirement { } +} diff --git a/src/Microsoft.AspNet.Security/IAuthorizationPolicy.cs b/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs similarity index 50% rename from src/Microsoft.AspNet.Security/IAuthorizationPolicy.cs rename to src/Microsoft.AspNet.Security/IAuthorizationHandler.cs index 0f121d558a..975a305b86 100644 --- a/src/Microsoft.AspNet.Security/IAuthorizationPolicy.cs +++ b/src/Microsoft.AspNet.Security/IAuthorizationHandler.cs @@ -5,11 +5,9 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Security { - public interface IAuthorizationPolicy + public interface IAuthorizationHandler { - int Order { get; set; } - Task ApplyingAsync(AuthorizationPolicyContext context); - Task ApplyAsync(AuthorizationPolicyContext context); - Task AppliedAsync(AuthorizationPolicyContext context); + Task HandleAsync(AuthorizationContext context); + //void Handle(AuthorizationContext context); } } diff --git a/src/Microsoft.AspNet.Security/IAuthorizationRequirement.cs b/src/Microsoft.AspNet.Security/IAuthorizationRequirement.cs new file mode 100644 index 0000000000..bd25247df2 --- /dev/null +++ b/src/Microsoft.AspNet.Security/IAuthorizationRequirement.cs @@ -0,0 +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. + +namespace Microsoft.AspNet.Security +{ + public interface IAuthorizationRequirement + { + } +} diff --git a/src/Microsoft.AspNet.Security/IAuthorizationService.cs b/src/Microsoft.AspNet.Security/IAuthorizationService.cs index 9fcef75a2f..8bcafda3d7 100644 --- a/src/Microsoft.AspNet.Security/IAuthorizationService.cs +++ b/src/Microsoft.AspNet.Security/IAuthorizationService.cs @@ -1,34 +1,32 @@ // 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.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Security { /// - /// Checks claims based permissions for a user. + /// Checks policy based permissions for a user /// public interface IAuthorizationService { /// - /// Checks if a user has specific claims for a specific context obj. + /// Checks if a user meets a specific authorization policy /// - /// The claims to check against a specific user. - /// The user to check claims against. - /// The resource the claims should be check with. - /// true when the user fulfills one of the claims, false otherwise. - Task AuthorizeAsync(IEnumerable claims, ClaimsPrincipal user, object 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); /// - /// Checks if a user has specific claims for a specific context obj. + /// Checks if a user meets a specific authorization policy /// - /// The claims to check against a specific user. - /// The user to check claims against. - /// The resource the claims should be check with. - /// true when the user fulfills one of the claims, false otherwise. - bool Authorize(IEnumerable claims, ClaimsPrincipal user, object 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(AuthorizationPolicy policy, HttpContext context, object resource = null); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs new file mode 100644 index 0000000000..0dfc6ab289 --- /dev/null +++ b/src/Microsoft.AspNet.Security/PassThroughAuthorizationHandler.cs @@ -0,0 +1,20 @@ +// 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 +{ + public class PassThroughAuthorizationHandler : IAuthorizationHandler + { + public async Task HandleAsync(AuthorizationContext context) + { + foreach (var handler in context.Policy.Requirements.OfType()) + { + await handler.HandleAsync(context); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..cceec4cc28 --- /dev/null +++ b/src/Microsoft.AspNet.Security/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +// 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.Security; +using Microsoft.Framework.ConfigurationModel; + +namespace Microsoft.Framework.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection ConfigureAuthorization([NotNull] this IServiceCollection services, [NotNull] Action configure) + { + return services.Configure(configure); + } + + // Review: Need UseDefaultSubkey parameter? + public static IServiceCollection AddAuthorization([NotNull] this IServiceCollection services, IConfiguration config = null, Action configureOptions = null) + { + var describe = new ServiceDescriber(config); + services.AddOptions(config); + services.TryAdd(describe.Transient()); + services.Add(describe.Transient()); + services.Add(describe.Transient()); + if (configureOptions != null) + { + services.Configure(configureOptions); + } + return services; + } + } +} diff --git a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs index a5a003b801..00f6cde0aa 100644 --- a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs +++ b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs @@ -3,307 +3,588 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Security; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using Microsoft.Framework.OptionsModel; +using Moq; using Xunit; namespace Microsoft.AspNet.Security.Test { public class DefaultAuthorizationServiceTests { + private IAuthorizationService BuildAuthorizationService(Action setupServices = null) + { + var services = new ServiceCollection(); + services.AddAuthorization(); + if (setupServices != null) + { + setupServices(services); + } + return services.BuildServiceProvider().GetRequiredService(); + } + + private Mock SetupContext(params ClaimsIdentity[] ids) + { + 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; + } + [Fact] - public void Check_ShouldAllowIfClaimIsPresent() + public async Task Authorize_ShouldAllowIfClaimIsPresent() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( - new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic") - ); + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + options.AddPolicy("Basic", new AuthorizationPolicyBuilder() + .RequiresClaim("Permission", "CanViewPage") + .Build()); + }); + }); + var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.True(allowed); } [Fact] - public void Check_ShouldAllowIfClaimIsAmongValues() + public async Task Authorize_ShouldAllowIfClaimIsPresentWithSpecifiedAuthType() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder("Basic").RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task Authorize_ShouldAllowIfClaimIsAmongValues() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CanViewPage"), + new Claim[] { + new Claim("Permission", "CanViewPage"), new Claim("Permission", "CanViewAnything") - }, + }, "Basic") ); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.True(allowed); } [Fact] - public void Check_ShouldNotAllowIfClaimTypeIsNotPresent() + public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( new ClaimsIdentity( - new Claim[] { - new Claim("SomethingElse", "CanViewPage"), + new Claim[] { + new Claim("SomethingElse", "CanViewPage"), }, "Basic") ); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.False(allowed); } [Fact] - public void Check_ShouldNotAllowIfClaimValueIsNotPresent() + public async Task Authorize_ShouldNotAllowIfClaimTypeIsNotPresent() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage", "CanViewAnything"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CanViewComment"), + new Claim[] { + new Claim("SomethingElse", "CanViewPage"), }, "Basic") ); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.False(allowed); } [Fact] - public void Check_ShouldNotAllowIfNoClaims() + public async Task Authorize_ShouldNotAllowIfClaimValueIsNotPresent() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CanViewComment"), + }, + "Basic") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task Authorize_ShouldNotAllowIfNoClaims() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( new ClaimsIdentity( new Claim[0], "Basic") ); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.False(allowed); } [Fact] - public void Check_ShouldNotAllowIfUserIsNull() + public async Task Authorize_ShouldNotAllowIfUserIsNull() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - ClaimsPrincipal user = null; + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext(); + context.Object.User = null; // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.False(allowed); } [Fact] - public void Check_ShouldNotAllowIfUserIsNotAuthenticated() + public async Task Authorize_ShouldNotAllowIfNotCorrectAuthType() { // Arrange - var authorizationService = new DefaultAuthorizationService(Enumerable.Empty()); - var user = new ClaimsPrincipal( + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder("Basic").RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext(new ClaimsIdentity()); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task Authorize_ShouldAllowWithNoAuthType() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( new ClaimsIdentity( - new Claim[] { - new Claim("Permission", "CanViewComment"), + new Claim[] { + new Claim("Permission", "CanViewPage"), + }, + "Basic") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task Authorize_ShouldNotAllowIfUnknownPolicy() + { + // Arrange + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim("Permission", "CanViewComment"), }, null) ); // Act - var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user); + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); // Assert Assert.False(allowed); } [Fact] - public void Check_ShouldApplyPoliciesInOrder() + public async Task Authorize_CustomRolePolicy() { // Arrange - string result = ""; - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - Order = 20, - ApplyingAsyncAction = (context) => { result += "20"; } - }, - new FakePolicy() { - Order = -1, - ApplyingAsyncAction = (context) => { result += "-1"; } - }, - new FakePolicy() { - Order = 30, - ApplyingAsyncAction = (context) => { result += "30"; } - }, - }; - - var authorizationService = new DefaultAuthorizationService(policies); - - // Act - var allowed = authorizationService.Authorize(Enumerable.Empty(), null); - - // Assert - Assert.Equal("-12030", result); - } - - [Fact] - public void Check_ShouldInvokeApplyingApplyAppliedInOrder() - { - // Arrange - string result = ""; - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - Order = 20, - ApplyingAsyncAction = (context) => { result += "Applying20"; }, - ApplyAsyncAction = (context) => { result += "Apply20"; }, - AppliedAsyncAction = (context) => { result += "Applied20"; } - }, - new FakePolicy() { - Order = -1, - ApplyingAsyncAction = (context) => { result += "Applying-1"; }, - ApplyAsyncAction = (context) => { result += "Apply-1"; }, - AppliedAsyncAction = (context) => { result += "Applied-1"; } - }, - new FakePolicy() { - Order = 30, - ApplyingAsyncAction = (context) => { result += "Applying30"; }, - ApplyAsyncAction = (context) => { result += "Apply30"; }, - AppliedAsyncAction = (context) => { result += "Applied30"; } - }, - }; - - var authorizationService = new DefaultAuthorizationService(policies); - - // Act - var allowed = authorizationService.Authorize(Enumerable.Empty(), null); - - // Assert - Assert.Equal("Applying-1Applying20Applying30Apply-1Apply20Apply30Applied-1Applied20Applied30", result); - } - - [Fact] - public void Check_ShouldConvertNullClaimsToEmptyList() - { - // Arrange - IList claims = null; - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - Order = 20, - ApplyingAsyncAction = (context) => { claims = context.Claims; } - } - }; - - var authorizationService = new DefaultAuthorizationService(policies); - - // Act - var allowed = authorizationService.Authorize(Enumerable.Empty(), null); - - // Assert - Assert.NotNull(claims); - Assert.Equal(0, claims.Count); - } - - [Fact] - public void Check_ShouldThrowWhenPoliciesDontStop() - { - // Arrange - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - ApplyAsyncAction = (context) => { context.Retry = true; } - } - }; - - var authorizationService = new DefaultAuthorizationService(policies); - - // Act - // Assert - Exception ex = Assert.Throws(() => authorizationService.Authorize(Enumerable.Empty(), null)); - } - - [Fact] - public void Check_ApplyCanMutateCheckedClaims() - { - - // Arrange - var user = new ClaimsPrincipal( - new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanDeleteComments") }, "Basic") + var policy = new AuthorizationPolicyBuilder().RequiresRole("Administrator") + .RequiresClaim(ClaimTypes.Role, "User"); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.Role, "Administrator") + }, + "Basic") ); - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - ApplyAsyncAction = (context) => { - // for instance, if user owns the comment - if(!context.Claims.Any(claim => claim.Type == "Permission" && claim.Value == "CanDeleteComments")) - { - context.Claims.Add(new Claim("Permission", "CanDeleteComments")); - context.Retry = true; - } - } - } - }; - - var authorizationService = new DefaultAuthorizationService(policies); - // Act - var allowed = authorizationService.Authorize(Enumerable.Empty(), user); + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); // Assert Assert.True(allowed); } [Fact] - public void Check_PoliciesCanMutateUsersClaims() + public async Task Authorize_HasAnyClaimOfTypePolicy() { - // Arrange - var user = new ClaimsPrincipal( - new ClaimsIdentity(new Claim[0], "Basic") + var policy = new AuthorizationPolicyBuilder().RequiresClaim(ClaimTypes.Role); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim(ClaimTypes.Role, ""), + }, + "Basic") ); - var policies = new IAuthorizationPolicy[] { - new FakePolicy() { - ApplyAsyncAction = (context) => { - if (!context.Authorized) - { - context.UserClaims.Add(new Claim("Permission", "CanDeleteComments")); - context.Retry = true; - } - } - } - }; - - var authorizationService = new DefaultAuthorizationService(policies); - // Act - var allowed = authorizationService.Authorize(new Claim("Permission", "CanDeleteComments"), user); + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); // Assert Assert.True(allowed); } + + [Fact] + public async Task Authorize_PolicyCanAuthenticationTypeWithNameClaim() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("AuthType").RequiresClaim(ClaimTypes.Name); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "Name") }, "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task RolePolicyCanRequireSingleRole() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin"); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Admin") }, "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task RolePolicyCanRequireOneOfManyRoles() + { + // Arrange + var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin", "Users"); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Users") }, "AuthType")); + + // Act + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task RolePolicyCanBlockWrongRole() + { + // Arrange + var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage"); + var authorizationService = BuildAuthorizationService(); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim(ClaimTypes.Role, "Nope"), + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task RolePolicyCanBlockNoRole() + { + // Arrange + + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequiresRole("Admin", "Users"); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task PolicyFailsWithNoRequirements() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder(); + options.AddPolicy("Basic", policy.Build()); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim(ClaimTypes.Name, "Name"), + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object); + + // Assert + Assert.False(allowed); + } + + [Fact] + public async Task CanApproveAnyAuthenticatedUser() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser(); + options.AddPolicy("Any", policy.Build()); + }); + }); + var context = SetupContext( + new ClaimsIdentity( + new Claim[] { + new Claim(ClaimTypes.Name, "Name"), + }, + "AuthType") + ); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Any", context.Object); + + // Assert + Assert.True(allowed); + } + + [Fact] + public async Task CanBlockNonAuthenticatedUser() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser(); + options.AddPolicy("Any", policy.Build()); + }); + }); + var context = SetupContext(new ClaimsIdentity()); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Any", context.Object); + + // Assert + Assert.False(allowed); + } + + public class CustomRequirement : IAuthorizationRequirement { } + public class CustomHandler : AuthorizationHandler + { + public override Task CheckAsync(AuthorizationContext context, CustomRequirement requirement) + { + return Task.FromResult(true); + } + } + + [Fact] + public async Task CustomReqWithNoHandlerFails() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder(); + policy.Requirements.Add(new CustomRequirement()); + options.AddPolicy("Custom", policy.Build()); + }); + }); + var context = SetupContext(); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object); + + // Assert + Assert.False(allowed); + } + + + [Fact] + public async Task CustomReqWithHandlerSucceeds() + { + // Arrange + var authorizationService = BuildAuthorizationService(services => + { + services.AddTransient(); + services.Configure(options => + { + var policy = new AuthorizationPolicyBuilder(); + policy.Requirements.Add(new CustomRequirement()); + options.AddPolicy("Custom", policy.Build()); + }); + }); + var context = SetupContext(); + + // Act + var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object); + + // Assert + Assert.True(allowed); + } + } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Security.Test/FakePolicy.cs b/test/Microsoft.AspNet.Security.Test/FakePolicy.cs deleted file mode 100644 index be9139b89d..0000000000 --- a/test/Microsoft.AspNet.Security.Test/FakePolicy.cs +++ /dev/null @@ -1,52 +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; -using System.Threading.Tasks; -using Microsoft.AspNet.Security; - -namespace Microsoft.AspNet.Security.Test -{ - public class FakePolicy : IAuthorizationPolicy - { - - public int Order { get; set; } - - public Task ApplyingAsync(AuthorizationPolicyContext context) - { - if (ApplyingAsyncAction != null) - { - ApplyingAsyncAction(context); - } - - return Task.FromResult(0); - } - - public Task ApplyAsync(AuthorizationPolicyContext context) - { - if (ApplyAsyncAction != null) - { - ApplyAsyncAction(context); - } - - return Task.FromResult(0); - - } - - public Task AppliedAsync(AuthorizationPolicyContext context) - { - if (AppliedAsyncAction != null) - { - AppliedAsyncAction(context); - } - - return Task.FromResult(0); - } - - public Action ApplyingAsyncAction { get; set;} - - public Action ApplyAsyncAction { get; set;} - - public Action AppliedAsyncAction { get; set;} - } -} diff --git a/test/Microsoft.AspNet.Security.Test/TestApplicationEnvironment.cs b/test/Microsoft.AspNet.Security.Test/TestApplicationEnvironment.cs deleted file mode 100644 index 01ef364c1d..0000000000 --- a/test/Microsoft.AspNet.Security.Test/TestApplicationEnvironment.cs +++ /dev/null @@ -1,37 +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; -using System.Runtime.Versioning; -using Microsoft.Framework.Runtime; - -namespace Microsoft.AspNet.Security -{ - public class TestApplicationEnvironment : IApplicationEnvironment - { - public string ApplicationBasePath - { - get { return Environment.CurrentDirectory; } - } - - public string ApplicationName - { - get { return "Test App environment"; } - } - - public string Configuration - { - get { return "Test"; } - } - - public FrameworkName RuntimeFramework - { - get { return new FrameworkName(".NETFramework", new Version(4, 5)); } - } - - public string Version - { - get { return "1.0.0"; } - } - } -} \ No newline at end of file