-
Notifications
You must be signed in to change notification settings - Fork 218
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial commit to use policy instead of filter * add policies per david's feedback * PR feedback * update test
- Loading branch information
Showing
18 changed files
with
3,222 additions
and
343 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2,644 changes: 2,642 additions & 2 deletions
2,644
src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml
Large diffs are not rendered by default.
Oops, something went wrong.
26 changes: 26 additions & 0 deletions
26
src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.Identity.Web | ||
{ | ||
/// <summary> | ||
/// This is the metadata that describes required auth scopes for a given endpoint | ||
/// in a web API. It's the underlying data structure the requirement <see cref="ScopeAuthorizationRequirement"/> will look for | ||
/// in order to validate scopes in the scope claims. | ||
/// </summary> | ||
public interface IAuthRequiredScopeMetadata | ||
{ | ||
/// <summary> | ||
/// Scopes accepted by this web API. | ||
/// </summary> | ||
IEnumerable<string>? AcceptedScope { get; } | ||
|
||
/// <summary> | ||
/// Fully qualified name of the configuration key containing the required scopes (separated | ||
/// by spaces). | ||
/// </summary> | ||
string? RequiredScopeConfigurationKey { get; } | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.AspNetCore.Authorization; | ||
|
||
namespace Microsoft.Identity.Web | ||
{ | ||
/// <summary> | ||
/// Extensions for building the RequiredScope policy during application startup. | ||
/// </summary> | ||
/// <example> | ||
/// <code> | ||
/// services.AddAuthorization(o => | ||
/// { o.AddPolicy("Custom", | ||
/// policyBuilder =>policyBuilder.RequireScope("access_as_user")); | ||
/// }); | ||
/// </code> | ||
/// </example> | ||
public static class PolicyBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds a <see cref="ScopeAuthorizationRequirement"/> to the current instance which requires | ||
/// that the current user has the specified claim and that the claim value must be one of the allowed values. | ||
/// </summary> | ||
/// <param name="authorizationPolicyBuilder">Used for building policies during application startup.</param> | ||
/// <param name="allowedValues">Values the claim must process one or more of for evaluation to succeed.</param> | ||
/// <returns>A reference to this instance after the operation has completed.</returns> | ||
public static AuthorizationPolicyBuilder RequireScope( | ||
this AuthorizationPolicyBuilder authorizationPolicyBuilder, | ||
params string[] allowedValues) | ||
{ | ||
if (authorizationPolicyBuilder == null) | ||
{ | ||
throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); | ||
} | ||
|
||
return RequireScope(authorizationPolicyBuilder, (IEnumerable<string>)allowedValues); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a <see cref="ScopeAuthorizationRequirement"/> to the current instance which requires | ||
/// that the current user has the specified claim and that the claim value must be one of the allowed values. | ||
/// </summary> | ||
/// <param name="authorizationPolicyBuilder">Used for building policies during application startup.</param> | ||
/// <param name="allowedValues">Values the claim must process one or more of for evaluation to succeed.</param> | ||
/// <returns>A reference to this instance after the operation has completed.</returns> | ||
public static AuthorizationPolicyBuilder RequireScope( | ||
this AuthorizationPolicyBuilder authorizationPolicyBuilder, | ||
IEnumerable<string> allowedValues) | ||
{ | ||
if (authorizationPolicyBuilder == null) | ||
{ | ||
throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); | ||
} | ||
|
||
authorizationPolicyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(allowedValues)); | ||
return authorizationPolicyBuilder; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Identity.Web | ||
{ | ||
/// <summary> | ||
/// RequireScopeOptions. | ||
/// </summary> | ||
internal class RequireScopeOptions : IPostConfigureOptions<AuthorizationOptions> | ||
{ | ||
private readonly AuthorizationPolicy _defaultPolicy; | ||
|
||
/// <summary> | ||
/// Sets the default policy. | ||
/// </summary> | ||
public RequireScopeOptions() | ||
{ | ||
_defaultPolicy = new AuthorizationPolicyBuilder() | ||
.AddRequirements(new ScopeAuthorizationRequirement()) | ||
.Build(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void PostConfigure( | ||
string name, | ||
AuthorizationOptions options) | ||
{ | ||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
|
||
options.DefaultPolicy = options.DefaultPolicy is null | ||
? _defaultPolicy | ||
: AuthorizationPolicy.Combine(options.DefaultPolicy, _defaultPolicy); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Identity.Web | ||
{ | ||
/// <summary> | ||
/// Extensions for building the required scope attribute during application startup. | ||
/// </summary> | ||
public static class RequiredScopeExtensions | ||
{ | ||
/// <summary> | ||
/// This method adds support for the required scope attribute. It adds a default policy that | ||
/// adds a scope requirement. This requirement looks for IAuthRequiredScopeMetadata on the current endpoint. | ||
/// </summary> | ||
/// <param name="services">The services being configured.</param> | ||
/// <returns>Services.</returns> | ||
public static IServiceCollection AddRequiredScopeAuthorization(this IServiceCollection services) | ||
{ | ||
services.AddAuthorization(); | ||
|
||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<AuthorizationOptions>, RequireScopeOptions>()); | ||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthorizationHandler, ScopeAuthorizationHandler>()); | ||
return services; | ||
} | ||
|
||
/// <summary> | ||
/// This method adds metadata to route endpoint to describe required scopes. It's the imperative version of | ||
/// the [RequiredScope] attribute. | ||
/// </summary> | ||
/// <typeparam name="TBuilder">Class implementing <see cref="IEndpointConventionBuilder"/>.</typeparam> | ||
/// <param name="endpointConventionBuilder">To customize the endpoints.</param> | ||
/// <param name="scope">Scope.</param> | ||
/// <returns>Builder.</returns> | ||
public static TBuilder RequireScope<TBuilder>(this TBuilder endpointConventionBuilder, params string[] scope) | ||
where TBuilder : IEndpointConventionBuilder | ||
{ | ||
return endpointConventionBuilder.WithMetadata(new RequiredScopeMetadata(scope)); | ||
} | ||
|
||
private sealed class RequiredScopeMetadata : IAuthRequiredScopeMetadata | ||
{ | ||
public RequiredScopeMetadata(string[] scope) | ||
{ | ||
AcceptedScope = scope; | ||
} | ||
|
||
public IEnumerable<string>? AcceptedScope { get; } | ||
|
||
public string? RequiredScopeConfigurationKey { get; } | ||
} | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Configuration; | ||
|
||
namespace Microsoft.Identity.Web | ||
{ | ||
/// <summary> | ||
/// Scope authorization handler that needs to be called for a specific requirement type. | ||
/// In this case, <see cref="ScopeAuthorizationRequirement"/>. | ||
/// </summary> | ||
internal class ScopeAuthorizationHandler : AuthorizationHandler<ScopeAuthorizationRequirement> | ||
{ | ||
private readonly IConfiguration _configuration; | ||
|
||
/// <summary> | ||
/// Constructor for the scope authorization handler, which takes a configuration. | ||
/// </summary> | ||
/// <param name="configuration">Configuration.</param> | ||
public ScopeAuthorizationHandler(IConfiguration configuration) | ||
{ | ||
_configuration = configuration; | ||
} | ||
|
||
/// <summary> | ||
/// Makes a decision if authorization is allowed based on a specific requirement. | ||
/// </summary> | ||
/// <param name="context">AuthorizationHandlerContext.</param> | ||
/// <param name="requirement">Scope requirement.</param> | ||
/// <returns>Task.</returns> | ||
protected override Task HandleRequirementAsync( | ||
AuthorizationHandlerContext context, | ||
ScopeAuthorizationRequirement requirement) | ||
{ | ||
if (context == null) | ||
{ | ||
throw new ArgumentNullException(nameof(context)); | ||
} | ||
|
||
// The resource is either the HttpContext or the Endpoint directly when used with the | ||
// authorization middleware | ||
var endpoint = context.Resource switch | ||
{ | ||
HttpContext httpContext => httpContext.GetEndpoint(), | ||
Endpoint ep => ep, | ||
_ => null, | ||
}; | ||
|
||
var data = endpoint?.Metadata.GetMetadata<IAuthRequiredScopeMetadata>(); | ||
|
||
IEnumerable<string>? scopes = null; | ||
if (requirement?.RequiredScopesConfigurationKey != null) | ||
{ | ||
scopes = _configuration.GetValue<string>(requirement?.RequiredScopesConfigurationKey)?.Split(' '); | ||
} | ||
|
||
if (scopes is null) | ||
{ | ||
scopes = requirement?.AllowedValues ?? data?.AcceptedScope; | ||
} | ||
|
||
// Can't determine what to do without scope metadata, so proceed | ||
if (scopes is null) | ||
{ | ||
context.Succeed(requirement); | ||
return Task.CompletedTask; | ||
} | ||
|
||
Claim? scopeClaim = context.User.FindFirst(ClaimConstants.Scp) ?? context.User.FindFirst(ClaimConstants.Scope); | ||
|
||
if (scopeClaim is null) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
if (scopeClaim != null && scopeClaim.Value.Split(' ').Intersect(scopes).Any()) | ||
{ | ||
context.Succeed(requirement); | ||
return Task.CompletedTask; | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
} | ||
} |
Oops, something went wrong.