mirror of
https://github.com/bitwarden/server.git
synced 2025-02-08 00:31:27 +01:00
SqlServer split manage collection permission (#1594)
* SqlServer split manage collection permission * Clarify names * Test claims generation * Test permission serialization * Simplify claims building * Use new collections permissions * Throw on use of deprecated permissions * Lower case all claims * Remove todos * Clean nonexistent project from test solution * JsonIgnore for both system and newtonsoft json * Make migrations more robust to multiple runs * remove duplicate usings * Remove obsolete permissions * Test solutions separately to detect failures * Handle dos line endings * Fix collections create/update permissions * Change restore cipher to edit permissions * Improve formatting * Simplify map * Refactor test
This commit is contained in:
parent
55fa4a5f63
commit
bd297fb7a2
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -68,10 +68,12 @@ jobs:
|
||||
run: msbuild bitwarden-server.sln /p:Configuration=Debug /verbosity:minimal
|
||||
shell: pwsh
|
||||
|
||||
- name: Test solution
|
||||
run: |
|
||||
dotnet test ./test --configuration Debug --no-build
|
||||
dotnet test ./bitwarden_license/test/CmmCore.Test --configuration Debug --no-build
|
||||
- name: Test OSS solution
|
||||
run: dotnet test ./test --configuration Debug --no-build
|
||||
shell: pwsh
|
||||
|
||||
- name: Test Bitwarden solution
|
||||
run: dotnet test ./bitwarden_license/test/CmmCore.Test --configuration Debug --no-build
|
||||
shell: pwsh
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.ViewAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -153,7 +153,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CipherMiniResponseModel> PostAdmin([FromBody]CipherCreateRequestModel model)
|
||||
{
|
||||
var cipher = model.Cipher.ToOrganizationCipher();
|
||||
if (!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
if (!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -197,7 +197,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -216,7 +216,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgIdGuid = new Guid(organizationId);
|
||||
if (!await _currentContext.ManageAllCollections(orgIdGuid) && !await _currentContext.AccessReports(orgIdGuid))
|
||||
if (!await _currentContext.ViewAllCollections(orgIdGuid) && !await _currentContext.AccessReports(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -330,7 +330,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -360,7 +360,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -393,7 +393,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!await _currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
|
||||
!await _currentContext.EditAnyCollection(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -420,7 +420,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -449,7 +449,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!await _currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
|
||||
!await _currentContext.EditAnyCollection(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -478,7 +478,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -572,7 +572,7 @@ namespace Bit.Api.Controllers
|
||||
else
|
||||
{
|
||||
var orgId = new Guid(organizationId);
|
||||
if (!await _currentContext.ManageAllCollections(orgId))
|
||||
if (!await _currentContext.EditAnyCollection(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -590,7 +590,7 @@ namespace Bit.Api.Controllers
|
||||
await _cipherRepository.GetByIdAsync(idGuid, userId);
|
||||
|
||||
if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -694,7 +694,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid);
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -760,7 +760,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid);
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ namespace Bit.Api.Controllers
|
||||
[HttpGet("{id}")]
|
||||
public async Task<CollectionResponseModel> Get(string orgId, string id)
|
||||
{
|
||||
if (!await CanViewCollectionAsync(orgId, id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
|
||||
return new CollectionResponseModel(collection);
|
||||
}
|
||||
@ -45,13 +50,13 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CollectionGroupDetailsResponseModel> GetDetails(string orgId, string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!await ManageAnyCollections(orgIdGuid) && !await _currentContext.ManageUsers(orgIdGuid))
|
||||
if (!await ViewAtLeastOneCollectionAsync(orgIdGuid) && !await _currentContext.ManageUsers(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var idGuid = new Guid(id);
|
||||
if (await _currentContext.ManageAllCollections(orgIdGuid))
|
||||
if (await _currentContext.ViewAllCollections(orgIdGuid))
|
||||
{
|
||||
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(idGuid);
|
||||
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgIdGuid)
|
||||
@ -76,7 +81,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<CollectionResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!await _currentContext.ManageAllCollections(orgIdGuid) && !await _currentContext.ManageUsers(orgIdGuid))
|
||||
if (!await _currentContext.ViewAllCollections(orgIdGuid) && !await _currentContext.ManageUsers(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -108,14 +113,16 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CollectionResponseModel> Post(string orgId, [FromBody]CollectionRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!await ManageAnyCollections(orgIdGuid))
|
||||
var collection = model.ToCollection(orgIdGuid);
|
||||
|
||||
if (!await CanCreateCollection(orgIdGuid, collection.Id) &&
|
||||
!await CanEditCollectionAsync(orgIdGuid, collection.Id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collection = model.ToCollection(orgIdGuid);
|
||||
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
|
||||
!await _currentContext.ManageAllCollections(orgIdGuid) ? _currentContext.UserId : null);
|
||||
!await _currentContext.ViewAllCollections(orgIdGuid) ? _currentContext.UserId : null);
|
||||
return new CollectionResponseModel(collection);
|
||||
}
|
||||
|
||||
@ -123,6 +130,11 @@ namespace Bit.Api.Controllers
|
||||
[HttpPost("{id}")]
|
||||
public async Task<CollectionResponseModel> Put(string orgId, string id, [FromBody]CollectionRequestModel model)
|
||||
{
|
||||
if (!await CanEditCollectionAsync(orgId, id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
|
||||
await _collectionService.SaveAsync(model.ToCollection(collection),
|
||||
model.Groups?.Select(g => g.ToSelectionReadOnly()));
|
||||
@ -140,6 +152,11 @@ namespace Bit.Api.Controllers
|
||||
[HttpPost("{id}/delete")]
|
||||
public async Task Delete(string orgId, string id)
|
||||
{
|
||||
if (!await CanDeleteCollectionAsync(orgId, id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
|
||||
await _collectionService.DeleteAsync(collection);
|
||||
}
|
||||
@ -154,14 +171,17 @@ namespace Bit.Api.Controllers
|
||||
|
||||
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
|
||||
{
|
||||
if (!await ManageAnyCollections(orgId))
|
||||
Collection collection = default;
|
||||
if (await _currentContext.ViewAllCollections(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
collection = await _collectionRepository.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
if (await _currentContext.ViewAssignedCollections(orgId))
|
||||
{
|
||||
collection = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
|
||||
}
|
||||
|
||||
var collection = await _currentContext.OrganizationAdmin(orgId) ?
|
||||
await _collectionRepository.GetByIdAsync(id) :
|
||||
await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
|
||||
if (collection == null || collection.OrganizationId != orgId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -170,9 +190,86 @@ namespace Bit.Api.Controllers
|
||||
return collection;
|
||||
}
|
||||
|
||||
private async Task<bool> ManageAnyCollections(Guid orgId)
|
||||
|
||||
public async Task<bool> CanCreateCollection(Guid orgId, Guid collectionId)
|
||||
{
|
||||
return await _currentContext.ManageAssignedCollections(orgId) || await _currentContext.ManageAllCollections(orgId);
|
||||
if (collectionId != default)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _currentContext.CreateNewCollections(orgId);
|
||||
}
|
||||
|
||||
private async Task<bool> CanEditCollectionAsync(string orgId, string collectionId) =>
|
||||
await CanEditCollectionAsync(new Guid(orgId), new Guid(collectionId));
|
||||
private async Task<bool> CanEditCollectionAsync(Guid orgId, Guid collectionId)
|
||||
{
|
||||
if (collectionId == default)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await _currentContext.EditAnyCollection(orgId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await _currentContext.EditAssignedCollections(orgId))
|
||||
{
|
||||
return null != _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> CanDeleteCollectionAsync(string orgId, string collectionId) =>
|
||||
await CanDeleteCollectionAsync(new Guid(orgId), new Guid(collectionId));
|
||||
private async Task<bool> CanDeleteCollectionAsync(Guid orgId, Guid collectionId)
|
||||
{
|
||||
if (collectionId == default)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await _currentContext.DeleteAnyCollection(orgId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await _currentContext.DeleteAssignedCollections(orgId))
|
||||
{
|
||||
return null != _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> CanViewCollectionAsync(string orgId, string collectionId) =>
|
||||
await CanViewCollectionAsync(new Guid(orgId), new Guid(collectionId));
|
||||
private async Task<bool> CanViewCollectionAsync(Guid orgId, Guid collectionId)
|
||||
{
|
||||
if (collectionId == default)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await _currentContext.ViewAllCollections(orgId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await _currentContext.ViewAssignedCollections(orgId))
|
||||
{
|
||||
return null != _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> ViewAtLeastOneCollectionAsync(Guid orgId)
|
||||
{
|
||||
return await _currentContext.ViewAllCollections(orgId) || await _currentContext.ViewAssignedCollections(orgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
|
||||
await _currentContext.ManageAssignedCollections(orgIdGuid) ||
|
||||
await _currentContext.ManageAllCollections(orgIdGuid) ||
|
||||
await _currentContext.ViewAssignedCollections(orgIdGuid) ||
|
||||
await _currentContext.ViewAllCollections(orgIdGuid) ||
|
||||
await _currentContext.ManageUsers(orgIdGuid);
|
||||
|
||||
if (!canAccess)
|
||||
|
@ -61,7 +61,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!await _currentContext.ManageAssignedCollections(orgGuidId) &&
|
||||
if (!await _currentContext.ViewAssignedCollections(orgGuidId) &&
|
||||
!await _currentContext.ManageGroups(orgGuidId) &&
|
||||
!await _currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
|
@ -142,7 +142,7 @@ namespace Bit.Core.Context
|
||||
Organizations = GetOrganizations(claimsDict, orgApi);
|
||||
|
||||
Providers = GetProviders(claimsDict);
|
||||
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ namespace Bit.Core.Context
|
||||
|
||||
return organizations;
|
||||
}
|
||||
|
||||
|
||||
private List<CurrentContentProvider> GetProviders(Dictionary<string, IEnumerable<Claim>> claimsDict)
|
||||
{
|
||||
var providers = new List<CurrentContentProvider>();
|
||||
@ -274,6 +274,7 @@ namespace Bit.Core.Context
|
||||
return Task.FromResult(Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Custom) ?? false);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> AccessBusinessPortal(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
@ -298,16 +299,44 @@ namespace Bit.Core.Context
|
||||
&& (o.Permissions?.AccessReports ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ManageAllCollections(Guid orgId)
|
||||
public async Task<bool> CreateNewCollections(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.ManageAllCollections ?? false)) ?? false);
|
||||
&& (o.Permissions?.CreateNewCollections ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ManageAssignedCollections(Guid orgId)
|
||||
public async Task<bool> EditAnyCollection(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.EditAnyCollection ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAnyCollection(Guid orgId)
|
||||
{
|
||||
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.DeleteAnyCollection ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ViewAllCollections(Guid orgId)
|
||||
{
|
||||
return await EditAnyCollection(orgId) || await DeleteAnyCollection(orgId);
|
||||
}
|
||||
|
||||
public async Task<bool> EditAssignedCollections(Guid orgId)
|
||||
{
|
||||
return await OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.ManageAssignedCollections ?? false)) ?? false);
|
||||
&& (o.Permissions?.EditAssignedCollections ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAssignedCollections(Guid orgId)
|
||||
{
|
||||
return await OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId
|
||||
&& (o.Permissions?.DeleteAssignedCollections ?? false)) ?? false);
|
||||
}
|
||||
|
||||
public async Task<bool> ViewAssignedCollections(Guid orgId)
|
||||
{
|
||||
return await EditAssignedCollections(orgId) || await DeleteAssignedCollections(orgId);
|
||||
}
|
||||
|
||||
public async Task<bool> ManageGroups(Guid orgId)
|
||||
@ -431,8 +460,11 @@ namespace Bit.Core.Context
|
||||
AccessEventLogs = hasClaim("accesseventlogs"),
|
||||
AccessImportExport = hasClaim("accessimportexport"),
|
||||
AccessReports = hasClaim("accessreports"),
|
||||
ManageAllCollections = hasClaim("manageallcollections"),
|
||||
ManageAssignedCollections = hasClaim("manageassignedcollections"),
|
||||
CreateNewCollections = hasClaim("createnewcollections"),
|
||||
EditAnyCollection = hasClaim("editanycollection"),
|
||||
DeleteAnyCollection = hasClaim("deleteanycollection"),
|
||||
EditAssignedCollections = hasClaim("editassignedcollections"),
|
||||
DeleteAssignedCollections = hasClaim("deleteassignedcollections"),
|
||||
ManageGroups = hasClaim("managegroups"),
|
||||
ManagePolicies = hasClaim("managepolicies"),
|
||||
ManageSso = hasClaim("managesso"),
|
||||
|
@ -40,8 +40,13 @@ namespace Bit.Core.Context
|
||||
Task<bool> AccessEventLogs(Guid orgId);
|
||||
Task<bool> AccessImportExport(Guid orgId);
|
||||
Task<bool> AccessReports(Guid orgId);
|
||||
Task<bool> ManageAllCollections(Guid orgId);
|
||||
Task<bool> ManageAssignedCollections(Guid orgId);
|
||||
Task<bool> CreateNewCollections(Guid orgId);
|
||||
Task<bool> EditAnyCollection(Guid orgId);
|
||||
Task<bool> DeleteAnyCollection(Guid orgId);
|
||||
Task<bool> ViewAllCollections(Guid orgId);
|
||||
Task<bool> EditAssignedCollections(Guid orgId);
|
||||
Task<bool> DeleteAssignedCollections(Guid orgId);
|
||||
Task<bool> ViewAssignedCollections(Guid orgId);
|
||||
Task<bool> ManageGroups(Guid orgId);
|
||||
Task<bool> ManagePolicies(Guid orgId);
|
||||
Task<bool> ManageSso(Guid orgId);
|
||||
|
@ -1,3 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class Permissions
|
||||
@ -6,12 +10,39 @@ namespace Bit.Core.Models.Data
|
||||
public bool AccessEventLogs { get; set; }
|
||||
public bool AccessImportExport { get; set; }
|
||||
public bool AccessReports { get; set; }
|
||||
public bool ManageAssignedCollections { get; set; }
|
||||
public bool ManageAllCollections { get; set; }
|
||||
[Obsolete("This permission exists for client backwards-compatibility. It should not be used to determine permissions in this repository", true)]
|
||||
public bool ManageAllCollections => CreateNewCollections && EditAnyCollection && DeleteAnyCollection;
|
||||
public bool CreateNewCollections { get; set; }
|
||||
public bool EditAnyCollection { get; set; }
|
||||
public bool DeleteAnyCollection { get; set; }
|
||||
[Obsolete("This permission exists for client backwards-compatibility. It should not be used to determine permissions in this repository", true)]
|
||||
public bool ManageAssignedCollections => EditAssignedCollections && DeleteAssignedCollections;
|
||||
public bool EditAssignedCollections { get; set; }
|
||||
public bool DeleteAssignedCollections { 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; }
|
||||
|
||||
[JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public List<(bool Permission, string ClaimName)> ClaimsMap => new()
|
||||
{
|
||||
(AccessBusinessPortal, "accessbusinessportal"),
|
||||
(AccessEventLogs, "accesseventlogs"),
|
||||
(AccessImportExport, "accessimportexport"),
|
||||
(AccessReports, "accessreports"),
|
||||
(CreateNewCollections, "createnewcollections"),
|
||||
(EditAnyCollection, "editanycollection"),
|
||||
(DeleteAnyCollection, "deleteanycollection"),
|
||||
(EditAssignedCollections, "editassignedcollections"),
|
||||
(DeleteAssignedCollections, "deleteassignedcollections"),
|
||||
(ManageGroups, "managegroups"),
|
||||
(ManagePolicies, "managepolicies"),
|
||||
(ManageSso, "managesso"),
|
||||
(ManageUsers, "manageusers"),
|
||||
(ManageResetPassword, "manageresetpassword"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -242,6 +242,17 @@ namespace Bit.Core.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetEmbeddedResourceContentsAsync(string file)
|
||||
{
|
||||
var assembly = Assembly.GetCallingAssembly();
|
||||
var resourceName = assembly.GetManifestResourceNames().Single(n => n.EndsWith(file));
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public async static Task<X509Certificate2> GetBlobCertificateAsync(CloudStorageAccount cloudStorageAccount,
|
||||
string container, string file, string password)
|
||||
{
|
||||
@ -827,60 +838,14 @@ namespace Bit.Core.Utilities
|
||||
foreach (var org in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("orgcustom", org.Id.ToString()));
|
||||
foreach (var (permission, claimName) in org.Permissions.ClaimsMap)
|
||||
{
|
||||
if (!permission)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (org.Permissions.AccessBusinessPortal)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("accessbusinessportal", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.AccessEventLogs)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("accesseventlogs", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.AccessImportExport)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("accessimportexport", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.AccessReports)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("accessreports", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageAllCollections)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("manageallcollections", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageAssignedCollections)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("manageassignedcollections", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageGroups)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("managegroups", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManagePolicies)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("managepolicies", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageSso)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("managesso", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageUsers)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("manageusers", org.Id.ToString()));
|
||||
}
|
||||
|
||||
if (org.Permissions.ManageResetPassword)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("manageresetpassword", org.Id.ToString()));
|
||||
claims.Add(new KeyValuePair<string, string>(claimName, org.Id.ToString()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -4,17 +4,11 @@ using AutoFixture;
|
||||
using TableModel = Bit.Core.Models.Table;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Models.EntityFramework;
|
||||
using Bit.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
using AutoFixture.Kernel;
|
||||
using System;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Repositories.EntityFramework;
|
||||
using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures;
|
||||
using System.Reflection;
|
||||
using AutoFixture.Xunit2;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.PolicyFixtures
|
||||
|
@ -2,8 +2,6 @@ using AutoFixture;
|
||||
using TableModel = Bit.Core.Models.Table;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.GlobalSettingsFixtures;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Models.EntityFramework;
|
||||
using Bit.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
@ -15,11 +13,11 @@ using Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.UserFixtures
|
||||
{
|
||||
internal class UserBuilder: ISpecimenBuilder
|
||||
internal class UserBuilder : ISpecimenBuilder
|
||||
{
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (context == null)
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
@ -49,19 +47,27 @@ namespace Bit.Core.Test.AutoFixture.UserFixtures
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfUser: ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||
fixture.Customizations.Add(new UserBuilder());
|
||||
fixture.Customizations.Add(new OrganizationBuilder());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<SsoUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||
}
|
||||
}
|
||||
internal class UserFixture : ICustomization
|
||||
{
|
||||
public virtual void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||
fixture.Customizations.Add(new UserBuilder());
|
||||
fixture.Customizations.Add(new OrganizationBuilder());
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfUser : UserFixture
|
||||
{
|
||||
public override void Customize(IFixture fixture)
|
||||
{
|
||||
base.Customize(fixture);
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<SsoUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfUserAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
|
@ -26,4 +26,10 @@
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Api\Api.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Utilities\data\embeddedResource.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Utilities\data\embeddedResource.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
56
test/Core.Test/Models/PermissionsTests.cs
Normal file
56
test/Core.Test/Models/PermissionsTests.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models
|
||||
{
|
||||
public class PermissionsTests
|
||||
{
|
||||
private static readonly string _exampleSerializedPermissions = string.Concat(
|
||||
"{",
|
||||
"\"accessBusinessPortal\": false,",
|
||||
"\"accessEventLogs\": false,",
|
||||
"\"accessImportExport\": false,",
|
||||
"\"accessReports\": false,",
|
||||
"\"manageAllCollections\": true,", // exists for backwards compatibility
|
||||
"\"createNewCollections\": true,",
|
||||
"\"editAnyCollection\": true,",
|
||||
"\"deleteAnyCollection\": true,",
|
||||
"\"manageAssignedCollections\": false,", // exists for backwards compatibility
|
||||
"\"editAssignedCollections\": false,",
|
||||
"\"deleteAssignedCollections\": false,",
|
||||
"\"manageGroups\": false,",
|
||||
"\"managePolicies\": false,",
|
||||
"\"manageSso\": false,",
|
||||
"\"manageUsers\": false,",
|
||||
"\"manageResetPassword\": false",
|
||||
"}");
|
||||
|
||||
[Fact]
|
||||
public void Serialization_Success()
|
||||
{
|
||||
// minify expected json
|
||||
var expected = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(_exampleSerializedPermissions));
|
||||
|
||||
DefaultContractResolver contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new CamelCaseNamingStrategy()
|
||||
};
|
||||
|
||||
var actual = JsonConvert.SerializeObject(
|
||||
CoreHelpers.LoadClassFromJsonData<Permissions>(_exampleSerializedPermissions), new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = contractResolver,
|
||||
});
|
||||
|
||||
Console.WriteLine(actual);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories.EntityFramework;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.CipherFixtures;
|
||||
using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers;
|
||||
@ -10,7 +8,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
using EfRepo = Bit.Core.Repositories.EntityFramework;
|
||||
using SqlRepo = Bit.Core.Repositories.SqlServer;
|
||||
using Bit.Core.Test.AutoFixture.CipherFixtures;
|
||||
using Bit.Core.Repositories.EntityFramework.Queries;
|
||||
using Bit.Core.Models.Data;
|
||||
using System;
|
||||
|
@ -3,7 +3,15 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
using MimeKit;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.UserFixtures;
|
||||
using IdentityModel;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Context;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Test.Utilities
|
||||
{
|
||||
@ -150,11 +158,11 @@ namespace Bit.Core.Test.Utilities
|
||||
[Fact]
|
||||
public void CloneObject_Success()
|
||||
{
|
||||
var orignial = new { Message = "Message" };
|
||||
var original = new { Message = "Message" };
|
||||
|
||||
var copy = CoreHelpers.CloneObject(orignial);
|
||||
var copy = CoreHelpers.CloneObject(original);
|
||||
|
||||
Assert.Equal(orignial.Message, copy.Message);
|
||||
Assert.Equal(original.Message, copy.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -232,5 +240,116 @@ namespace Bit.Core.Test.Utilities
|
||||
var actual = CoreHelpers.PunyEncode(text);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEmbeddedResourceContentsAsync_Success()
|
||||
{
|
||||
var fileContents = CoreHelpers.GetEmbeddedResourceContentsAsync("data.embeddedResource.txt");
|
||||
Assert.Equal("Contents of embeddedResource.txt\n", fileContents.Replace("\r\n", "\n"));
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(UserFixture))]
|
||||
public void BuildIdentityClaims_BaseClaims_Success(User user, bool isPremium)
|
||||
{
|
||||
var expected = new Dictionary<string, string>
|
||||
{
|
||||
{ "premium", isPremium ? "true" : "false" },
|
||||
{ JwtClaimTypes.Email, user.Email },
|
||||
{ JwtClaimTypes.EmailVerified, user.EmailVerified ? "true" : "false" },
|
||||
{ JwtClaimTypes.Name, user.Name },
|
||||
{ "sstamp", user.SecurityStamp },
|
||||
}.ToList();
|
||||
|
||||
var actual = CoreHelpers.BuildIdentityClaims(user, Array.Empty<CurrentContentOrganization>(),
|
||||
Array.Empty<CurrentContentProvider>(), isPremium);
|
||||
|
||||
foreach (var claim in expected)
|
||||
{
|
||||
Assert.Contains(claim, actual);
|
||||
}
|
||||
Assert.Equal(expected.Count, actual.Count);
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(UserFixture))]
|
||||
public void BuildIdentityClaims_NonCustomOrganizationUserType_Success(User user)
|
||||
{
|
||||
var fixture = new Fixture().WithAutoNSubstitutions();
|
||||
foreach (var organizationUserType in Enum.GetValues<OrganizationUserType>().Except(new[] { OrganizationUserType.Custom }))
|
||||
{
|
||||
var org = fixture.Create<CurrentContentOrganization>();
|
||||
org.Type = organizationUserType;
|
||||
|
||||
var expected = new KeyValuePair<string, string>($"org{organizationUserType.ToString().ToLower()}", org.Id.ToString());
|
||||
var actual = CoreHelpers.BuildIdentityClaims(user, new[] { org }, Array.Empty<CurrentContentProvider>(), false);
|
||||
|
||||
Assert.Contains(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(UserFixture))]
|
||||
public void BuildIdentityClaims_CustomOrganizationUserClaims_Success(User user, CurrentContentOrganization org)
|
||||
{
|
||||
var fixture = new Fixture().WithAutoNSubstitutions();
|
||||
org.Type = OrganizationUserType.Custom;
|
||||
|
||||
var actual = CoreHelpers.BuildIdentityClaims(user, new[] { org }, Array.Empty<CurrentContentProvider>(), false);
|
||||
foreach (var (permitted, claimName) in org.Permissions.ClaimsMap)
|
||||
{
|
||||
var claim = new KeyValuePair<string, string>(claimName, org.Id.ToString());
|
||||
if (permitted)
|
||||
{
|
||||
|
||||
Assert.Contains(claim, actual);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.DoesNotContain(claim, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(UserFixture))]
|
||||
public void BuildIdentityClaims_ProviderClaims_Success(User user)
|
||||
{
|
||||
var fixture = new Fixture().WithAutoNSubstitutions();
|
||||
var providers = new List<CurrentContentProvider>();
|
||||
foreach (var providerUserType in Enum.GetValues<ProviderUserType>())
|
||||
{
|
||||
var provider = fixture.Create<CurrentContentProvider>();
|
||||
provider.Type = providerUserType;
|
||||
providers.Add(provider);
|
||||
}
|
||||
|
||||
var claims = new List<KeyValuePair<string, string>>();
|
||||
|
||||
if (providers.Any())
|
||||
{
|
||||
foreach (var group in providers.GroupBy(o => o.Type))
|
||||
{
|
||||
switch (group.Key)
|
||||
{
|
||||
case ProviderUserType.ProviderAdmin:
|
||||
foreach (var provider in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("providerprovideradmin", provider.Id.ToString()));
|
||||
}
|
||||
break;
|
||||
case ProviderUserType.ServiceUser:
|
||||
foreach (var provider in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("providerserviceuser", provider.Id.ToString()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var actual = CoreHelpers.BuildIdentityClaims(user, Array.Empty<CurrentContentOrganization>(), providers, false);
|
||||
foreach (var claim in claims)
|
||||
{
|
||||
Assert.Contains(claim, actual);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
1
test/Core.Test/Utilities/data/embeddedResource.txt
Normal file
1
test/Core.Test/Utilities/data/embeddedResource.txt
Normal file
@ -0,0 +1 @@
|
||||
Contents of embeddedResource.txt
|
@ -0,0 +1,64 @@
|
||||
-- Split Manage Assigned Collections into edit and delete
|
||||
UPDATE [vault_dev].[dbo].[OrganizationUser]
|
||||
SET [Permissions] =
|
||||
JSON_MODIFY(
|
||||
JSON_MODIFY(
|
||||
[Permissions],
|
||||
'$.editAssignedCollections',
|
||||
CAST(ISNULL(
|
||||
ISNULL(
|
||||
JSON_VALUE([Permissions], '$.editAssignedCollections'),
|
||||
JSON_VALUE([Permissions], '$.manageAssignedCollections')
|
||||
),
|
||||
0) AS BIT)
|
||||
),
|
||||
'$.deleteAssignedCollections',
|
||||
CAST(ISNULL(
|
||||
ISNULL(
|
||||
JSON_VALUE([Permissions], '$.deleteAssignedCollections'),
|
||||
JSON_VALUE([Permissions], '$.manageAssignedCollections')),
|
||||
0) AS BIT)
|
||||
)
|
||||
WHERE [Permissions] IS NOT NULL
|
||||
AND ISJSON([Permissions]) > 0
|
||||
AND (
|
||||
JSON_VALUE([Permissions], '$.editAssignedCollections') IS NULL
|
||||
OR JSON_VALUE([Permissions], '$.deleteAssignedCollections') IS NULL
|
||||
)
|
||||
|
||||
-- Split Manage All Collections into create, edit, and delete
|
||||
UPDATE [vault_dev].[dbo].[OrganizationUser]
|
||||
SET [Permissions] =
|
||||
JSON_MODIFY(
|
||||
JSON_MODIFY(
|
||||
JSON_MODIFY(
|
||||
[Permissions],
|
||||
'$.createNewCollections',
|
||||
CAST(ISNULL(
|
||||
ISNULL(
|
||||
JSON_VALUE([Permissions], '$.createNewCollections'),
|
||||
JSON_VALUE([Permissions], '$.manageAllCollections')),
|
||||
0) AS BIT)
|
||||
),
|
||||
'$.editAnyCollection',
|
||||
CAST(ISNULL(
|
||||
ISNULL(
|
||||
JSON_VALUE([Permissions], '$.editAnyCollection'),
|
||||
JSON_VALUE([Permissions], '$.manageAllCollections')),
|
||||
0) AS BIT)
|
||||
),
|
||||
'$.deleteAnyCollection',
|
||||
CAST(ISNULL(
|
||||
ISNULL(
|
||||
JSON_VALUE([Permissions], '$.deleteAnyCollection'),
|
||||
JSON_VALUE([Permissions], '$.manageAllCollections')),
|
||||
0) AS BIT)
|
||||
)
|
||||
WHERE [Permissions] IS NOT NULL
|
||||
AND ISJSON([Permissions]) > 0
|
||||
AND (
|
||||
JSON_VALUE([Permissions], '$.createNewCollections') IS NULL
|
||||
OR JSON_VALUE([Permissions], '$.editAnyCollection') IS NULL
|
||||
OR JSON_VALUE([Permissions], '$.deleteAnyCollection') IS NULL
|
||||
)
|
||||
|
1486
util/MySqlMigrations/Migrations/20210921200227_SplitManageCollectionsPermissions.Designer.cs
generated
Normal file
1486
util/MySqlMigrations/Migrations/20210921200227_SplitManageCollectionsPermissions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Bit.MySqlMigrations.Migrations
|
||||
{
|
||||
public partial class SplitManageCollectionsPermissions : Migration
|
||||
{
|
||||
private const string _scriptLocation =
|
||||
"MySqlMigrations.Scripts.2021-09-21_00_SplitManageCollectionsPermission.sql";
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(CoreHelpers.GetEmbeddedResourceContentsAsync(_scriptLocation));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
throw new Exception("Irreversible migration");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,4 +16,10 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Scripts\2021-09-21_00_SplitManageCollectionsPermission.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Scripts\2021-09-21_00_SplitManageCollectionsPermission.sql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,56 @@
|
||||
-- Split Manage Assigned Collections into edit and delete
|
||||
UPDATE `bw-vault`.`OrganizationUser`
|
||||
SET `Permissions` =
|
||||
JSON_INSERT(
|
||||
`Permissions`,
|
||||
'$.editAssignedCollections',
|
||||
IFNULL(
|
||||
IFNULL(
|
||||
JSON_EXTRACT(`Permissions`,'$.editAssignedCollections'),
|
||||
JSON_EXTRACT(`Permissions`, '$.manageAssignedCollections')),
|
||||
false),
|
||||
'$.deleteAssignedCollections',
|
||||
IFNULL(
|
||||
IFNULL(
|
||||
JSON_EXTRACT(`Permissions`, '$.deleteAssignedCollections'),
|
||||
JSON_EXTRACT(`Permissions`, '$.manageAssignedCollections')),
|
||||
false)
|
||||
)
|
||||
WHERE `Permissions` IS NOT NULL
|
||||
AND JSON_VALID(`Permissions`) > 0
|
||||
AND (
|
||||
JSON_EXTRACT(`Permissions`, '$.editAssignedCollections') IS NULL
|
||||
OR JSON_EXTRACT(`Permissions`, '$.deleteAssignedCollections') IS NULL
|
||||
);
|
||||
|
||||
-- Split Manage All Collections into create, edit, and delete
|
||||
UPDATE `bw-vault`.`OrganizationUser`
|
||||
SET `Permissions` =
|
||||
JSON_INSERT(
|
||||
`Permissions`,
|
||||
'$.createNewCollections',
|
||||
IFNULL(
|
||||
IFNULL(
|
||||
JSON_EXTRACT(`Permissions`, '$.createNewColletions'),
|
||||
JSON_EXTRACT(`Permissions`, '$.manageAllCollections')),
|
||||
false),
|
||||
'$.editAnyCollection',
|
||||
IFNULL(
|
||||
IFNULL(
|
||||
JSON_EXTRACT(`Permissions`, '$.editAnyCollection'),
|
||||
JSON_EXTRACT(`Permissions`, '$.manageAllCollections')),
|
||||
false),
|
||||
'$.deleteAnyCollection',
|
||||
IFNULL(
|
||||
IFNULL(
|
||||
JSON_EXTRACT(`Permissions`, '$.deleteAnyCollection'),
|
||||
JSON_EXTRACT(`Permissions`, '$.manageAllCollections')),
|
||||
false)
|
||||
)
|
||||
WHERE `Permissions` IS NOT NULL
|
||||
AND JSON_VALID(`Permissions`) > 0
|
||||
AND (
|
||||
JSON_EXTRACT(`Permissions`, '$.createNewCollections') IS NULL
|
||||
OR JSON_EXTRACT(`Permissions`, '$.editAnyCollection') IS NULL
|
||||
OR JSON_EXTRACT(`Permissions`, '$.deleteAnyCollection') IS NULL
|
||||
);
|
1495
util/PostgresMigrations/Migrations/20210921163012_SplitManageCollectionsPermissions.Designer.cs
generated
Normal file
1495
util/PostgresMigrations/Migrations/20210921163012_SplitManageCollectionsPermissions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Bit.PostgresMigrations.Migrations
|
||||
{
|
||||
public partial class SplitManageCollectionsPermissions : Migration
|
||||
{
|
||||
private const string _scriptLocation =
|
||||
"PostgresMigration.Scripts.2021-09-21_00_SplitManageCollectionsPermission.psql";
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(CoreHelpers.GetEmbeddedResourceContentsAsync(_scriptLocation));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
throw new Exception("Irreversible migration");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,4 +16,10 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Scripts\2021-09-21_00_SplitManageCollectionsPermission.psql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Scripts\2021-09-21_00_SplitManageCollectionsPermission.psql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,42 @@
|
||||
CREATE OR REPLACE FUNCTION updatePermissionsJson(permissions jsonb) returns jsonb LANGUAGE plpgsql AS $$
|
||||
DECLARE manageAllCollections jsonb := COALESCE(jsonb_extract_path(permissions, 'manageAllCollections'), 'false');
|
||||
DECLARE manageAssignedCollections jsonb := COALESCE(jsonb_extract_path(permissions, 'manageAssignedCollections'), 'false');
|
||||
|
||||
DECLARE createNewCollections jsonb := COALESCE(jsonb_extract_path(permissions, 'createNewCollections'), manageAllCollections);
|
||||
DECLARE editAnyCollection jsonb := COALESCE(jsonb_extract_path(permissions, 'editAnyCollection'), manageAllCollections);
|
||||
DECLARE deleteAnyCollection jsonb := COALESCE(jsonb_extract_path(permissions, 'deleteAnyCollection'), manageAllCollections);
|
||||
|
||||
DECLARE editAssignedCollections jsonb := COALESCE(jsonb_extract_path(permissions, 'editAssignedCollections'), manageAssignedCollections);
|
||||
DECLARE deleteAssignedCollections jsonb := COALESCE(jsonb_extract_path(permissions, 'deleteAssignedCollections'), manageAssignedCollections);
|
||||
|
||||
BEGIN
|
||||
RETURN
|
||||
jsonb_set(
|
||||
jsonb_set(
|
||||
jsonb_set(
|
||||
jsonb_set(
|
||||
jsonb_set(
|
||||
permissions,
|
||||
'{createNewCollections}',
|
||||
createNewCollections
|
||||
),
|
||||
'{editAnyCollection}',
|
||||
editAnyCollection
|
||||
),
|
||||
'{deleteAnyCollection}',
|
||||
deleteAnyCollection
|
||||
),
|
||||
'{editAssignedCollections}',
|
||||
editAssignedCollections
|
||||
),
|
||||
'{deleteAssignedCollections}',
|
||||
deleteAssignedCollections
|
||||
);
|
||||
END
|
||||
$$;
|
||||
|
||||
UPDATE public."OrganizationUser"
|
||||
SET "Permissions" = updatePermissionsJson("Permissions"::jsonb)::text
|
||||
WHERE "Permissions" IS NOT NULL;
|
||||
|
||||
DROP FUNCTION updatePermissionsJson(jsonb);
|
Loading…
Reference in New Issue
Block a user