From 2384e0b7efca275854d0a389c4c0f789c6b93e6c Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:45:14 +1000 Subject: [PATCH] Add AuthorizeOrThrowAsync extension method (#4790) --- .../Controllers/GroupsController.cs | 2 +- .../OrganizationUsersController.cs | 2 +- src/Api/Controllers/CollectionsController.cs | 2 +- .../AuthorizationServiceExtensions.cs | 25 +++++++++++- .../AuthorizationServiceExtensionTests.cs | 38 +++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) rename src/{Api => Core}/Utilities/AuthorizationServiceExtensions.cs (58%) create mode 100644 test/Core.Test/Utilities/AuthorizationServiceExtensionTests.cs diff --git a/src/Api/AdminConsole/Controllers/GroupsController.cs b/src/Api/AdminConsole/Controllers/GroupsController.cs index b314155be..f3f1d343c 100644 --- a/src/Api/AdminConsole/Controllers/GroupsController.cs +++ b/src/Api/AdminConsole/Controllers/GroupsController.cs @@ -1,7 +1,6 @@ using Bit.Api.AdminConsole.Models.Request; using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Models.Response; -using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Api.Vault.AuthorizationHandlers.Groups; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; @@ -11,6 +10,7 @@ using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 89c61b8de..c9a414316 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -2,7 +2,6 @@ using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; -using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers; using Bit.Core; @@ -22,6 +21,7 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Utilities; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Microsoft.AspNetCore.Authorization; diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index 37b4fe266..e0f1c0d2c 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -1,6 +1,5 @@ using Bit.Api.Models.Request; using Bit.Api.Models.Response; -using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Core.Context; using Bit.Core.Entities; @@ -9,6 +8,7 @@ using Bit.Core.Models.Data; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Api/Utilities/AuthorizationServiceExtensions.cs b/src/Core/Utilities/AuthorizationServiceExtensions.cs similarity index 58% rename from src/Api/Utilities/AuthorizationServiceExtensions.cs rename to src/Core/Utilities/AuthorizationServiceExtensions.cs index 4f10162cb..030c4573c 100644 --- a/src/Api/Utilities/AuthorizationServiceExtensions.cs +++ b/src/Core/Utilities/AuthorizationServiceExtensions.cs @@ -1,7 +1,8 @@ using System.Security.Claims; +using Bit.Core.Exceptions; using Microsoft.AspNetCore.Authorization; -namespace Bit.Api.Utilities; +namespace Bit.Core.Utilities; public static class AuthorizationServiceExtensions { @@ -29,4 +30,26 @@ public static class AuthorizationServiceExtensions return service.AuthorizeAsync(user, resource: null, new[] { requirement }); } + + /// + /// Performs an authorization check and throws a if the + /// check fails or the resource is null. + /// + public static async Task AuthorizeOrThrowAsync(this IAuthorizationService service, + ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) + { + ArgumentNullException.ThrowIfNull(service); + ArgumentNullException.ThrowIfNull(requirement); + + if (resource == null) + { + throw new NotFoundException(); + } + + var authorizationResult = await service.AuthorizeAsync(user, resource, requirement); + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + } } diff --git a/test/Core.Test/Utilities/AuthorizationServiceExtensionTests.cs b/test/Core.Test/Utilities/AuthorizationServiceExtensionTests.cs new file mode 100644 index 000000000..c88027b85 --- /dev/null +++ b/test/Core.Test/Utilities/AuthorizationServiceExtensionTests.cs @@ -0,0 +1,38 @@ +using System.Security.Claims; +using Bit.Core.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using NSubstitute; +using Xunit; +using AuthorizationServiceExtensions = Bit.Core.Utilities.AuthorizationServiceExtensions; + +namespace Bit.Core.Test.Utilities; + +public class AuthorizationServiceExtensionTests +{ + [Fact] + async Task AuthorizeOrThrowAsync_ThrowsNotFoundException_IfResourceIsNull() + { + var authorizationService = Substitute.For(); + await Assert.ThrowsAsync(() => + AuthorizationServiceExtensions.AuthorizeOrThrowAsync(authorizationService, new ClaimsPrincipal(), + null, new OperationAuthorizationRequirement())); + } + + [Fact] + async Task AuthorizeOrThrowAsync_ThrowsNotFoundException_IfAuthorizationFails() + { + var authorizationService = Substitute.For(); + var claimsPrincipal = new ClaimsPrincipal(); + var requirement = new OperationAuthorizationRequirement(); + var resource = new object(); + + authorizationService + .AuthorizeAsync(claimsPrincipal, resource, Arg.Is>(r => + r.First() == requirement)) + .Returns(AuthorizationResult.Failed()); + + await Assert.ThrowsAsync(() => + AuthorizationServiceExtensions.AuthorizeOrThrowAsync(authorizationService, claimsPrincipal, resource, requirement)); + } +}