-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[PM-19332] Create InitPendingOrganizationCommand #5584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
๏ปฟusing Bit.Core.Entities; | ||
using Bit.Core.Enums; | ||
using Bit.Core.Models.Commands; | ||
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( | ||
jrmccannon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
IOrganizationService organizationService, | ||
ICollectionRepository collectionRepository, | ||
IOrganizationRepository organizationRepository | ||
) | ||
{ | ||
_organizationService = organizationService; | ||
_collectionRepository = collectionRepository; | ||
_organizationRepository = organizationRepository; | ||
} | ||
|
||
public const string OrgEnabled = "Organization is already enabled."; | ||
public const string OrgNotPending = "Organization is not on a Pending status."; | ||
public const string OrgHasPublicKey = "Organization already has a Public Key."; | ||
public const string OrgHasPrivateKey = "Organization already has a Private Key."; | ||
|
||
public async Task<CommandResult> 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) | ||
{ | ||
return new CommandResult(OrgEnabled); | ||
} | ||
|
||
if (org.Status != OrganizationStatusType.Pending) | ||
{ | ||
return new CommandResult(OrgNotPending); | ||
} | ||
|
||
if (!string.IsNullOrEmpty(org.PublicKey)) | ||
{ | ||
return new CommandResult(OrgHasPublicKey); | ||
} | ||
|
||
if (!string.IsNullOrEmpty(org.PrivateKey)) | ||
{ | ||
return new CommandResult(OrgHasPrivateKey); | ||
} | ||
|
||
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); | ||
} | ||
|
||
return new CommandResult(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're going to return a CommandResult. Then I would suggest you return a This way if we want to return something later, we can simply add a property to the model and populate it. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
๏ปฟusing Bit.Core.Models.Commands; | ||
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<CommandResult> InitPendingOrganizationAsync(Guid userId, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,4 +63,5 @@ Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId | |
Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, | ||
OrganizationUserType? oldType, Permissions permissions); | ||
Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType); | ||
Task ValidateSignUpPoliciesAsync(Guid ownerId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is just a single policy check - I don't think it needs to be reused. I suggest copying it into the new command rather than exposing it as public here. One less tie back to OrganizationService. (It's also what we did in the cloud org signup command) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
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 @@ private static void AddOrganizationUserCommandsQueries(this IServiceCollection s | |
|
||
services.AddScoped<IAuthorizationHandler, OrganizationUserUserMiniDetailsAuthorizationHandler>(); | ||
services.AddScoped<IAuthorizationHandler, OrganizationUserUserDetailsAuthorizationHandler>(); | ||
services.AddScoped<IAuthorizationHandler, OrganizationAuthorizationHandler>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this goes here... Which method should contain this, |
||
services.AddScoped<IHasConfirmedOwnersExceptQuery, HasConfirmedOwnersExceptQuery>(); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fully sure which checks here would really be best