1
0
mirror of https://github.com/bitwarden/server synced 2026-01-06 18:43:36 +00: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:
Matt Gibson
2021-10-05 11:12:05 -05:00
committed by GitHub
parent 55fa4a5f63
commit bd297fb7a2
25 changed files with 3639 additions and 129 deletions

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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))
{

View File

@@ -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"),

View File

@@ -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);

View File

@@ -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"),
};
}
}

View File

@@ -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;