1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-29 23:34:53 -05:00
This commit is contained in:
Brandon 2025-03-31 13:56:58 -04:00
parent e7abb07d19
commit e9222e7fce
No known key found for this signature in database
GPG Key ID: A0E0EF0B207BA40D
8 changed files with 155 additions and 2 deletions

View File

@ -6,6 +6,7 @@ using Bit.Api.Vault.AuthorizationHandlers.Collections;
using Bit.Core;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v1;
@ -63,6 +64,7 @@ public class OrganizationUsersController : Controller
private readonly IPricingClient _pricingClient;
private readonly IConfirmOrganizationUserCommand _confirmOrganizationUserCommand;
private readonly IRestoreOrganizationUserCommand _restoreOrganizationUserCommand;
private readonly IInitPendingOrganizationCommand _initPendingOrganizationCommand;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
@ -313,7 +315,13 @@ public class OrganizationUsersController : Controller
throw new UnauthorizedAccessException();
}
await _organizationService.InitPendingOrganization(user.Id, orgId, organizationUserId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), OrganizationOperations.Update);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
await _initPendingOrganizationCommand.InitPendingOrganizationAsync(user.Id, orgId, organizationUserId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService);
await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id);
}

View File

@ -0,0 +1,47 @@
#nullable enable
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Authorization;
public class OrganizationAuthorizationHandler
: AuthorizationHandler<OrganizationOperationRequirement, OrganizationScope>
{
private readonly ICurrentContext _currentContext;
public OrganizationAuthorizationHandler(ICurrentContext currentContext)
{
_currentContext = currentContext;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
OrganizationOperationRequirement requirement, OrganizationScope organizationScope)
{
var authorized = false;
switch (requirement)
{
case not null when requirement.Name == nameof(OrganizationOperations.Update):
authorized = await CanUpdateAsync(organizationScope);
break;
}
if (authorized)
{
context.Succeed(requirement!);
}
}
private async Task<bool> CanUpdateAsync(Guid organizationId)
{
var organization = _currentContext.GetOrganization(organizationId);
if (organization != null)
{
return true;
}
// Allow provider users to update organization data if they are a provider for the target organization
return await _currentContext.ProviderUserForOrgAsync(organizationId);
}
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Authorization;
public class OrganizationOperationRequirement : OperationAuthorizationRequirement;
public static class OrganizationOperations
{
public static OrganizationOperationRequirement Update = new() { Name = nameof(Update) };
}

View File

@ -0,0 +1,76 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
{
private readonly IOrganizationService _organizationService;
private readonly ICollectionRepository _collectionRepository;
private readonly IOrganizationRepository _organizationRepository;
public InitPendingOrganizationCommand(
IOrganizationService organizationService,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository
)
{
_organizationService = organizationService;
_collectionRepository = collectionRepository;
_organizationRepository = organizationRepository;
}
public async Task InitPendingOrganizationAsync(Guid userId, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName)
{
await _organizationService.ValidateSignUpPoliciesAsync(userId);
var org = await _organizationRepository.GetByIdAsync(organizationId);
if (org.Enabled)
{
throw new BadRequestException("Organization is already enabled.");
}
if (org.Status != OrganizationStatusType.Pending)
{
throw new BadRequestException("Organization is not on a Pending status.");
}
if (!string.IsNullOrEmpty(org.PublicKey))
{
throw new BadRequestException("Organization already has a Public Key.");
}
if (!string.IsNullOrEmpty(org.PrivateKey))
{
throw new BadRequestException("Organization already has a Private Key.");
}
org.Enabled = true;
org.Status = OrganizationStatusType.Created;
org.PublicKey = publicKey;
org.PrivateKey = privateKey;
await _organizationService.UpdateAsync(org);
if (!string.IsNullOrWhiteSpace(collectionName))
{
// give the owner Can Manage access over the default collection
List<CollectionAccessSelection> defaultOwnerAccess =
[new CollectionAccessSelection { Id = organizationUserId, HidePasswords = false, ReadOnly = false, Manage = true }];
var defaultCollection = new Collection
{
Name = collectionName,
OrganizationId = org.Id
};
await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess);
}
}
}

View File

@ -0,0 +1,9 @@
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IInitPendingOrganizationCommand
{
/// <summary>
/// Accept an invitation to initialize and join an organization created via the Admin Portal
/// </summary>
Task InitPendingOrganizationAsync(Guid userId, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName);
}

View File

@ -63,4 +63,5 @@ public interface IOrganizationService
Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
OrganizationUserType? oldType, Permissions permissions);
Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType);
Task ValidateSignUpPoliciesAsync(Guid ownerId);
}

View File

@ -496,7 +496,7 @@ public class OrganizationService : IOrganizationService
return returnValue;
}
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
public async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)

View File

@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfa
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
@ -173,6 +174,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IAuthorizationHandler, OrganizationUserUserMiniDetailsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserUserDetailsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationAuthorizationHandler>();
services.AddScoped<IHasConfirmedOwnersExceptQuery, HasConfirmedOwnersExceptQuery>();
}