diff --git a/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicy.cs b/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicy.cs new file mode 100644 index 0000000000..505f873aac --- /dev/null +++ b/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicy.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security.Authorization +{ + /// + /// This class provides a base implementation for + /// + public abstract class AuthorizationPolicy : IAuthorizationPolicy + { + public int Order { get; set; } + + public virtual Task ApplyingAsync(AuthorizationPolicyContext context) + { + return Task.FromResult(0); + } + + public virtual Task ApplyAsync(AuthorizationPolicyContext context) + { + return Task.FromResult(0); + } + + public virtual Task AppliedAsync(AuthorizationPolicyContext context) + { + return Task.FromResult(0); + } + } +} diff --git a/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicyContext.cs b/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicyContext.cs index ab65513ab5..fabc41d05d 100644 --- a/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicyContext.cs +++ b/src/Microsoft.AspNet.Security/Authorization/AuthorizationPolicyContext.cs @@ -16,6 +16,11 @@ namespace Microsoft.AspNet.Security.Authorization 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(); } /// @@ -28,6 +33,14 @@ namespace Microsoft.AspNet.Security.Authorization /// 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. /// diff --git a/src/Microsoft.AspNet.Security/Authorization/AuthorizationServiceExtensions.cs b/src/Microsoft.AspNet.Security/Authorization/AuthorizationServiceExtensions.cs new file mode 100644 index 0000000000..70d52bf8d1 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Authorization/AuthorizationServiceExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 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.Authorization +{ + 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(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(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(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(this IAuthorizationService service, Claim claim, ClaimsPrincipal user, object resource) + { + return service.Authorize(new Claim[] { claim }, user, resource); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Authorization/DefaultAuthorizationService.cs b/src/Microsoft.AspNet.Security/Authorization/DefaultAuthorizationService.cs index 09a4d9a95b..544caac07f 100644 --- a/src/Microsoft.AspNet.Security/Authorization/DefaultAuthorizationService.cs +++ b/src/Microsoft.AspNet.Security/Authorization/DefaultAuthorizationService.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Security.Authorization { if (context.User != null) { - if (context.Claims.Any(claim => user.HasClaim(claim.Type, claim.Value))) + if (ClaimsMatch(context.Claims, context.UserClaims)) { context.Authorized = true; } @@ -95,5 +95,16 @@ namespace Microsoft.AspNet.Security.Authorization { return AuthorizeAsync(claims, user, resource).Result; } + + 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/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs index 8be366974b..9c36c54bf6 100644 --- a/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs +++ b/test/Microsoft.AspNet.Security.Test/DefaultAuthorizationServiceTests.cs @@ -243,7 +243,7 @@ namespace Microsoft.AspNet.Security.Test } [Fact] - public void Check_ApplyCanMutateClaims() + public void Check_ApplyCanMutateCheckedClaims() { // Arrange @@ -272,5 +272,35 @@ namespace Microsoft.AspNet.Security.Test // Assert Assert.True(allowed); } + + [Fact] + public void Check_PoliciesCanMutateUsersClaims() + { + + // Arrange + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[0], "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); + + // Assert + Assert.True(allowed); + } } }