1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

First pass using policy based auth

This commit is contained in:
Thomas Rittson 2025-03-21 11:52:48 +10:00
parent 948d8f707d
commit 1c697544b8
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
4 changed files with 63 additions and 8 deletions

View File

@ -145,14 +145,15 @@ public class OrganizationUsersController : Controller
} }
[HttpGet("")] [HttpGet("")]
[Authorize(Policy = "owner")]
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(Guid orgId, bool includeGroups = false, bool includeCollections = false) public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(Guid orgId, bool includeGroups = false, bool includeCollections = false)
{ {
var authorized = (await _authorizationService.AuthorizeAsync( // var authorized = (await _authorizationService.AuthorizeAsync(
User, new OrganizationScope(orgId), OrganizationUserUserDetailsOperations.ReadAll)).Succeeded; // User, new OrganizationScope(orgId), OrganizationUserUserDetailsOperations.ReadAll)).Succeeded;
if (!authorized) // if (!authorized)
{ // {
throw new NotFoundException(); // throw new NotFoundException();
} // }
var organizationUsers = await _organizationUserUserDetailsQuery.GetOrganizationUserUserDetails( var organizationUsers = await _organizationUserUserDetailsQuery.GetOrganizationUserUserDetails(
new OrganizationUserUserDetailsQueryRequest new OrganizationUserUserDetailsQueryRequest

View File

@ -27,8 +27,10 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
using Bit.Api.Auth.Models.Request.WebAuthn; using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Core.AdminConsole.OrganizationFeatures;
using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Identity.TokenProviders; using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Enums;
using Bit.Core.Tools.ImportFeatures; using Bit.Core.Tools.ImportFeatures;
using Bit.Core.Tools.ReportFeatures; using Bit.Core.Tools.ReportFeatures;
@ -143,6 +145,18 @@ public class Startup
(c.Value.Contains(ApiScopes.Api) || c.Value.Contains(ApiScopes.ApiSecrets)) (c.Value.Contains(ApiScopes.Api) || c.Value.Contains(ApiScopes.ApiSecrets))
)); ));
}); });
// Simplest implementation: check for role
// Issues:
// - unable to specify custom permissions
// - multiple policies are treated as AND rather than OR
// - does not allow for more complex conditional logic - e.g. providers can affect whether owners can view billing
// Alternative: describe broad action/capability, e.g. ManageUsers, ManageGroups, ViewBilling, similar to CurrentContext today
// the handler is then implemented per domain to define who can do those things
config.AddPolicy("owner", policy
=> policy.AddRequirements(new RoleRequirement(OrganizationUserType.Owner)));
config.AddPolicy("admin", policy
=> policy.AddRequirements(new RoleRequirement(OrganizationUserType.Admin)));
}); });
services.AddScoped<AuthenticatorTokenProvider>(); services.AddScoped<AuthenticatorTokenProvider>();
@ -255,11 +269,12 @@ public class Startup
// Add authentication and authorization to the request pipeline. // Add authentication and authorization to the request pipeline.
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization();
// Add current context // Add current context - before authz
app.UseMiddleware<CurrentContextMiddleware>(); app.UseMiddleware<CurrentContextMiddleware>();
app.UseAuthorization();
// Add endpoints to the request pipeline. // Add endpoints to the request pipeline.
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {

View File

@ -1,5 +1,6 @@
using Bit.Api.Tools.Authorization; using Bit.Api.Tools.Authorization;
using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Core.AdminConsole.OrganizationFeatures;
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -105,5 +106,7 @@ public static class ServiceCollectionExtensions
services.AddScoped<IAuthorizationHandler, VaultExportAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, VaultExportAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, SecurityTaskAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, SecurityTaskAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, SecurityTaskOrganizationAuthorizationHandler>(); services.AddScoped<IAuthorizationHandler, SecurityTaskOrganizationAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, RoleAuthorizationHandler>();
} }
} }

View File

@ -0,0 +1,36 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Bit.Core.AdminConsole.OrganizationFeatures;
public record RoleRequirement(OrganizationUserType Role) : IAuthorizationRequirement;
public class RoleAuthorizationHandler(ICurrentContext currentContext, IHttpContextAccessor httpContextAccessor) : AuthorizationHandler<RoleRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (httpContextAccessor.HttpContext is null)
{
return Task.CompletedTask;
}
httpContextAccessor.HttpContext.GetRouteData().Values.TryGetValue("orgId", out var orgIdParam);
if (!Guid.TryParse(orgIdParam?.ToString(), out var orgId))
{
// No orgId supplied, unable to authorize
return Task.CompletedTask;
}
// This could be an extension method on ClaimsPrincipal
var orgClaims = currentContext.GetOrganization(orgId);
if (orgClaims?.Type == requirement.Role)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}