From d56fa1a766cc039a9ae12e1bb2152e4a057dfb51 Mon Sep 17 00:00:00 2001
From: BluMichele <106175315+BluMichele@users.noreply.github.com>
Date: Tue, 19 Jul 2022 17:58:21 +0200
Subject: [PATCH 1/2] Create a mix of ScopesRequiredHttpContextExtensions and
RolesRequiredHttpContextExtensions
---
...esOrScopesRequiredHttpContextExtensions.cs | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
diff --git a/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
new file mode 100644
index 000000000..79a875b21
--- /dev/null
+++ b/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
@@ -0,0 +1,94 @@
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Claims;
+using global::Microsoft.AspNetCore.Http;
+using global::Microsoft.Identity.Web;
+using Bludata.Blu.Web.Security.Entities;
+using Bludata.Blu.Web.Security.Interface;
+
+namespace Bludata.Blu.Web.Security.Utils;
+
+///
+/// Extension class providing the extension methods for that
+/// can be used in web APIs to validate scopes and roles in controller actions.
+///
+public static class RolesOrScopesRequiredHttpContextExtensions
+{
+ ///
+ /// When applied to an , verifies that the user authenticated in the
+ /// web API has any of the accepted scopes or roles.
+ /// If there is no authenticated user, the response is a 401 (Unauthenticated).
+ /// If the authenticated user does not have any of these or , the
+ /// method updates the HTTP response providing a status code 403 (Forbidden)
+ /// and writes to the response body a message telling which scopes or roles are expected in the token.
+ ///
+ /// HttpContext (from the controller).
+ /// Scopes accepted by this web API.
+ /// Roles accepted by this web API.
+
+ public static void ValidateAppRolesOrScopes(this HttpContext context, string[] acceptedScopes, string[] acceptedRoles)
+ {
+ if (acceptedScopes?.Any() != true && acceptedRoles?.Any() != true)
+ {
+ throw new ArgumentException($"{nameof(acceptedScopes)} and {nameof(acceptedRoles)} are null or empty");
+ }
+ ArgumentNullException.ThrowIfNull(context);
+
+ IEnumerable userClaims;
+ ClaimsPrincipal user;
+
+ // Need to lock due to https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?#do-not-access-httpcontext-from-multiple-threads
+ lock (context)
+ {
+ user = context.User;
+ userClaims = user.Claims;
+ }
+
+ if (user == null || userClaims == null || !userClaims.Any())
+ {
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ }
+ //throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser);
+ throw new UnauthorizedAccessException("IDW10204: The user is unauthenticated. The HttpContext does not contain any claims. ");
+ }
+
+ var hasScopeOrRole = false;
+ if (acceptedScopes?.Any() == true)
+ {
+ var scpClaim = user.FindFirst(ClaimConstants.Scp)?.Value?.Split(' ');
+ var scopeClaim = user.FindFirst(ClaimConstants.Scope)?.Value?.Split(' ');
+
+ hasScopeOrRole = scpClaim?.Any(acceptedScopes.Contains) == true || scopeClaim?.Any(acceptedScopes.Contains) == true;
+ }
+ if (acceptedRoles?.Any() == true)
+ {
+ var rolesClaim = userClaims.Where(claims => claims.Type == ClaimConstants.Roles || claims.Type == ClaimConstants.Role)
+ .SelectMany(roles => roles.Value.Split(' '));
+ hasScopeOrRole = rolesClaim?.Any(acceptedRoles.Contains) == true;
+ }
+ if (hasScopeOrRole)
+ {
+ return;
+ }
+
+ var message = string.Format(CultureInfo.InvariantCulture,
+ $"The 'scope' or 'scp' claim does not contain scopes '{0}' nor " +
+ $"the 'roles' or 'role' claim does not contain roles '{1}'",
+ new string[] { string.Join(",", acceptedScopes), string.Join(",", acceptedRoles) });
+
+ lock (context)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ context.Response.WriteAsync(message);
+ context.Response.CompleteAsync();
+ }
+ throw new UnauthorizedAccessException(message);
+ }
+}
From b8210ff56b6a246b1d35a26632a6d0f77408b498 Mon Sep 17 00:00:00 2001
From: BluMichele <106175315+BluMichele@users.noreply.github.com>
Date: Thu, 21 Jul 2022 14:32:13 +0200
Subject: [PATCH 2/2] Added license and copyright, fixed namespace and removed
unused usings
---
.../RolesOrScopesRequiredHttpContextExtensions.cs | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
index 79a875b21..d55e5ceaa 100644
--- a/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
+++ b/src/Microsoft.Identity.Web/Resource/RolesOrScopesRequiredHttpContextExtensions.cs
@@ -1,17 +1,13 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
-using System;
-using System.Collections.Generic;
+using global::Microsoft.AspNetCore.Http;
+using global::Microsoft.Identity.Web;
using System.Globalization;
-using System.Linq;
using System.Net;
-using System.Net.Http;
using System.Security.Claims;
-using global::Microsoft.AspNetCore.Http;
-using global::Microsoft.Identity.Web;
-using Bludata.Blu.Web.Security.Entities;
-using Bludata.Blu.Web.Security.Interface;
-namespace Bludata.Blu.Web.Security.Utils;
+namespace Microsoft.Identity.Web.Resource;
///
/// Extension class providing the extension methods for that