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

Latest authZ iteration #151

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/Microsoft.AspNet.Security/AuthorizationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNet.Http;

namespace Microsoft.AspNet.Security
{
Expand All @@ -13,27 +12,23 @@ namespace Microsoft.AspNet.Security
/// </summary>
public class AuthorizationContext
{
private HashSet<IAuthorizationRequirement> _pendingRequirements = new HashSet<IAuthorizationRequirement>();
private HashSet<IAuthorizationRequirement> _pendingRequirements;
private bool _failCalled;
private bool _succeedCalled;

public AuthorizationContext(
[NotNull] AuthorizationPolicy policy,
HttpContext context,
[NotNull] IEnumerable<IAuthorizationRequirement> 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<IAuthorizationRequirement>(requirements);
}

public AuthorizationPolicy Policy { get; private set; }
public ClaimsPrincipal User { get { return Context.User; } }
public HttpContext Context { get; private set; }
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
public ClaimsPrincipal User { get; private set; }
public object Resource { get; private set; }

public IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } }
Expand Down
80 changes: 45 additions & 35 deletions src/Microsoft.AspNet.Security/AuthorizationHandler.cs
Original file line number Diff line number Diff line change
@@ -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
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
where TRequirement : IAuthorizationRequirement
{
public void Handle(AuthorizationContext context)
{
foreach (var req in context.Requirements.OfType<TRequirement>())
{
Handle(context, req);
}
}

// await AuthorizeAsync<Album>(user, "Edit", albumInstance);
public virtual Task HandleAsync(AuthorizationContext context)
{
Handle(context);
return Task.FromResult(0);
}

// No policy name needed because this is auto based on resource (operation is the policy name)
//RegisterOperation which auto generates the policy for Authorize<T>
//bool AuthorizeAsync<TResource>(ClaimsPrincipal, string operation, TResource instance)
//bool AuthorizeAsync<TResource>(IAuthorization, ClaimsPrincipal, string operation, TResource instance)
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
// REVIEW: do we need an async hook too?
public abstract void Handle(AuthorizationContext context, TRequirement requirement);
}

public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler
where TResource : class
where TRequirement : IAuthorizationRequirement
{
public async Task HandleAsync(AuthorizationContext context)
public virtual async Task HandleAsync(AuthorizationContext context)
{
foreach (var req in context.Policy.Requirements.OfType<TRequirement>())
var resource = context.Resource as TResource;
// REVIEW: should we allow null resources?
if (resource != null)
{
if (await CheckAsync(context, req))
foreach (var req in context.Requirements.OfType<TRequirement>())
{
context.Succeed(req);
await HandleAsync(context, req, resource);
}
else
}
}

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<TRequirement>())
{
context.Fail();
Handle(context, req, resource);
}
}
}

public abstract Task<bool> CheckAsync(AuthorizationContext context, TRequirement requirement);
public abstract void Handle(AuthorizationContext context, TRequirement requirement, TResource resource);
}

// TODO:
//public abstract class AuthorizationHandler<TRequirement, TResource> : AuthorizationHandler<TRequirement>
// 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);
//}
}
66 changes: 62 additions & 4 deletions src/Microsoft.AspNet.Security/AuthorizationPolicy.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,77 @@
// 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
{
public class AuthorizationPolicy
{
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> activeAuthenticationTypes)
{
Requirements = requirements;
ActiveAuthenticationTypes = activeAuthenticationTypes;
Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
ActiveAuthenticationTypes = new List<string>(activeAuthenticationTypes).AsReadOnly();
}

public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
public IEnumerable<string> ActiveAuthenticationTypes { get; private set; }
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; private set; }
public IReadOnlyList<string> ActiveAuthenticationTypes { get; private set; }

public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies)
{
return Combine((IEnumerable<AuthorizationPolicy>)policies);
}

// TODO: Add unit tests
public static AuthorizationPolicy Combine([NotNull] IEnumerable<AuthorizationPolicy> 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<AuthorizeAttribute> attributes)
{
var policyBuilder = new AuthorizationPolicyBuilder();
bool any = false;
foreach (var authorizeAttribute in attributes.OfType<AuthorizeAttribute>())
{
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;
}
}
}
14 changes: 12 additions & 2 deletions src/Microsoft.AspNet.Security/AuthorizationPolicyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequireme
return this;
}

public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
public AuthorizationPolicyBuilder Combine([NotNull] AuthorizationPolicy policy)
{
AddAuthenticationTypes(policy.ActiveAuthenticationTypes.ToArray());
AddRequirements(policy.Requirements.ToArray());
return this;
}

public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues)
{
return RequiresClaim(claimType, (IEnumerable<string>)requiredValues);
}

public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, IEnumerable<string> requiredValues)
{
Requirements.Add(new ClaimsAuthorizationRequirement
{
Expand All @@ -68,6 +73,11 @@ public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType)
}

public AuthorizationPolicyBuilder RequiresRole([NotNull] params string[] roles)
{
return RequiresRole((IEnumerable<string>)roles);
}

public AuthorizationPolicyBuilder RequiresRole([NotNull] IEnumerable<string> roles)
{
RequiresClaim(ClaimTypes.Role, roles);
return this;
Expand All @@ -81,7 +91,7 @@ public AuthorizationPolicyBuilder RequireAuthenticatedUser()

public AuthorizationPolicy Build()
{
return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes);
return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes.Distinct());
}
}
}
49 changes: 49 additions & 0 deletions src/Microsoft.AspNet.Security/AuthorizationServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="service">The authorization service.</param>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">The resource the policy should be checked with.</param>
/// <param name="policy">The policy to check against a specific context.</param>
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
public static Task<bool> 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());
}

/// <summary>
/// Checks if a user meets a specific authorization policy
/// </summary>
/// <param name="service">The authorization service.</param>
/// <param name="user">The user to check the policy against.</param>
/// <param name="resource">The resource the policy should be checked with.</param>
/// <param name="policy">The policy to check against a specific context.</param>
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
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());
}

}
}
25 changes: 25 additions & 0 deletions src/Microsoft.AspNet.Security/AuthorizeAttribute.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
32 changes: 16 additions & 16 deletions src/Microsoft.AspNet.Security/ClaimsAuthorizationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@

using System;
using System.Linq;
using System.Threading.Tasks;

namespace Microsoft.AspNet.Security
{
public class ClaimsAuthorizationHandler : AuthorizationHandler<ClaimsAuthorizationRequirement>
{
public override Task<bool> 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);
}
}
}
Loading