From 8ac0bb4916b1c3ea12269c05e0df868a4b614b95 Mon Sep 17 00:00:00 2001 From: Jennyf19 Date: Fri, 27 May 2022 20:21:29 -0700 Subject: [PATCH 1/4] initial commit for app permissions --- .../Microsoft.Identity.Web.xml | 232 ++++++++++++++++++ ...uthRequiredScopeOrAppPermissionMetadata.cs | 38 +++ .../RequireScopeOrAppPermissionOptions.cs | 42 ++++ .../RequiredScopeOrAppPermissionAttribute.cs | 106 ++++++++ .../RequiredScopeOrAppPermissionExtensions.cs | 65 +++++ .../Policy/ScopeAuthorizationHandler.cs | 1 - ...copeOrAppPermissionAuthorizationHandler.cs | 119 +++++++++ ...OrAppPermissionAuthorizationRequirement.cs | 75 ++++++ ...tyWebApiAuthenticationBuilderExtensions.cs | 1 + 9 files changed, 678 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs create mode 100644 src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs create mode 100644 src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs create mode 100644 src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs create mode 100644 src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs create mode 100644 src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index 26f9eb734..7e740b29a 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -216,6 +216,23 @@ The current HTTP Context, such as req.HttpContext. A task indicating success or failure. In case of failure . + + + Azure SDK token credential for App tokens based on the ITokenAcquisition service. + + + + + Constructor from an ITokenAcquisition service. + + Token acquisition. + + + + + + + Azure SDK token credential based on the ITokenAcquisition service. @@ -1731,6 +1748,36 @@ by spaces). + + + This is the metadata that describes required auth scopes or app permissions for a given endpoint + in a web API. It's the underlying data structure the requirement will look for + in order to validate scopes in the scope claims. + + + + + App permissions accepted by this web API. + App permissions appear in the roles claim of the token. + + + + + Fully qualified name of the configuration key containing the required scopes + or app permissions (separated by spaces). + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + Extensions for building the RequiredScope policy during application startup. @@ -1829,6 +1876,96 @@ Unused: Compatibility of interface with the Authorization Filter. + + + This attribute is used on a controller, pages, or controller actions + to declare (and validate) the scopes or app permissions required by a web API. + These scopes or app permissions can be declared in two ways: + hardcoding them, or declaring them in the configuration. Depending on your + choice, use either one or the other of the constructors. + For details, see https://aka.ms/ms-id-web/required-scope-or-app-permissions-attribute. + + + + + Scopes accepted by this web API. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "Scopes" contains the required scopes, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + + + + + + Unused: Compatibility of interface with the Authorization Filter. + + + + + App permissions accepted by this web API. + App permissions appear in the roles claim of the token. + + + + + Fully qualified name of the configuration key containing the required app permissions (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "AppPermissions" contains the required app permissions, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey="AzureAd:AppPermissions")] + + + + + + Verifies that the web API is called with the right app permissions. + If the token obtained for this API is on behalf of the authenticated user does not have + any of these in its scope claim, + nor in its roles claim, the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + + Scopes accepted by this web API. + App permissions accepted by this web API. + When neither the scopes nor app permissions match, the response is a 403 (Forbidden), + because the user is authenticated (hence not 401), but not authorized. + + Add the following attribute on the controller/page/action to protect: + + + [RequiredScopeOrAppPermissionAttribute(new string[] { "access_as_user" }, new string[] { "access_as_app" })] + + + and + if you want to express the required scopes or app permissions from the configuration. + + + + Default constructor. + + + + [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAD:Scope", RequiredAppPermissionsConfigurationKey="AzureAD:AppPermission")] + class Controller : BaseController + { + } + + + Interface implemented by diagnostics for the JWT Bearer middleware. @@ -2079,6 +2216,31 @@ Scope. Builder. + + + Extensions for building the required scope or app permission attribute during application startup. + + + + + This method adds support for the required scope or app permission attribute. It adds a default policy that + adds a scope requirement or app permission requirement. + This requirement looks for IAuthRequiredScopeOrAppPermissionMetadata on the current endpoint. + + The services being configured. + Services. + + + + This method adds metadata to route endpoint to describe required scopes or app permissions. It's the imperative version of + the [RequiredScopeOrAppPermission] attribute. + + Class implementing . + To customize the endpoints. + Scope. + App permission. + Builder. + RequireScopeOptions. @@ -2092,6 +2254,19 @@ + + + RequireScopeOrAppPermissionOptions. + + + + + Sets the default policy. + + + + + Scope authorization handler that needs to be called for a specific requirement type. @@ -2138,6 +2313,63 @@ + + + Scope or app permission authorization handler that needs to be called for a specific requirement type. + In this case, . + + + + + Constructor for the scope authorization handler, which takes a configuration. + + Configuration. + + + + Makes a decision if authorization is allowed based on a specific requirement. + + AuthorizationHandlerContext. + Scope authorization requirement. + Task. + + + + Implements an + which requires at least one instance of the specified claim type, and, if allowed values are specified, + the claim value must be any of the allowed values. + + + + + Creates a new instance of . + + The optional list of scope values. + + + + + Gets the optional list of scope values. + + + + + Gets the optional list of app permission values. + + + + + Gets the optional list of scope values from configuration. + + + + + Gets the optional list of app permission values from configuration. + + + + + Options passed-in to create the AadIssuerValidator object. diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs new file mode 100644 index 000000000..7f8094537 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Identity.Web +{ + /// + /// This is the metadata that describes required auth scopes or app permissions for a given endpoint + /// in a web API. It's the underlying data structure the requirement will look for + /// in order to validate scopes in the scope claims. + /// + public interface IAuthRequiredScopeOrAppPermissionMetadata + { + /// + /// App permissions accepted by this web API. + /// App permissions appear in the roles claim of the token. + /// + IEnumerable? AcceptedAppPermission { get; } + + /// + /// Fully qualified name of the configuration key containing the required scopes + /// or app permissions (separated by spaces). + /// + string? RequiredAppPermissionsConfigurationKey { get; } + + /// + /// Scopes accepted by this web API. + /// + IEnumerable? AcceptedScope { get; } + + /// + /// Fully qualified name of the configuration key containing the required scopes (separated + /// by spaces). + /// + string? RequiredScopesConfigurationKey { get; } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs b/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs new file mode 100644 index 000000000..787951d26 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequireScopeOrAppPermissionOptions.cs @@ -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 +{ + /// + /// RequireScopeOrAppPermissionOptions. + /// + internal class RequireScopeOrAppPermissionOptions : IPostConfigureOptions + { + private readonly AuthorizationPolicy _defaultPolicy; + + /// + /// Sets the default policy. + /// + public RequireScopeOrAppPermissionOptions() + { + _defaultPolicy = new AuthorizationPolicyBuilder() + .AddRequirements(new ScopeOrAppPermissionAuthorizationRequirement()) + .Build(); + } + + /// + 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); + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs new file mode 100644 index 000000000..6ec4c9be7 --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Identity.Web.Resource +{ + /// + /// This attribute is used on a controller, pages, or controller actions + /// to declare (and validate) the scopes or app permissions required by a web API. + /// These scopes or app permissions can be declared in two ways: + /// hardcoding them, or declaring them in the configuration. Depending on your + /// choice, use either one or the other of the constructors. + /// For details, see https://aka.ms/ms-id-web/required-scope-or-app-permissions-attribute. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredScopeOrAppPermissionMetadata + { + /// + /// Scopes accepted by this web API. + /// + public IEnumerable? AcceptedScope { get; set; } + + /// + /// Fully qualified name of the configuration key containing the required scopes (separated + /// by spaces). + /// + /// + /// If the appsettings.json file contains a section named "AzureAd", in which + /// a property named "Scopes" contains the required scopes, the attribute on the + /// controller/page/action to protect should be set to the following: + /// + /// [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + /// + /// + public string? RequiredScopesConfigurationKey { get; set; } + + /// + /// Unused: Compatibility of interface with the Authorization Filter. + /// + public bool IsReusable { get; set; } + + /// + /// App permissions accepted by this web API. + /// App permissions appear in the roles claim of the token. + /// + public IEnumerable? AcceptedAppPermission { get; set; } + + /// + /// Fully qualified name of the configuration key containing the required app permissions (separated + /// by spaces). + /// + /// + /// If the appsettings.json file contains a section named "AzureAd", in which + /// a property named "AppPermissions" contains the required app permissions, the attribute on the + /// controller/page/action to protect should be set to the following: + /// + /// [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey="AzureAd:AppPermissions")] + /// + /// + public string? RequiredAppPermissionsConfigurationKey { get; set; } + + /// + /// Verifies that the web API is called with the right app permissions. + /// If the token obtained for this API is on behalf of the authenticated user does not have + /// any of these in its scope claim, + /// nor in its roles claim, the + /// method updates the HTTP response providing a status code 403 (Forbidden) + /// and writes to the response body a message telling which scopes are expected in the token. + /// + /// Scopes accepted by this web API. + /// App permissions accepted by this web API. + /// When neither the scopes nor app permissions match, the response is a 403 (Forbidden), + /// because the user is authenticated (hence not 401), but not authorized. + /// + /// Add the following attribute on the controller/page/action to protect: + /// + /// + /// [RequiredScopeOrAppPermissionAttribute(new string[] { "access_as_user" }, new string[] { "access_as_app" })] + /// + /// + /// and + /// if you want to express the required scopes or app permissions from the configuration. + public RequiredScopeOrAppPermissionAttribute(string[] acceptedScopes, string[] acceptedAppPermissions) + { + AcceptedScope = acceptedScopes ?? throw new ArgumentNullException(nameof(acceptedScopes)); + AcceptedAppPermission = acceptedAppPermissions ?? throw new ArgumentNullException(nameof(acceptedAppPermissions)); + } + + /// + /// Default constructor. + /// + /// + /// + /// [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAD:Scope", RequiredAppPermissionsConfigurationKey="AzureAD:AppPermission")] + /// class Controller : BaseController + /// { + /// } + /// + /// + public RequiredScopeOrAppPermissionAttribute() + { + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs new file mode 100644 index 000000000..02fad8a6c --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs @@ -0,0 +1,65 @@ +// 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 +{ + /// + /// Extensions for building the required scope or app permission attribute during application startup. + /// + public static class RequiredScopeOrAppPermissionExtensions + { + /// + /// This method adds support for the required scope or app permission attribute. It adds a default policy that + /// adds a scope requirement or app permission requirement. + /// This requirement looks for IAuthRequiredScopeOrAppPermissionMetadata on the current endpoint. + /// + /// The services being configured. + /// Services. + public static IServiceCollection AddRequiredScopeOrAppPermissionAuthorization(this IServiceCollection services) + { + services.AddAuthorization(); + + services.TryAddEnumerable(ServiceDescriptor.Singleton, RequireScopeOrAppPermissionOptions>()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + return services; + } + + /// + /// This method adds metadata to route endpoint to describe required scopes or app permissions. It's the imperative version of + /// the [RequiredScopeOrAppPermission] attribute. + /// + /// Class implementing . + /// To customize the endpoints. + /// Scope. + /// App permission. + /// Builder. + public static TBuilder RequireScope(this TBuilder endpointConventionBuilder, string[] scope, string[] appPermission) + where TBuilder : IEndpointConventionBuilder + { + return endpointConventionBuilder.WithMetadata(new RequiredScopeOrAppPermissionMetadata(scope, appPermission)); + } + + private sealed class RequiredScopeOrAppPermissionMetadata : IAuthRequiredScopeMetadata + { + public RequiredScopeOrAppPermissionMetadata(string[] scope, string[] appPermission) + { + AcceptedScope = scope; + AcceptedAppPermission = appPermission; + } + + public IEnumerable? AcceptedScope { get; } + public IEnumerable? AcceptedAppPermission { get; } + + public string? RequiredScopesConfigurationKey { get; } + public string? RequiredAppPermissionsConfigurationKey { get; } + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs b/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs index 39fb05d51..a96f05ff7 100644 --- a/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs +++ b/src/Microsoft.Identity.Web/Policy/ScopeAuthorizationHandler.cs @@ -4,7 +4,6 @@ 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; diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs new file mode 100644 index 000000000..ff2afac7e --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs @@ -0,0 +1,119 @@ +// 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 +{ + /// + /// Scope or app permission authorization handler that needs to be called for a specific requirement type. + /// In this case, . + /// + internal class ScopeOrAppPermissionAuthorizationHandler : AuthorizationHandler + { + private readonly IConfiguration _configuration; + + /// + /// Constructor for the scope authorization handler, which takes a configuration. + /// + /// Configuration. + public ScopeOrAppPermissionAuthorizationHandler(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + /// Makes a decision if authorization is allowed based on a specific requirement. + /// + /// AuthorizationHandlerContext. + /// Scope authorization requirement. + /// Task. + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + ScopeOrAppPermissionAuthorizationRequirement requirement) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (requirement is null) + { + throw new ArgumentNullException(nameof(requirement)); + } + + // 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(); + + IEnumerable? scopes = null; + IEnumerable? appPermissions = null; + + var scopeConfigurationKey = requirement.RequiredScopesConfigurationKey ?? data?.RequiredScopesConfigurationKey; + + if (scopeConfigurationKey != null) + { + scopes = _configuration.GetValue(scopeConfigurationKey)?.Split(' '); + } + + if (scopes is null) + { + scopes = requirement.ScopeAllowedValues ?? data?.AcceptedScope; + } + + var appPermissionConfigurationKey = requirement.RequiredAppPermissionsConfigurationKey ?? data?.RequiredAppPermissionsConfigurationKey; + + if (appPermissionConfigurationKey != null) + { + appPermissions = _configuration.GetValue(appPermissionConfigurationKey)?.Split(' '); + } + + if (appPermissions is null) + { + appPermissions = requirement.AppPermissionAllowedValues ?? data?.AcceptedAppPermission; + } + + // Can't determine what to do without scope or app permission metadata, so proceed + if (scopes is null && appPermissions is null) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + var scopeClaims = context.User.FindAll(ClaimConstants.Scp) + .Union(context.User.FindAll(ClaimConstants.Scope)) + .ToList(); + + bool appPermissionMatch = appPermissions.Any(p => context.User.IsInRole(p)); + + if (!scopeClaims.Any() && !appPermissionMatch) + { + return Task.CompletedTask; + } + + var hasScope = scopeClaims.SelectMany(s => s.Value.Split(' ')).Intersect(scopes).Any(); + + if (hasScope || appPermissionMatch) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs new file mode 100644 index 000000000..5d56e7adf --- /dev/null +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; + +namespace Microsoft.Identity.Web +{ + /// + /// Implements an + /// which requires at least one instance of the specified claim type, and, if allowed values are specified, + /// the claim value must be any of the allowed values. + /// + public class ScopeOrAppPermissionAuthorizationRequirement : IAuthorizationRequirement + { + /// + /// Creates a new instance of . + /// + /// The optional list of scope values. + /// + public ScopeOrAppPermissionAuthorizationRequirement( + IEnumerable? scopeAllowedValues = null, + IEnumerable? appPermissionAllowedValues = null) + { + ScopeAllowedValues = scopeAllowedValues; + AppPermissionAllowedValues = appPermissionAllowedValues; + } + + /// + /// Gets the optional list of scope values. + /// + public IEnumerable? ScopeAllowedValues { get; } + + /// + /// Gets the optional list of app permission values. + /// + public IEnumerable? AppPermissionAllowedValues { get; } + + /// + /// Gets the optional list of scope values from configuration. + /// + public string? RequiredScopesConfigurationKey { get; set; } + + /// + /// Gets the optional list of app permission values from configuration. + /// + public string? RequiredAppPermissionsConfigurationKey { get; set; } + + /// + public override string ToString() + { + string value; + + if (ScopeAllowedValues == null && AppPermissionAllowedValues == null) + { + value = string.Empty; + } + else if (ScopeAllowedValues != null && AppPermissionAllowedValues != null) + { + value = $" and `{ClaimConstants.Scp}` or `{ClaimConstants.Scope}` is one of the following values: ({string.Join("|", ScopeAllowedValues)}) or " + + $"TokenValidationParameters.RoleClaimType is one of th following values: ({string.Join("|", AppPermissionAllowedValues)})"; + } + else if (ScopeAllowedValues != null) + { + value = $" and `{ClaimConstants.Scp}` or `{ClaimConstants.Scope}` is one of the following values: ({string.Join("|", ScopeAllowedValues)})"; + } + else + { + value = $"TokenValidationParameters.RoleClaimType is one of th following values: ({string.Join("|", AppPermissionAllowedValues)})"; + } + + return $"{nameof(ScopeOrAppPermissionAuthorizationRequirement)}:Scope/AppPermission={value}"; + } + } +} diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs index f64700f9d..e877e54e0 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs @@ -163,6 +163,7 @@ private static void AddMicrosoftIdentityWebApiImplementation( builder.Services.AddHttpClient(); builder.Services.TryAddSingleton(); builder.Services.AddRequiredScopeAuthorization(); + builder.Services.AddRequiredScopeOrAppPermissionAuthorization(); builder.Services.AddOptions(); if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) From b54a7bc28b4e72dbde52560e449abe2c308eca7f Mon Sep 17 00:00:00 2001 From: Jennyf19 Date: Mon, 30 May 2022 19:15:04 -0700 Subject: [PATCH 2/4] add test coverage --- .../Microsoft.Identity.Web.xml | 20 ++ .../Policy/PolicyBuilderExtensions.cs | 48 +++++ ...copeOrAppPermissionAuthorizationHandler.cs | 12 +- ...OrAppPermissionAuthorizationRequirement.cs | 2 +- ...RequiredScopeOrAppPermissionPolicyTests.cs | 202 ++++++++++++++++++ 5 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index 7e740b29a..3f1fd14c0 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -1809,6 +1809,26 @@ Values the claim must process one or more of for evaluation to succeed. A reference to this instance after the operation has completed. + + + Adds a 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. + + Used for building policies during application startup. + Values the claim must process one or more of for evaluation to succeed. + Values the claim must process one or more of for evaluation to succeed. + A reference to this instance after the operation has completed. + + + + Adds a 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. + + Used for building policies during application startup. + Values the claim must process one or more of for evaluation to succeed. + Values the claim must process one or more of for evaluation to succeed. + A reference to this instance after the operation has completed. + This attribute is used on a controller, pages, or controller actions diff --git a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs index d5c5ac2ae..e00798c7d 100644 --- a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs @@ -58,5 +58,53 @@ public static AuthorizationPolicyBuilder RequireScope( authorizationPolicyBuilder.Requirements.Add(new ScopeAuthorizationRequirement(allowedValues)); return authorizationPolicyBuilder; } + + /// + /// Adds a 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. + /// + /// Used for building policies during application startup. + /// Values the claim must process one or more of for evaluation to succeed. + /// Values the claim must process one or more of for evaluation to succeed. + /// A reference to this instance after the operation has completed. + public static AuthorizationPolicyBuilder RequireScopeOrAppPermission( + this AuthorizationPolicyBuilder authorizationPolicyBuilder, + string[] allowedScopeValues, + string[] allowedAppPermissionValues) + { + if (authorizationPolicyBuilder == null) + { + throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); + } + + return RequireScopeOrAppPermission( + authorizationPolicyBuilder, + (IEnumerable)allowedScopeValues, + (IEnumerable)allowedAppPermissionValues); + } + + /// + /// Adds a 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. + /// + /// Used for building policies during application startup. + /// Values the claim must process one or more of for evaluation to succeed. + /// Values the claim must process one or more of for evaluation to succeed. + /// A reference to this instance after the operation has completed. + public static AuthorizationPolicyBuilder RequireScopeOrAppPermission( + this AuthorizationPolicyBuilder authorizationPolicyBuilder, + IEnumerable allowedScopeValues, + IEnumerable allowedAppPermissionValues) + { + if (authorizationPolicyBuilder == null) + { + throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); + } + + authorizationPolicyBuilder.Requirements.Add(new ScopeOrAppPermissionAuthorizationRequirement( + allowedScopeValues, + allowedAppPermissionValues)); + return authorizationPolicyBuilder; + } } } diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs index ff2afac7e..27bf6ec0c 100644 --- a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationHandler.cs @@ -98,16 +98,20 @@ protected override Task HandleRequirementAsync( .Union(context.User.FindAll(ClaimConstants.Scope)) .ToList(); - bool appPermissionMatch = appPermissions.Any(p => context.User.IsInRole(p)); + var appPermissionClaims = context.User.FindAll(ClaimConstants.Role) + .Union(context.User.FindAll(ClaimConstants.Roles)) + .ToList(); - if (!scopeClaims.Any() && !appPermissionMatch) + if (!scopeClaims.Any() && !appPermissionClaims.Any()) { return Task.CompletedTask; } + + var hasScope = scopes != null && scopeClaims.SelectMany(s => s.Value.Split(' ')).Intersect(scopes).Any(); - var hasScope = scopeClaims.SelectMany(s => s.Value.Split(' ')).Intersect(scopes).Any(); + var hasAppPermission = appPermissions != null && appPermissionClaims.SelectMany(s => s.Value.Split(' ')).Intersect(appPermissions).Any(); - if (hasScope || appPermissionMatch) + if (hasScope || hasAppPermission) { context.Succeed(requirement); return Task.CompletedTask; diff --git a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs index 5d56e7adf..d4ca91e47 100644 --- a/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs +++ b/src/Microsoft.Identity.Web/Policy/ScopeOrAppPermissionAuthorizationRequirement.cs @@ -66,7 +66,7 @@ public override string ToString() } else { - value = $"TokenValidationParameters.RoleClaimType is one of th following values: ({string.Join("|", AppPermissionAllowedValues)})"; + value = $" and `{ClaimConstants.Roles}` or `{ClaimConstants.Roles}` is one of the following values: ({string.Join("|", AppPermissionAllowedValues!)})"; } return $"{nameof(ScopeOrAppPermissionAuthorizationRequirement)}:Scope/AppPermission={value}"; diff --git a/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs b/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs new file mode 100644 index 000000000..6f6ae311c --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test/Resource/RequiredScopeOrAppPermissionPolicyTests.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Identity.Web.Resource; +using Microsoft.Identity.Web.Test.Common; +using Xunit; + +namespace Microsoft.Identity.Web.Test.Resource +{ + [RequiredScopeOrAppPermission(new string[] { "access_as_user" }, new string[] { "access_as_app" })] + public class RequiredScopeOrAppPermissionPolicyTests + { + private IServiceProvider _provider; + private const string ConfigSectionName = "AzureAd"; + private const string MultipleAppPermissions = "access_as_app_read_only access_as_app"; + private const string AppPermission = "access_as_app"; + private const string Scope = "user.read"; + private const string PolicyName = "foo"; + + [Theory] + [InlineData(ClaimConstants.Role)] + [InlineData(ClaimConstants.Role, true)] + [InlineData(ClaimConstants.Roles)] + [InlineData(ClaimConstants.Roles, true)] + [RequiredScopeOrAppPermission(RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermission")] + public async void VerifyAppHasAnyAcceptedAppPermission_TestAsync( + string claimType, + bool withConfig = false) + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null, + withConfig); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(claimType, AppPermission) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Theory] + [InlineData(ClaimConstants.Role)] + [InlineData(ClaimConstants.Role, true)] + [InlineData(ClaimConstants.Roles)] + [InlineData(ClaimConstants.Roles, true)] + public async void VerifyAppHasAnyAcceptedAppPermission_OneAppPermissionMatches_TestAsync( + string claimType, + bool withConfig = false) + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null, + withConfig); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(claimType, MultipleAppPermissions) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedAppPermission_WithMismatchAppPermissionTest_FailsAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, "access_as_app2") })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.False(allowed.Succeeded); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedAppPermission_RequiredAppPermissionMissingAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + null, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + [Fact] + public async void IncorrectPolicyName_FailsAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + "foobar", + AppPermission, + null); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission) })); + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => authorizationService.AuthorizeAsync(user, PolicyName)).ConfigureAwait(false); + Assert.Equal("No policy found: foo.", exception.Message); + } + + [Fact] + public async void VerifyAppHasAnyAcceptedScopeOrAppPermission_TestAsync() + { + // Arrange + var authorizationService = BuildAuthorizationService( + PolicyName, + AppPermission, + Scope); + + var user = new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] { new Claim(ClaimConstants.Role, AppPermission), new Claim(ClaimConstants.Scp, Scope) })); + + // Act + var allowed = await authorizationService.AuthorizeAsync(user, PolicyName).ConfigureAwait(false); + + // Assert + Assert.True(allowed.Succeeded); + } + + private IAuthorizationService BuildAuthorizationService( + string policy, + string appPermission, + string scope, + bool withConfig = false) + { + var configAsDictionary = new Dictionary() + { + { ConfigSectionName, null }, + { $"{ConfigSectionName}:Instance", TestConstants.AadInstance }, + { $"{ConfigSectionName}:TenantId", TestConstants.TenantIdAsGuid }, + { $"{ConfigSectionName}:ClientId", TestConstants.ClientId }, + { $"{ConfigSectionName}:AppPermission", appPermission }, + { $"{ConfigSectionName}:Scope", scope }, + }; + var memoryConfigSource = new MemoryConfigurationSource { InitialData = configAsDictionary }; + + IHostBuilder hostBuilder = Host.CreateDefaultBuilder() + .ConfigureHostConfiguration(configBuilder => + { + configBuilder.Add(memoryConfigSource); + configBuilder.Build().GetSection(ConfigSectionName); + }) + .ConfigureServices(services => + { + services.AddAuthorization(options => + { + options.AddPolicy(policy, policyBuilder => + { + if (withConfig) + { + policyBuilder.Requirements.Add(new ScopeOrAppPermissionAuthorizationRequirement() { RequiredAppPermissionsConfigurationKey = $"{ConfigSectionName}:AppPermission" }); + } + else + { + policyBuilder.RequireScopeOrAppPermission(scope?.Split(' '), appPermission?.Split(' ')); + } + }); + }); + services.AddLogging(); + services.AddOptions(); + services.AddSingleton(); + services.AddRequiredScopeOrAppPermissionAuthorization(); + }); + _provider = hostBuilder.Build().Services; + return _provider.GetRequiredService(); + } + } +} From 728846a536fa058b670e3f7e4a0a499833e07d53 Mon Sep 17 00:00:00 2001 From: Jennyf19 Date: Tue, 31 May 2022 10:51:31 -0700 Subject: [PATCH 3/4] remove IEnumerable and use string[] --- .../Policy/IAuthRequiredScopeMetadata.cs | 2 +- .../Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs | 4 ++-- src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs | 2 +- src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs | 2 +- .../Policy/RequiredScopeOrAppPermissionAttribute.cs | 4 ++-- .../Policy/RequiredScopeOrAppPermissionExtensions.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs index 25a3ee3c0..6d34324bc 100644 --- a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeMetadata.cs @@ -15,7 +15,7 @@ public interface IAuthRequiredScopeMetadata /// /// Scopes accepted by this web API. /// - IEnumerable? AcceptedScope { get; } + string[]? AcceptedScope { get; } /// /// Fully qualified name of the configuration key containing the required scopes (separated diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs index 7f8094537..3071e4110 100644 --- a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs @@ -16,7 +16,7 @@ public interface IAuthRequiredScopeOrAppPermissionMetadata /// App permissions accepted by this web API. /// App permissions appear in the roles claim of the token. /// - IEnumerable? AcceptedAppPermission { get; } + string[]? AcceptedAppPermission { get; } /// /// Fully qualified name of the configuration key containing the required scopes @@ -27,7 +27,7 @@ public interface IAuthRequiredScopeOrAppPermissionMetadata /// /// Scopes accepted by this web API. /// - IEnumerable? AcceptedScope { get; } + string[]? AcceptedScope { get; } /// /// Fully qualified name of the configuration key containing the required scopes (separated diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs index 0dc314361..32a731965 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeAttribute.cs @@ -19,7 +19,7 @@ public class RequiredScopeAttribute : Attribute, IAuthRequiredScopeMetadata /// /// Scopes accepted by this web API. /// - public IEnumerable? AcceptedScope { get; set; } + public string[]? AcceptedScope { get; set; } /// /// Fully qualified name of the configuration key containing the required scopes (separated diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs index 39cc41c04..304682f18 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeExtensions.cs @@ -52,7 +52,7 @@ public RequiredScopeMetadata(string[] scope) AcceptedScope = scope; } - public IEnumerable? AcceptedScope { get; } + public string[]? AcceptedScope { get; } public string? RequiredScopesConfigurationKey { get; } } diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs index 6ec4c9be7..5db3af692 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs @@ -20,7 +20,7 @@ public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredSco /// /// Scopes accepted by this web API. /// - public IEnumerable? AcceptedScope { get; set; } + public string[]? AcceptedScope { get; set; } /// /// Fully qualified name of the configuration key containing the required scopes (separated @@ -45,7 +45,7 @@ public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredSco /// App permissions accepted by this web API. /// App permissions appear in the roles claim of the token. /// - public IEnumerable? AcceptedAppPermission { get; set; } + public string[]? AcceptedAppPermission { get; set; } /// /// Fully qualified name of the configuration key containing the required app permissions (separated diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs index 02fad8a6c..c2260f3d9 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs @@ -55,8 +55,8 @@ public RequiredScopeOrAppPermissionMetadata(string[] scope, string[] appPermissi AcceptedAppPermission = appPermission; } - public IEnumerable? AcceptedScope { get; } - public IEnumerable? AcceptedAppPermission { get; } + public string[]? AcceptedScope { get; } + public string[]? AcceptedAppPermission { get; } public string? RequiredScopesConfigurationKey { get; } public string? RequiredAppPermissionsConfigurationKey { get; } From a5ef7cbc19480a381dd29d0bb3e445d4d068b8c5 Mon Sep 17 00:00:00 2001 From: Jennyf19 Date: Tue, 31 May 2022 21:09:24 -0700 Subject: [PATCH 4/4] PR comments --- ...uthRequiredScopeOrAppPermissionMetadata.cs | 8 +++--- .../Policy/PolicyBuilderExtensions.cs | 28 ++----------------- .../RequiredScopeOrAppPermissionAttribute.cs | 9 ++---- .../RequiredScopeOrAppPermissionExtensions.cs | 2 +- 4 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs index 3071e4110..9e7597431 100644 --- a/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs +++ b/src/Microsoft.Identity.Web/Policy/IAuthRequiredScopeOrAppPermissionMetadata.cs @@ -7,8 +7,8 @@ namespace Microsoft.Identity.Web { /// /// This is the metadata that describes required auth scopes or app permissions for a given endpoint - /// in a web API. It's the underlying data structure the requirement will look for - /// in order to validate scopes in the scope claims. + /// in a web API. It's the underlying data structure the requirement will look for + /// in order to validate scopes in the scope claims or app permissions in the roles claim. /// public interface IAuthRequiredScopeOrAppPermissionMetadata { @@ -19,8 +19,8 @@ public interface IAuthRequiredScopeOrAppPermissionMetadata string[]? AcceptedAppPermission { get; } /// - /// Fully qualified name of the configuration key containing the required scopes - /// or app permissions (separated by spaces). + /// Fully qualified name of the configuration key containing the required + /// app permissions (separated by spaces). /// string? RequiredAppPermissionsConfigurationKey { get; } diff --git a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs index e00798c7d..3e5f63a16 100644 --- a/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/PolicyBuilderExtensions.cs @@ -64,32 +64,8 @@ public static AuthorizationPolicyBuilder RequireScope( /// that the current user has the specified claim and that the claim value must be one of the allowed values. /// /// Used for building policies during application startup. - /// Values the claim must process one or more of for evaluation to succeed. - /// Values the claim must process one or more of for evaluation to succeed. - /// A reference to this instance after the operation has completed. - public static AuthorizationPolicyBuilder RequireScopeOrAppPermission( - this AuthorizationPolicyBuilder authorizationPolicyBuilder, - string[] allowedScopeValues, - string[] allowedAppPermissionValues) - { - if (authorizationPolicyBuilder == null) - { - throw new ArgumentNullException(nameof(authorizationPolicyBuilder)); - } - - return RequireScopeOrAppPermission( - authorizationPolicyBuilder, - (IEnumerable)allowedScopeValues, - (IEnumerable)allowedAppPermissionValues); - } - - /// - /// Adds a 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. - /// - /// Used for building policies during application startup. - /// Values the claim must process one or more of for evaluation to succeed. - /// Values the claim must process one or more of for evaluation to succeed. + /// scopes (the value of scope or scp) accepted by this app. + /// App permission (in role claim) that this app accepts. /// A reference to this instance after the operation has completed. public static AuthorizationPolicyBuilder RequireScopeOrAppPermission( this AuthorizationPolicyBuilder authorizationPolicyBuilder, diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs index 5db3af692..384581e81 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionAttribute.cs @@ -31,16 +31,11 @@ public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredSco /// a property named "Scopes" contains the required scopes, the attribute on the /// controller/page/action to protect should be set to the following: /// - /// [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + /// [RequiredScopeOrAppPermission(RequiredScopesConfigurationKey="AzureAd:Scopes")] /// /// public string? RequiredScopesConfigurationKey { get; set; } - /// - /// Unused: Compatibility of interface with the Authorization Filter. - /// - public bool IsReusable { get; set; } - /// /// App permissions accepted by this web API. /// App permissions appear in the roles claim of the token. @@ -77,7 +72,7 @@ public class RequiredScopeOrAppPermissionAttribute : Attribute, IAuthRequiredSco /// Add the following attribute on the controller/page/action to protect: /// /// - /// [RequiredScopeOrAppPermissionAttribute(new string[] { "access_as_user" }, new string[] { "access_as_app" })] + /// [RequiredScopeOrAppPermission(new [] { "access_as_user" }, new [] { "access_as_app" })] /// /// /// and diff --git a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs index c2260f3d9..118ef011b 100644 --- a/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs +++ b/src/Microsoft.Identity.Web/Policy/RequiredScopeOrAppPermissionExtensions.cs @@ -41,7 +41,7 @@ public static IServiceCollection AddRequiredScopeOrAppPermissionAuthorization(th /// Scope. /// App permission. /// Builder. - public static TBuilder RequireScope(this TBuilder endpointConventionBuilder, string[] scope, string[] appPermission) + public static TBuilder RequireScopeOrAppPermission(this TBuilder endpointConventionBuilder, string[] scope, string[] appPermission) where TBuilder : IEndpointConventionBuilder { return endpointConventionBuilder.WithMetadata(new RequiredScopeOrAppPermissionMetadata(scope, appPermission));