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);
+ }
}
}