1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 08:32:50 -05:00

[AC-292] Public Api - allow configuration of custom permissions (#4022)

* Also refactor OrganizationService user invite methods
This commit is contained in:
Thomas Rittson
2024-05-31 09:23:31 +10:00
committed by GitHub
parent 0189952e1f
commit 357ac4f40a
23 changed files with 829 additions and 179 deletions

View File

@ -225,7 +225,7 @@ public class OrganizationUsersController : Controller
}
var userId = _userService.GetProperUserId(User);
await _organizationService.InviteUsersAsync(orgId, userId.Value,
await _organizationService.InviteUsersAsync(orgId, userId.Value, systemUser: null,
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
}

View File

@ -127,10 +127,11 @@ public class MembersController : Controller
public async Task<IActionResult> Post([FromBody] MemberCreateRequestModel model)
{
var flexibleCollectionsIsEnabled = await FlexibleCollectionsIsEnabledAsync(_currentContext.OrganizationId.Value);
var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection(flexibleCollectionsIsEnabled)).ToList();
var invite = model.ToOrganizationUserInvite(flexibleCollectionsIsEnabled);
var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null,
model.Email, model.Type.Value, model.AccessAll.Value, model.ExternalId, associations, model.Groups);
var response = new MemberResponseModel(user, associations, flexibleCollectionsIsEnabled);
systemUser: null, invite, model.ExternalId);
var response = new MemberResponseModel(user, invite.Collections, flexibleCollectionsIsEnabled);
return new JsonResult(response);
}

View File

@ -1,4 +1,6 @@
using System.ComponentModel.DataAnnotations;
#nullable enable
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@ -21,6 +23,11 @@ public abstract class MemberBaseModel
AccessAll = user.AccessAll;
ExternalId = user.ExternalId;
ResetPasswordEnrolled = user.ResetPasswordKey != null;
if (Type == OrganizationUserType.Custom)
{
Permissions = new PermissionsModel(user.GetPermissions());
}
}
public MemberBaseModel(OrganizationUserUserDetails user, bool flexibleCollectionsEnabled)
@ -34,6 +41,11 @@ public abstract class MemberBaseModel
AccessAll = user.AccessAll;
ExternalId = user.ExternalId;
ResetPasswordEnrolled = user.ResetPasswordKey != null;
if (Type == OrganizationUserType.Custom)
{
Permissions = new PermissionsModel(user.GetPermissions());
}
}
/// <summary>
@ -59,6 +71,11 @@ public abstract class MemberBaseModel
/// </summary>
[Required]
public bool ResetPasswordEnrolled { get; set; }
/// <summary>
/// The member's custom permissions if the member has a Custom role. If not supplied, all custom permissions will
/// default to false.
/// </summary>
public PermissionsModel? Permissions { get; set; }
// TODO: AC-2188 - Remove this method when the custom users with no other permissions than 'Edit/Delete Assigned Collections' are migrated
private OrganizationUserType GetFlexibleCollectionsUserType(OrganizationUserType type, Permissions permissions)

View File

@ -0,0 +1,67 @@
#nullable enable
using System.Text.Json.Serialization;
using Bit.Core.Models.Data;
namespace Bit.Api.AdminConsole.Public.Models;
/// <summary>
/// Represents a member's custom permissions if the member has a Custom role.
/// </summary>
public class PermissionsModel
{
[JsonConstructor]
public PermissionsModel() { }
public PermissionsModel(Permissions? data)
{
if (data is null)
{
return;
}
AccessEventLogs = data.AccessEventLogs;
AccessImportExport = data.AccessImportExport;
AccessReports = data.AccessReports;
CreateNewCollections = data.CreateNewCollections;
EditAnyCollection = data.EditAnyCollection;
DeleteAnyCollection = data.DeleteAnyCollection;
ManageGroups = data.ManageGroups;
ManagePolicies = data.ManagePolicies;
ManageSso = data.ManageSso;
ManageUsers = data.ManageUsers;
ManageResetPassword = data.ManageResetPassword;
ManageScim = data.ManageScim;
}
public bool AccessEventLogs { get; set; }
public bool AccessImportExport { get; set; }
public bool AccessReports { get; set; }
public bool CreateNewCollections { get; set; }
public bool EditAnyCollection { get; set; }
public bool DeleteAnyCollection { get; set; }
public bool ManageGroups { get; set; }
public bool ManagePolicies { get; set; }
public bool ManageSso { get; set; }
public bool ManageUsers { get; set; }
public bool ManageResetPassword { get; set; }
public bool ManageScim { get; set; }
public Permissions ToData()
{
return new Permissions
{
AccessEventLogs = AccessEventLogs,
AccessImportExport = AccessImportExport,
AccessReports = AccessReports,
CreateNewCollections = CreateNewCollections,
EditAnyCollection = EditAnyCollection,
DeleteAnyCollection = DeleteAnyCollection,
ManageGroups = ManageGroups,
ManagePolicies = ManagePolicies,
ManageSso = ManageSso,
ManageUsers = ManageUsers,
ManageResetPassword = ManageResetPassword,
ManageScim = ManageScim
};
}
}

View File

@ -1,5 +1,7 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
namespace Bit.Api.AdminConsole.Public.Models.Request;
@ -19,4 +21,24 @@ public class MemberCreateRequestModel : MemberUpdateRequestModel
{
throw new NotImplementedException();
}
public OrganizationUserInvite ToOrganizationUserInvite(bool flexibleCollectionsIsEnabled)
{
var invite = new OrganizationUserInvite
{
Emails = new[] { Email },
Type = Type.Value,
AccessAll = AccessAll.Value,
Collections = Collections?.Select(c => c.ToCollectionAccessSelection(flexibleCollectionsIsEnabled)).ToList(),
Groups = Groups
};
// Permissions property is optional for backwards compatibility with existing usage
if (Type is OrganizationUserType.Custom && Permissions is not null)
{
invite.Permissions = Permissions.ToData();
}
return invite;
}
}

View File

@ -1,8 +1,10 @@
using Bit.Core.Entities;
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Api.AdminConsole.Public.Models.Request;
public class MemberUpdateRequestModel : MemberBaseModel
public class MemberUpdateRequestModel : MemberBaseModel, IValidatableObject
{
/// <summary>
/// The associated collections that this member can access.
@ -19,6 +21,21 @@ public class MemberUpdateRequestModel : MemberBaseModel
existingUser.Type = Type.Value;
existingUser.AccessAll = AccessAll.Value;
existingUser.ExternalId = ExternalId;
// Permissions property is optional for backwards compatibility with existing usage
if (existingUser.Type is OrganizationUserType.Custom && Permissions is not null)
{
existingUser.SetPermissions(Permissions.ToData());
}
return existingUser;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Type is not OrganizationUserType.Custom && Permissions is not null)
{
yield return new ValidationResult("Only users with the Custom role may use custom permissions.");
}
}
}

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Bit.Api.Models.Public.Response;
using Bit.Core.Entities;
using Bit.Core.Enums;
@ -12,6 +13,9 @@ namespace Bit.Api.AdminConsole.Public.Models.Response;
/// </summary>
public class MemberResponseModel : MemberBaseModel, IResponseModel
{
[JsonConstructor]
public MemberResponseModel() { }
public MemberResponseModel(OrganizationUser user, IEnumerable<CollectionAccessSelection> collections,
bool flexibleCollectionsEnabled)
: base(user, flexibleCollectionsEnabled)