Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 5094b85

Browse files
committed
Latest AuthZ iteration
- Core Auth API now takes list of IAuthorizationRequirements, or policy name. - Overload that takes AuthorizationPolicy instance moved to extension method. - Remove HttpContext from API and replace with ClaimsPrincipal instead - Add Operation requirement - Add Sync overloads - Add ClaimsTransformationOptions (TBD where to use this) Fixes #132 Fixes #116 Fixes #11 Fixes #117
1 parent 58661b2 commit 5094b85

20 files changed

+624
-288
lines changed

src/Microsoft.AspNet.Security/AuthorizationContext.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Security.Claims;
7-
using Microsoft.AspNet.Http;
87

98
namespace Microsoft.AspNet.Security
109
{
@@ -13,27 +12,23 @@ namespace Microsoft.AspNet.Security
1312
/// </summary>
1413
public class AuthorizationContext
1514
{
16-
private HashSet<IAuthorizationRequirement> _pendingRequirements = new HashSet<IAuthorizationRequirement>();
15+
private HashSet<IAuthorizationRequirement> _pendingRequirements;
1716
private bool _failCalled;
1817
private bool _succeedCalled;
1918

2019
public AuthorizationContext(
21-
[NotNull] AuthorizationPolicy policy,
22-
HttpContext context,
20+
[NotNull] IEnumerable<IAuthorizationRequirement> requirements,
21+
ClaimsPrincipal user,
2322
object resource)
2423
{
25-
Policy = policy;
26-
Context = context;
24+
Requirements = requirements;
25+
User = user;
2726
Resource = resource;
28-
foreach (var req in Policy.Requirements)
29-
{
30-
_pendingRequirements.Add(req);
31-
}
27+
_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
3228
}
3329

34-
public AuthorizationPolicy Policy { get; private set; }
35-
public ClaimsPrincipal User { get { return Context.User; } }
36-
public HttpContext Context { get; private set; }
30+
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
31+
public ClaimsPrincipal User { get; private set; }
3732
public object Resource { get; private set; }
3833

3934
public IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } }
Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,68 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Linq;
65
using System.Threading.Tasks;
76

87
namespace Microsoft.AspNet.Security
98
{
10-
// Music store use case
9+
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
10+
where TRequirement : IAuthorizationRequirement
11+
{
12+
public void Handle(AuthorizationContext context)
13+
{
14+
foreach (var req in context.Requirements.OfType<TRequirement>())
15+
{
16+
Handle(context, req);
17+
}
18+
}
1119

12-
// await AuthorizeAsync<Album>(user, "Edit", albumInstance);
20+
public virtual Task HandleAsync(AuthorizationContext context)
21+
{
22+
Handle(context);
23+
return Task.FromResult(0);
24+
}
1325

14-
// No policy name needed because this is auto based on resource (operation is the policy name)
15-
//RegisterOperation which auto generates the policy for Authorize<T>
16-
//bool AuthorizeAsync<TResource>(ClaimsPrincipal, string operation, TResource instance)
17-
//bool AuthorizeAsync<TResource>(IAuthorization, ClaimsPrincipal, string operation, TResource instance)
18-
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
26+
// REVIEW: do we need an async hook too?
27+
public abstract void Handle(AuthorizationContext context, TRequirement requirement);
28+
}
29+
30+
public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler
31+
where TResource : class
1932
where TRequirement : IAuthorizationRequirement
2033
{
21-
public async Task HandleAsync(AuthorizationContext context)
34+
public virtual async Task HandleAsync(AuthorizationContext context)
2235
{
23-
foreach (var req in context.Policy.Requirements.OfType<TRequirement>())
36+
var resource = context.Resource as TResource;
37+
// REVIEW: should we allow null resources?
38+
if (resource != null)
2439
{
25-
if (await CheckAsync(context, req))
40+
foreach (var req in context.Requirements.OfType<TRequirement>())
2641
{
27-
context.Succeed(req);
42+
await HandleAsync(context, req, resource);
2843
}
29-
else
44+
}
45+
}
46+
47+
public virtual Task HandleAsync(AuthorizationContext context, TRequirement requirement, TResource resource)
48+
{
49+
Handle(context, requirement, resource);
50+
return Task.FromResult(0);
51+
}
52+
53+
public virtual void Handle(AuthorizationContext context)
54+
{
55+
var resource = context.Resource as TResource;
56+
// REVIEW: should we allow null resources?
57+
if (resource != null)
58+
{
59+
foreach (var req in context.Requirements.OfType<TRequirement>())
3060
{
31-
context.Fail();
61+
Handle(context, req, resource);
3262
}
3363
}
3464
}
3565

36-
public abstract Task<bool> CheckAsync(AuthorizationContext context, TRequirement requirement);
66+
public abstract void Handle(AuthorizationContext context, TRequirement requirement, TResource resource);
3767
}
38-
39-
// TODO:
40-
//public abstract class AuthorizationHandler<TRequirement, TResource> : AuthorizationHandler<TRequirement>
41-
// where TResource : class
42-
// where TRequirement : IAuthorizationRequirement
43-
//{
44-
// public override Task HandleAsync(AuthorizationContext context)
45-
// {
46-
// var resource = context.Resource as TResource;
47-
// if (resource != null)
48-
// {
49-
// return HandleAsync(context, resource);
50-
// }
51-
52-
// return Task.FromResult(0);
53-
54-
// }
55-
56-
// public abstract Task HandleAsync(AuthorizationContext context, TResource resource);
57-
//}
5868
}
Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,77 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Linq;
57

68
namespace Microsoft.AspNet.Security
79
{
810
public class AuthorizationPolicy
911
{
1012
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> activeAuthenticationTypes)
1113
{
12-
Requirements = requirements;
13-
ActiveAuthenticationTypes = activeAuthenticationTypes;
14+
Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
15+
ActiveAuthenticationTypes = new List<string>(activeAuthenticationTypes).AsReadOnly();
1416
}
1517

16-
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
17-
public IEnumerable<string> ActiveAuthenticationTypes { get; private set; }
18+
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; private set; }
19+
public IReadOnlyList<string> ActiveAuthenticationTypes { get; private set; }
20+
21+
public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies)
22+
{
23+
return Combine((IEnumerable<AuthorizationPolicy>)policies);
24+
}
25+
26+
// TODO: Add unit tests
27+
public static AuthorizationPolicy Combine([NotNull] IEnumerable<AuthorizationPolicy> policies)
28+
{
29+
var builder = new AuthorizationPolicyBuilder();
30+
foreach (var policy in policies)
31+
{
32+
builder.Combine(policy);
33+
}
34+
return builder.Build();
35+
}
36+
37+
public static AuthorizationPolicy Combine([NotNull] AuthorizationOptions options, [NotNull] IEnumerable<AuthorizeAttribute> attributes)
38+
{
39+
var policyBuilder = new AuthorizationPolicyBuilder();
40+
bool any = false;
41+
foreach (var authorizeAttribute in attributes.OfType<AuthorizeAttribute>())
42+
{
43+
any = true;
44+
var requireAnyAuthenticated = true;
45+
if (!string.IsNullOrWhiteSpace(authorizeAttribute.Policy))
46+
{
47+
var policy = options.GetPolicy(authorizeAttribute.Policy);
48+
if (policy == null)
49+
{
50+
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeAttribute.Policy));
51+
}
52+
policyBuilder.Combine(policy);
53+
requireAnyAuthenticated = false;
54+
}
55+
var rolesSplit = authorizeAttribute.Roles?.Split(',');
56+
if (rolesSplit != null && rolesSplit.Any())
57+
{
58+
policyBuilder.RequiresRole(rolesSplit);
59+
requireAnyAuthenticated = false;
60+
}
61+
string[] authTypesSplit = authorizeAttribute.ActiveAuthenticationTypes?.Split(',');
62+
if (authTypesSplit != null && authTypesSplit.Any())
63+
{
64+
foreach (var authType in authTypesSplit)
65+
{
66+
policyBuilder.ActiveAuthenticationTypes.Add(authType);
67+
}
68+
}
69+
if (requireAnyAuthenticated)
70+
{
71+
policyBuilder.RequireAuthenticatedUser();
72+
}
73+
}
74+
return any ? policyBuilder.Build() : null;
75+
}
1876
}
1977
}

src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@ public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequireme
4040
return this;
4141
}
4242

43-
public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
43+
public AuthorizationPolicyBuilder Combine([NotNull] AuthorizationPolicy policy)
4444
{
4545
AddAuthenticationTypes(policy.ActiveAuthenticationTypes.ToArray());
4646
AddRequirements(policy.Requirements.ToArray());
4747
return this;
4848
}
4949

5050
public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues)
51+
{
52+
return RequiresClaim(claimType, (IEnumerable<string>)requiredValues);
53+
}
54+
55+
public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, IEnumerable<string> requiredValues)
5156
{
5257
Requirements.Add(new ClaimsAuthorizationRequirement
5358
{
@@ -68,6 +73,11 @@ public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType)
6873
}
6974

7075
public AuthorizationPolicyBuilder RequiresRole([NotNull] params string[] roles)
76+
{
77+
return RequiresRole((IEnumerable<string>)roles);
78+
}
79+
80+
public AuthorizationPolicyBuilder RequiresRole([NotNull] IEnumerable<string> roles)
7181
{
7282
RequiresClaim(ClaimTypes.Role, roles);
7383
return this;
@@ -81,7 +91,7 @@ public AuthorizationPolicyBuilder RequireAuthenticatedUser()
8191

8292
public AuthorizationPolicy Build()
8393
{
84-
return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes);
94+
return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes.Distinct());
8595
}
8696
}
8797
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Linq;
5+
using System.Security.Claims;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.AspNet.Security
9+
{
10+
public static class AuthorizationServiceExtensions
11+
{
12+
/// <summary>
13+
/// Checks if a user meets a specific authorization policy
14+
/// </summary>
15+
/// <param name="service">The authorization service.</param>
16+
/// <param name="user">The user to check the policy against.</param>
17+
/// <param name="resource">The resource the policy should be checked with.</param>
18+
/// <param name="policy">The policy to check against a specific context.</param>
19+
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
20+
public static Task<bool> AuthorizeAsync([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy)
21+
{
22+
if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() && user != null)
23+
{
24+
// Filter the user to only contain the active authentication types
25+
user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationTypes.Contains(i.AuthenticationType)));
26+
}
27+
return service.AuthorizeAsync(user, resource, policy.Requirements.ToArray());
28+
}
29+
30+
/// <summary>
31+
/// Checks if a user meets a specific authorization policy
32+
/// </summary>
33+
/// <param name="service">The authorization service.</param>
34+
/// <param name="user">The user to check the policy against.</param>
35+
/// <param name="resource">The resource the policy should be checked with.</param>
36+
/// <param name="policy">The policy to check against a specific context.</param>
37+
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
38+
public static bool Authorize([NotNull] this IAuthorizationService service, ClaimsPrincipal user, object resource, [NotNull] AuthorizationPolicy policy)
39+
{
40+
if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() && user != null)
41+
{
42+
// Filter the user to only contain the active authentication types
43+
user = new ClaimsPrincipal(user.Identities.Where(i => policy.ActiveAuthenticationTypes.Contains(i.AuthenticationType)));
44+
}
45+
return service.Authorize(user, resource, policy.Requirements.ToArray());
46+
}
47+
48+
}
49+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNet.Security
7+
{
8+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
9+
public class AuthorizeAttribute : Attribute
10+
{
11+
public AuthorizeAttribute() { }
12+
13+
public AuthorizeAttribute(string policy)
14+
{
15+
Policy = policy;
16+
}
17+
18+
public string Policy { get; set; }
19+
20+
// REVIEW: can we get rid of the , deliminated in Roles/AuthTypes
21+
public string Roles { get; set; }
22+
23+
public string ActiveAuthenticationTypes { get; set; }
24+
}
25+
}

src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,30 @@
33

44
using System;
55
using System.Linq;
6-
using System.Threading.Tasks;
76

87
namespace Microsoft.AspNet.Security
98
{
109
public class ClaimsAuthorizationHandler : AuthorizationHandler<ClaimsAuthorizationRequirement>
1110
{
12-
public override Task<bool> CheckAsync(AuthorizationContext context, ClaimsAuthorizationRequirement requirement)
11+
public override void Handle(AuthorizationContext context, ClaimsAuthorizationRequirement requirement)
1312
{
14-
if (context.Context.User == null)
13+
if (context.User != null)
1514
{
16-
return Task.FromResult(false);
15+
bool found = false;
16+
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
17+
{
18+
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
19+
}
20+
else
21+
{
22+
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
23+
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
24+
}
25+
if (found)
26+
{
27+
context.Succeed(requirement);
28+
}
1729
}
18-
19-
bool found = false;
20-
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
21-
{
22-
found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
23-
}
24-
else
25-
{
26-
found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
27-
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
28-
}
29-
return Task.FromResult(found);
3030
}
3131
}
3232
}

0 commit comments

Comments
 (0)