mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
Implemented Custom role and permissions (#1057)
* Implemented Custom role and permissions * Converted permissions columns to a json blob * Code review fixes for Permissions * sql build fix * Update Permissions.cs * formatting * Update IOrganizationService.cs * reworked a conditional * built out tests for relevant organization service methods * removed unused usings * fixed a broken test and a bad empty string init * removed 'Attribute' from some attribute instances
This commit is contained in:
parent
99b95b5330
commit
63fcdc1418
@ -45,7 +45,7 @@ namespace Bit.Portal.Controllers
|
||||
}
|
||||
|
||||
if (!_enterprisePortalCurrentContext.SelectedOrganizationDetails.UsePolicies ||
|
||||
!_enterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
!_enterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
@ -65,7 +65,7 @@ namespace Bit.Portal.Controllers
|
||||
}
|
||||
|
||||
if (!_enterprisePortalCurrentContext.SelectedOrganizationDetails.UsePolicies ||
|
||||
!_enterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
!_enterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
@ -85,7 +85,7 @@ namespace Bit.Portal.Controllers
|
||||
}
|
||||
|
||||
if (!_enterprisePortalCurrentContext.SelectedOrganizationDetails.UsePolicies ||
|
||||
!_enterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
!_enterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ namespace Bit.Portal.Controllers
|
||||
}
|
||||
|
||||
if (!_enterprisePortalCurrentContext.SelectedOrganizationDetails.UseSso ||
|
||||
!_enterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
!_enterprisePortalCurrentContext.CanManageSsoForSelectedOrganization)
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
@ -63,7 +63,7 @@ namespace Bit.Portal.Controllers
|
||||
}
|
||||
|
||||
if (!_enterprisePortalCurrentContext.SelectedOrganizationDetails.UseSso ||
|
||||
!_enterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
!_enterprisePortalCurrentContext.CanManageSsoForSelectedOrganization)
|
||||
{
|
||||
return Redirect("~/");
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Bit.Core.Repositories;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Portal
|
||||
{
|
||||
@ -37,6 +38,14 @@ namespace Bit.Portal
|
||||
public bool OwnerForSelectedOrganization =>
|
||||
SelectedOrganizationDetails?.Type == Core.Enums.OrganizationUserType.Owner;
|
||||
|
||||
public bool CanManagePoliciesForSelectedOrganization =>
|
||||
AdminForSelectedOrganization || SelectedOrganizationDetailsPermissions.ManagePolicies == true;
|
||||
|
||||
public bool CanManageSsoForSelectedOrganization =>
|
||||
AdminForSelectedOrganization || SelectedOrganizationDetailsPermissions.ManageSso == true;
|
||||
|
||||
public Permissions SelectedOrganizationDetailsPermissions => CoreHelpers.LoadClassFromJsonData<Permissions>(SelectedOrganizationDetails?.Permissions);
|
||||
|
||||
public async override Task SetContextAsync(ClaimsPrincipal user)
|
||||
{
|
||||
var nameId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
@if (EnterprisePortalCurrentContext.SelectedOrganizationDetails.UseSso &&
|
||||
EnterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
EnterprisePortalCurrentContext.CanManageSsoForSelectedOrganization)
|
||||
{
|
||||
<div class="col">
|
||||
<a class="card p-5 border border-primary" asp-area="" asp-controller="Sso" asp-action="Index">
|
||||
@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
@if (EnterprisePortalCurrentContext.SelectedOrganizationDetails.UsePolicies &&
|
||||
EnterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
EnterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
<div class="col">
|
||||
<a class="card p-5 border border-primary" asp-area="" asp-controller="Policies" asp-action="Index">
|
||||
|
@ -32,7 +32,7 @@
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
@if (EnterprisePortalCurrentContext.SelectedOrganizationDetails.UseSso &&
|
||||
EnterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
EnterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" asp-area="" asp-controller="Sso" asp-action="Index">
|
||||
@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
@if (EnterprisePortalCurrentContext.SelectedOrganizationDetails.UsePolicies &&
|
||||
EnterprisePortalCurrentContext.AdminForSelectedOrganization)
|
||||
EnterprisePortalCurrentContext.CanManagePoliciesForSelectedOrganization)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" asp-area="" asp-controller="Policies" asp-action="Index">
|
||||
|
@ -60,7 +60,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -137,7 +137,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CipherMiniResponseModel> PostAdmin([FromBody]CipherCreateRequestModel model)
|
||||
{
|
||||
var cipher = model.Cipher.ToOrganizationCipher();
|
||||
if (!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
if (!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -181,7 +181,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 ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -200,7 +200,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var orgIdGuid = new Guid(organizationId);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManageAllCollections(orgIdGuid) && !_currentContext.AccessReports(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -243,7 +243,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
var orgId = new Guid(organizationId);
|
||||
if (!_currentContext.OrganizationAdmin(orgId))
|
||||
if (!_currentContext.AccessImportExport(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -308,7 +308,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 ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -338,7 +338,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 ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -371,7 +371,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!_currentContext.OrganizationAdmin(new Guid(model.OrganizationId)))
|
||||
!_currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -398,7 +398,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 ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -427,7 +427,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
|
||||
!_currentContext.OrganizationAdmin(new Guid(model.OrganizationId)))
|
||||
!_currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -456,7 +456,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 ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -550,7 +550,7 @@ namespace Bit.Api.Controllers
|
||||
else
|
||||
{
|
||||
var orgId = new Guid(organizationId);
|
||||
if (!_currentContext.OrganizationAdmin(orgId))
|
||||
if (!_currentContext.ManageAllCollections(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -593,7 +593,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid);
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -651,7 +651,7 @@ namespace Bit.Api.Controllers
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid);
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!_currentContext.OrganizationAdmin(cipher.OrganizationId.Value))
|
||||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -45,13 +45,13 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CollectionGroupDetailsResponseModel> GetDetails(string orgId, string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationManager(orgIdGuid))
|
||||
if (!ManageAnyCollections(orgIdGuid) && !_currentContext.ManageUsers(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var idGuid = new Guid(id);
|
||||
if (_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (_currentContext.ManageAllCollections(orgIdGuid))
|
||||
{
|
||||
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(idGuid);
|
||||
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgIdGuid)
|
||||
@ -76,7 +76,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<CollectionResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManageAllCollections(orgIdGuid) && !_currentContext.ManageUsers(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -108,14 +108,14 @@ namespace Bit.Api.Controllers
|
||||
public async Task<CollectionResponseModel> Post(string orgId, [FromBody]CollectionRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationManager(orgIdGuid))
|
||||
if (!ManageAnyCollections(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collection = model.ToCollection(orgIdGuid);
|
||||
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
|
||||
!_currentContext.OrganizationAdmin(orgIdGuid) ? _currentContext.UserId : null);
|
||||
!_currentContext.ManageAllCollections(orgIdGuid) ? _currentContext.UserId : null);
|
||||
return new CollectionResponseModel(collection);
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ namespace Bit.Api.Controllers
|
||||
|
||||
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
|
||||
{
|
||||
if (!_currentContext.OrganizationManager(orgId))
|
||||
if (!ManageAnyCollections(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -169,5 +169,10 @@ namespace Bit.Api.Controllers
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
private bool ManageAnyCollections(Guid orgId)
|
||||
{
|
||||
return _currentContext.ManageAssignedCollections(orgId) || _currentContext.ManageAllCollections(orgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ namespace Bit.Api.Controllers
|
||||
var canView = false;
|
||||
if (cipher.OrganizationId.HasValue)
|
||||
{
|
||||
canView = _currentContext.OrganizationAdmin(cipher.OrganizationId.Value);
|
||||
canView = _currentContext.AccessEventLogs(cipher.OrganizationId.Value);
|
||||
}
|
||||
else if (cipher.UserId.HasValue)
|
||||
{
|
||||
@ -86,7 +86,7 @@ namespace Bit.Api.Controllers
|
||||
[FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null, [FromQuery]string continuationToken = null)
|
||||
{
|
||||
var orgId = new Guid(id);
|
||||
if (!_currentContext.OrganizationAdmin(orgId))
|
||||
if (!_currentContext.AccessEventLogs(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -104,7 +104,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
||||
if (organizationUser == null || !organizationUser.UserId.HasValue ||
|
||||
!_currentContext.OrganizationAdmin(organizationUser.OrganizationId))
|
||||
!_currentContext.AccessEventLogs(organizationUser.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<GroupResponseModel> Get(string orgId, string id)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(new Guid(id));
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -46,7 +46,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<GroupDetailsResponseModel> GetDetails(string orgId, string id)
|
||||
{
|
||||
var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(new Guid(id));
|
||||
if (groupDetails?.Item1 == null || !_currentContext.OrganizationAdmin(groupDetails.Item1.OrganizationId))
|
||||
if (groupDetails?.Item1 == null || !_currentContext.ManageGroups(groupDetails.Item1.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -58,7 +58,11 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationManager(orgIdGuid))
|
||||
var canAccess = _currentContext.ManageGroups(orgIdGuid) ||
|
||||
_currentContext.ManageAssignedCollections(orgIdGuid) ||
|
||||
_currentContext.ManageAllCollections(orgIdGuid);
|
||||
|
||||
if (!canAccess)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -73,7 +77,7 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
var idGuid = new Guid(id);
|
||||
var group = await _groupRepository.GetByIdAsync(idGuid);
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -86,7 +90,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<GroupResponseModel> Post(string orgId, [FromBody]GroupRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManageGroups(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -101,7 +105,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<GroupResponseModel> Put(string orgId, string id, [FromBody]GroupRequestModel model)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(new Guid(id));
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -114,7 +118,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task PutUsers(string orgId, string id, [FromBody]IEnumerable<Guid> model)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(new Guid(id));
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -126,7 +130,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task Delete(string orgId, string id)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(new Guid(id));
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -139,7 +143,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task Delete(string orgId, string id, string orgUserId)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(new Guid(id));
|
||||
if (group == null || !_currentContext.OrganizationAdmin(group.OrganizationId))
|
||||
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -46,7 +47,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<OrganizationUserDetailsResponseModel> Get(string orgId, string id)
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByIdWithCollectionsAsync(new Guid(id));
|
||||
if (organizationUser == null || !_currentContext.OrganizationAdmin(organizationUser.Item1.OrganizationId))
|
||||
if (organizationUser == null || !_currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -58,7 +59,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationManager(orgGuidId))
|
||||
if (!_currentContext.ManageAssignedCollections(orgGuidId) && !_currentContext.ManageGroups(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -74,7 +75,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
||||
if (organizationUser == null || !_currentContext.OrganizationAdmin(organizationUser.OrganizationId))
|
||||
if (organizationUser == null || !_currentContext.ManageGroups(organizationUser.OrganizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -88,21 +89,20 @@ namespace Bit.Api.Controllers
|
||||
public async Task Invite(string orgId, [FromBody]OrganizationUserInviteRequestModel model)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var result = await _organizationService.InviteUserAsync(orgGuidId, userId.Value, model.Emails, model.Type.Value,
|
||||
model.AccessAll, null, model.Collections?.Select(c => c.ToSelectionReadOnly()));
|
||||
var result = await _organizationService.InviteUserAsync(orgGuidId, userId.Value, null, new OrganizationUserInvite(model));
|
||||
}
|
||||
|
||||
[HttpPost("{id}/reinvite")]
|
||||
public async Task Reinvite(string orgId, string id)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -127,7 +127,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task Confirm(string orgId, string id, [FromBody]OrganizationUserConfirmRequestModel model)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -142,7 +142,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task Put(string orgId, string id, [FromBody]OrganizationUserUpdateRequestModel model)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -163,7 +163,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task PutGroups(string orgId, string id, [FromBody]OrganizationUserUpdateGroupsRequestModel model)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -174,7 +174,8 @@ namespace Bit.Api.Controllers
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)));
|
||||
var loggedInUserId = _userService.GetProperUserId(User);
|
||||
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
@ -182,7 +183,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task Delete(string orgId, string id)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgGuidId))
|
||||
if (!_currentContext.ManageUsers(orgGuidId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<PolicyResponseModel> Get(string orgId, int type)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -69,7 +69,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationManager(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -108,7 +108,7 @@ namespace Bit.Api.Controllers
|
||||
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody]PolicyRequestModel model)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ namespace Bit.Api.Controllers
|
||||
var user = await CheckAsync(model.MasterPasswordHash, false);
|
||||
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -190,7 +190,7 @@ namespace Bit.Api.Controllers
|
||||
var user = await CheckAsync(model.MasterPasswordHash, false);
|
||||
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -331,7 +331,7 @@ namespace Bit.Api.Controllers
|
||||
var user = await CheckAsync(model.MasterPasswordHash, false);
|
||||
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!_currentContext.OrganizationAdmin(orgIdGuid))
|
||||
if (!_currentContext.ManagePolicies(orgIdGuid))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Models.Api.Public;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -116,8 +117,15 @@ namespace Bit.Api.Public.Controllers
|
||||
public async Task<IActionResult> Post([FromBody]MemberCreateRequestModel model)
|
||||
{
|
||||
var associations = model.Collections?.Select(c => c.ToSelectionReadOnly());
|
||||
var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null,
|
||||
model.Email, model.Type.Value, model.AccessAll.Value, model.ExternalId, associations);
|
||||
var invite = new OrganizationUserInvite
|
||||
{
|
||||
Emails = new List<string> { model.Email },
|
||||
Type = model.Type.Value,
|
||||
AccessAll = model.AccessAll.Value,
|
||||
Collections = associations
|
||||
};
|
||||
var userPromise = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null, model.ExternalId, invite);
|
||||
var user = userPromise.FirstOrDefault();
|
||||
var response = new MemberResponseModel(user, associations);
|
||||
return new JsonResult(response);
|
||||
}
|
||||
@ -178,7 +186,7 @@ namespace Bit.Api.Public.Controllers
|
||||
{
|
||||
return new NotFoundResult();
|
||||
}
|
||||
await _organizationService.UpdateUserGroupsAsync(existingUser, model.GroupIds);
|
||||
await _organizationService.UpdateUserGroupsAsync(existingUser, model.GroupIds, null);
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using Bit.Core.Repositories;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core
|
||||
{
|
||||
@ -148,6 +149,18 @@ namespace Bit.Core
|
||||
Type = OrganizationUserType.Manager
|
||||
}));
|
||||
}
|
||||
|
||||
if (claimsDict.ContainsKey("orgcustom"))
|
||||
{
|
||||
Organizations.AddRange(claimsDict["orgcustom"].Select(c =>
|
||||
new CurrentContentOrganization
|
||||
{
|
||||
Id = new Guid(c.Value),
|
||||
Type = OrganizationUserType.Custom,
|
||||
Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict)
|
||||
}));
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
@ -174,6 +187,61 @@ namespace Bit.Core
|
||||
return Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner) ?? false;
|
||||
}
|
||||
|
||||
public bool OrganizationCustom(Guid orgId)
|
||||
{
|
||||
return Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Custom) ?? false;
|
||||
}
|
||||
|
||||
public bool AccessBusinessPortal(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.AccessBusinessPortal) ?? false);
|
||||
}
|
||||
|
||||
public bool AccessEventLogs(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.AccessEventLogs) ?? false);
|
||||
}
|
||||
|
||||
public bool AccessImportExport(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.AccessImportExport) ?? false);
|
||||
}
|
||||
|
||||
public bool AccessReports(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.AccessReports) ?? false);
|
||||
}
|
||||
|
||||
public bool ManageAllCollections(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManageAllCollections) ?? false);
|
||||
}
|
||||
|
||||
public bool ManageAssignedCollections(Guid orgId)
|
||||
{
|
||||
return OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManageAssignedCollections) ?? false);
|
||||
}
|
||||
|
||||
public bool ManageGroups(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManageGroups) ?? false);
|
||||
}
|
||||
|
||||
public bool ManagePolicies(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManagePolicies) ?? false);
|
||||
}
|
||||
|
||||
public bool ManageSso(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManageSso) ?? false);
|
||||
}
|
||||
|
||||
public bool ManageUsers(Guid orgId)
|
||||
{
|
||||
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId && o.Permissions.ManageUsers) ?? false);
|
||||
}
|
||||
|
||||
public async Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync(
|
||||
IOrganizationUserRepository organizationUserRepository, Guid userId)
|
||||
{
|
||||
@ -196,6 +264,29 @@ namespace Bit.Core
|
||||
return claims[type].FirstOrDefault()?.Value;
|
||||
}
|
||||
|
||||
private Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary<string, IEnumerable<Claim>> claimsDict)
|
||||
{
|
||||
bool hasClaim(string claimKey)
|
||||
{
|
||||
return claimsDict.ContainsKey(claimKey) ?
|
||||
claimsDict[claimKey].Any(x => x.Value == organizationId) : false;
|
||||
}
|
||||
|
||||
return new Permissions
|
||||
{
|
||||
AccessBusinessPortal = hasClaim("accessbusinessportal"),
|
||||
AccessEventLogs = hasClaim("accesseventlogs"),
|
||||
AccessImportExport = hasClaim(""),
|
||||
AccessReports = hasClaim("accessreports"),
|
||||
ManageAllCollections = hasClaim(""),
|
||||
ManageAssignedCollections = hasClaim(""),
|
||||
ManageGroups = hasClaim(""),
|
||||
ManagePolicies = hasClaim(""),
|
||||
ManageSso = hasClaim("managesso"),
|
||||
ManageUsers = hasClaim("manageusers")
|
||||
};
|
||||
}
|
||||
|
||||
public class CurrentContentOrganization
|
||||
{
|
||||
public CurrentContentOrganization() { }
|
||||
@ -204,10 +295,12 @@ namespace Bit.Core
|
||||
{
|
||||
Id = orgUser.OrganizationId;
|
||||
Type = orgUser.Type;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions);
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public OrganizationUserType Type { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,6 @@
|
||||
Admin = 1,
|
||||
User = 2,
|
||||
Manager = 3,
|
||||
Custom = 4,
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ namespace Bit.Core.IdentityServer
|
||||
"orgowner",
|
||||
"orgadmin",
|
||||
"orgmanager",
|
||||
"orguser"
|
||||
"orguser",
|
||||
"orgcustom",
|
||||
}),
|
||||
new ApiResource("internal", new string[] { JwtClaimTypes.Subject }),
|
||||
new ApiResource("api.push", new string[] { JwtClaimTypes.Subject }),
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
@ -17,6 +18,8 @@ namespace Bit.Core.Models.Api
|
||||
[StringLength(50)]
|
||||
public string BillingEmail { get; set; }
|
||||
|
||||
public Permissions Permissions { get; set; }
|
||||
|
||||
public virtual Organization ToOrganization(Organization existingOrganization, GlobalSettings globalSettings)
|
||||
{
|
||||
if (!globalSettings.SelfHosted)
|
||||
|
@ -1,8 +1,9 @@
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
@ -13,6 +14,7 @@ namespace Bit.Core.Models.Api
|
||||
[Required]
|
||||
public Enums.OrganizationUserType? Type { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
@ -62,11 +64,16 @@ namespace Bit.Core.Models.Api
|
||||
[Required]
|
||||
public Enums.OrganizationUserType? Type { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
|
||||
public OrganizationUser ToOrganizationUser(OrganizationUser existingUser)
|
||||
{
|
||||
existingUser.Type = Type.Value;
|
||||
existingUser.Permissions = JsonSerializer.Serialize(Permissions, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
existingUser.AccessAll = AccessAll;
|
||||
return existingUser;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ using Bit.Core.Models.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
using Bit.Core.Utilities;
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class OrganizationUserResponseModel : ResponseModel
|
||||
@ -22,6 +22,7 @@ namespace Bit.Core.Models.Api
|
||||
Type = organizationUser.Type;
|
||||
Status = organizationUser.Status;
|
||||
AccessAll = organizationUser.AccessAll;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
|
||||
}
|
||||
|
||||
public OrganizationUserResponseModel(OrganizationUserUserDetails organizationUser, string obj = "organizationUser")
|
||||
@ -37,6 +38,7 @@ namespace Bit.Core.Models.Api
|
||||
Type = organizationUser.Type;
|
||||
Status = organizationUser.Status;
|
||||
AccessAll = organizationUser.AccessAll;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -44,6 +46,7 @@ namespace Bit.Core.Models.Api
|
||||
public OrganizationUserType Type { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationUserDetailsResponseModel : OrganizationUserResponseModel
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
using Bit.Core.Utilities;
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProfileOrganizationResponseModel : ResponseModel
|
||||
@ -29,6 +29,7 @@ namespace Bit.Core.Models.Api
|
||||
Enabled = organization.Enabled;
|
||||
SsoBound = !string.IsNullOrWhiteSpace(organization.SsoExternalId);
|
||||
Identifier = organization.Identifier;
|
||||
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organization.Permissions);
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -53,5 +54,6 @@ namespace Bit.Core.Models.Api
|
||||
public bool Enabled { get; set; }
|
||||
public bool SsoBound { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
}
|
||||
}
|
||||
|
27
src/Core/Models/Business/OrganizationUserInvite.cs
Normal file
27
src/Core/Models/Business/OrganizationUserInvite.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
public class OrganizationUserInvite
|
||||
{
|
||||
public IEnumerable<string> Emails { get; set; }
|
||||
public Enums.OrganizationUserType? Type { get; set; }
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnly> Collections { get; set; }
|
||||
|
||||
public OrganizationUserInvite() {}
|
||||
|
||||
public OrganizationUserInvite(OrganizationUserInviteRequestModel requestModel)
|
||||
{
|
||||
Emails = requestModel.Emails;
|
||||
Type = requestModel.Type.Value;
|
||||
AccessAll = requestModel.AccessAll;
|
||||
Collections = requestModel.Collections.Select(c => c.ToSelectionReadOnly());
|
||||
Permissions = requestModel.Permissions;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,5 +27,6 @@ namespace Bit.Core.Models.Data
|
||||
public bool Enabled { get; set; }
|
||||
public string SsoExternalId { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace Bit.Core.Models.Data
|
||||
public bool AccessAll { get; set; }
|
||||
public string ExternalId { get; set; }
|
||||
public string SsoExternalId { get; set; }
|
||||
public string Permissions { get; set; }
|
||||
|
||||
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
|
||||
{
|
||||
|
16
src/Core/Models/Data/Permissions.cs
Normal file
16
src/Core/Models/Data/Permissions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class Permissions
|
||||
{
|
||||
public bool AccessBusinessPortal { get; set; }
|
||||
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; }
|
||||
public bool ManageGroups { get; set; }
|
||||
public bool ManagePolicies { get; set; }
|
||||
public bool ManageSso { get; set; }
|
||||
public bool ManageUsers { get; set; }
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ namespace Bit.Core.Models.Table
|
||||
public string ExternalId { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
public string Permissions { get; set; }
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
@ -30,11 +30,7 @@ namespace Bit.Core.Services
|
||||
Task UpdateAsync(Organization organization, bool updateBilling = false);
|
||||
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
||||
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId,
|
||||
IEnumerable<string> emails, OrganizationUserType type, bool accessAll, string externalId,
|
||||
IEnumerable<SelectionReadOnly> collections);
|
||||
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string externalId, OrganizationUserInvite orgUserInvite);
|
||||
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
|
||||
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||
IUserService userService);
|
||||
@ -44,7 +40,7 @@ namespace Bit.Core.Services
|
||||
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<SelectionReadOnly> collections);
|
||||
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||
Task DeleteUserAsync(Guid organizationId, Guid userId);
|
||||
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds);
|
||||
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
|
||||
Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId);
|
||||
Task<OrganizationLicense> GenerateLicenseAsync(Organization organization, Guid installationId,
|
||||
int? version = null);
|
||||
|
@ -13,6 +13,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -972,45 +973,25 @@ namespace Bit.Core.Services
|
||||
await UpdateAsync(organization);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
||||
{
|
||||
var results = await InviteUserAsync(organizationId, invitingUserId, new List<string> { email },
|
||||
type, accessAll, externalId, collections);
|
||||
var result = results.FirstOrDefault();
|
||||
if (result == null)
|
||||
{
|
||||
throw new BadRequestException("This user has already been invited.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId,
|
||||
IEnumerable<string> emails, OrganizationUserType type, bool accessAll, string externalId,
|
||||
IEnumerable<SelectionReadOnly> collections)
|
||||
string externalId, OrganizationUserInvite invite)
|
||||
{
|
||||
var organization = await GetOrgById(organizationId);
|
||||
if (organization == null)
|
||||
if (organization == null || invite?.Emails == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (type == OrganizationUserType.Owner && invitingUserId.HasValue)
|
||||
if (invitingUserId.HasValue && invite.Type.HasValue)
|
||||
{
|
||||
var invitingUserOrgs = await _organizationUserRepository.GetManyByUserAsync(invitingUserId.Value);
|
||||
var anyOwners = invitingUserOrgs.Any(
|
||||
u => u.OrganizationId == organizationId && u.Type == OrganizationUserType.Owner);
|
||||
if (!anyOwners)
|
||||
{
|
||||
throw new BadRequestException("Only owners can invite new owners.");
|
||||
}
|
||||
await ValidateOrganizationUserUpdatePermissions(invitingUserId.Value, organizationId, invite.Type.Value, null);
|
||||
}
|
||||
|
||||
if (organization.Seats.HasValue)
|
||||
{
|
||||
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organizationId);
|
||||
var availableSeats = organization.Seats.Value - userCount;
|
||||
if (availableSeats < emails.Count())
|
||||
if (availableSeats < invite.Emails.Count())
|
||||
{
|
||||
throw new BadRequestException("You have reached the maximum number of users " +
|
||||
$"({organization.Seats.Value}) for this organization.");
|
||||
@ -1019,7 +1000,7 @@ namespace Bit.Core.Services
|
||||
|
||||
var orgUsers = new List<OrganizationUser>();
|
||||
var orgUserInvitedCount = 0;
|
||||
foreach (var email in emails)
|
||||
foreach (var email in invite.Emails)
|
||||
{
|
||||
// Make sure user is not already invited
|
||||
var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync(
|
||||
@ -1035,17 +1016,21 @@ namespace Bit.Core.Services
|
||||
UserId = null,
|
||||
Email = email.ToLowerInvariant(),
|
||||
Key = null,
|
||||
Type = type,
|
||||
Type = invite.Type.Value,
|
||||
Status = OrganizationUserStatusType.Invited,
|
||||
AccessAll = accessAll,
|
||||
AccessAll = invite.AccessAll,
|
||||
ExternalId = externalId,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Permissions = System.Text.Json.JsonSerializer.Serialize(invite.Permissions, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
}),
|
||||
};
|
||||
|
||||
if (!orgUser.AccessAll && collections.Any())
|
||||
if (!orgUser.AccessAll && invite.Collections.Any())
|
||||
{
|
||||
await _organizationUserRepository.CreateAsync(orgUser, collections);
|
||||
await _organizationUserRepository.CreateAsync(orgUser, invite.Collections);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1264,21 +1249,14 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("Invite the user first.");
|
||||
}
|
||||
|
||||
var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id);
|
||||
if (user.Equals(originalUser)) {
|
||||
throw new BadRequestException("Please make changes before saving.");
|
||||
}
|
||||
|
||||
if (savingUserId.HasValue)
|
||||
{
|
||||
var savingUserOrgs = await _organizationUserRepository.GetManyByUserAsync(savingUserId.Value);
|
||||
var savingUserIsOrgOwner = savingUserOrgs
|
||||
.Any(u => u.OrganizationId == user.OrganizationId && u.Type == OrganizationUserType.Owner);
|
||||
if (!savingUserIsOrgOwner)
|
||||
{
|
||||
var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id);
|
||||
var isOwner = originalUser.Type == OrganizationUserType.Owner;
|
||||
var nowOwner = user.Type == OrganizationUserType.Owner;
|
||||
if ((isOwner && !nowOwner) || (!isOwner && nowOwner))
|
||||
{
|
||||
throw new BadRequestException("Only an owner can change the user type of another owner.");
|
||||
}
|
||||
}
|
||||
await ValidateOrganizationUserUpdatePermissions(savingUserId.Value, user.OrganizationId, user.Type, originalUser.Type);
|
||||
}
|
||||
|
||||
var confirmedOwners = (await GetConfirmedOwnersAsync(user.OrganizationId)).ToList();
|
||||
@ -1367,8 +1345,12 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds)
|
||||
public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId)
|
||||
{
|
||||
if (loggedInUserId.HasValue)
|
||||
{
|
||||
await ValidateOrganizationUserUpdatePermissions(loggedInUserId.Value, organizationUser.OrganizationId, organizationUser.Type, null);
|
||||
}
|
||||
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||
await _eventService.LogOrganizationUserEventAsync(organizationUser,
|
||||
EventType.OrganizationUser_UpdatedGroups);
|
||||
@ -1502,8 +1484,16 @@ namespace Bit.Core.Services
|
||||
|
||||
try
|
||||
{
|
||||
var newUser = await InviteUserAsync(organizationId, importingUserId, user.Email,
|
||||
OrganizationUserType.User, false, user.ExternalId, new List<SelectionReadOnly>());
|
||||
var invite = new OrganizationUserInvite
|
||||
{
|
||||
Emails = new List<string> { user.Email },
|
||||
Type = OrganizationUserType.User,
|
||||
AccessAll = false,
|
||||
Collections = new List<SelectionReadOnly>(),
|
||||
};
|
||||
var newUserPromise = await InviteUserAsync(organizationId, importingUserId, user.ExternalId, invite);
|
||||
var newUser = newUserPromise.FirstOrDefault();
|
||||
|
||||
existingExternalUsersIdDict.Add(newUser.ExternalId, newUser.Id);
|
||||
}
|
||||
catch (BadRequestException)
|
||||
@ -1673,5 +1663,59 @@ namespace Bit.Core.Services
|
||||
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateOrganizationUserUpdatePermissions(Guid loggedInUserId, Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType)
|
||||
{
|
||||
var loggedInUserOrgs = await _organizationUserRepository.GetManyByUserAsync(loggedInUserId);
|
||||
var loggedInAsOrgOwner = loggedInUserOrgs
|
||||
.Any(u => u.OrganizationId == organizationId && u.Type == OrganizationUserType.Owner);
|
||||
if (loggedInAsOrgOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isOwner = oldType == OrganizationUserType.Owner;
|
||||
var nowOwner = newType == OrganizationUserType.Owner;
|
||||
var ownerUserConfigurationAttempt = (isOwner && nowOwner) || !(isOwner.Equals(nowOwner));
|
||||
if (ownerUserConfigurationAttempt)
|
||||
{
|
||||
throw new BadRequestException("Only an Owner can configure another Owner's account.");
|
||||
}
|
||||
|
||||
var loggedInAsOrgAdmin = loggedInUserOrgs.Any(u => u.OrganizationId == organizationId && u.Type == OrganizationUserType.Admin);
|
||||
if (loggedInAsOrgAdmin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isCustom = oldType == OrganizationUserType.Custom;
|
||||
var nowCustom = newType == OrganizationUserType.Custom;
|
||||
var customUserConfigurationAttempt = (isCustom && nowCustom) || !(isCustom.Equals(nowCustom));
|
||||
if (customUserConfigurationAttempt)
|
||||
{
|
||||
throw new BadRequestException("Only Owners and Admins can configure Custom accounts.");
|
||||
}
|
||||
|
||||
var loggedInAsOrgCustom = loggedInUserOrgs.Any(u => u.OrganizationId == organizationId && u.Type == OrganizationUserType.Custom);
|
||||
if (!loggedInAsOrgCustom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var loggedInCustomOrgUser = loggedInUserOrgs.First(u => u.OrganizationId == organizationId && u.Type == OrganizationUserType.Custom);
|
||||
var loggedInUserPermissions = CoreHelpers.LoadClassFromJsonData<Permissions>(loggedInCustomOrgUser.Permissions);
|
||||
if (!loggedInUserPermissions.ManageUsers)
|
||||
{
|
||||
throw new BadRequestException("Your account does not have permission to manage users.");
|
||||
}
|
||||
|
||||
var isAdmin = oldType == OrganizationUserType.Admin;
|
||||
var nowAdmin = newType == OrganizationUserType.Admin;
|
||||
var adminUserConfigurationAttempt = (isAdmin && nowAdmin) || !(isAdmin.Equals(nowAdmin));
|
||||
if (adminUserConfigurationAttempt)
|
||||
{
|
||||
throw new BadRequestException("Custom users can not manage Admins or Owners.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ using Microsoft.Azure.Storage;
|
||||
using Microsoft.Azure.Storage.Blob;
|
||||
using Bit.Core.Models.Table;
|
||||
using IdentityModel;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
@ -730,6 +731,62 @@ namespace Bit.Core.Utilities
|
||||
claims.Add(new KeyValuePair<string, string>("orguser", org.Id.ToString()));
|
||||
}
|
||||
break;
|
||||
case Enums.OrganizationUserType.Custom:
|
||||
foreach (var org in group)
|
||||
{
|
||||
claims.Add(new KeyValuePair<string, string>("orgcustom", org.Id.ToString()));
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -737,5 +794,20 @@ namespace Bit.Core.Utilities
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
public static T LoadClassFromJsonData<T>(string jsonData) where T : new()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jsonData))
|
||||
{
|
||||
return new T();
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
return System.Text.Json.JsonSerializer.Deserialize<T>(jsonData, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,8 @@
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7)
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -26,7 +27,8 @@ BEGIN
|
||||
[AccessAll],
|
||||
[ExternalId],
|
||||
[CreationDate],
|
||||
[RevisionDate]
|
||||
[RevisionDate],
|
||||
[Permissions]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -40,6 +42,7 @@ BEGIN
|
||||
@AccessAll,
|
||||
@ExternalId,
|
||||
@CreationDate,
|
||||
@RevisionDate
|
||||
@RevisionDate,
|
||||
@Permissions
|
||||
)
|
||||
END
|
@ -10,12 +10,13 @@
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX),
|
||||
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate
|
||||
EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions
|
||||
|
||||
;WITH [AvailableCollectionsCTE] AS(
|
||||
SELECT
|
||||
|
@ -9,7 +9,8 @@
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7)
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -26,7 +27,8 @@ BEGIN
|
||||
[AccessAll] = @AccessAll,
|
||||
[ExternalId] = @ExternalId,
|
||||
[CreationDate] = @CreationDate,
|
||||
[RevisionDate] = @RevisionDate
|
||||
[RevisionDate] = @RevisionDate,
|
||||
[Permissions] = @Permissions
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
|
@ -10,13 +10,13 @@
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX),
|
||||
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate
|
||||
|
||||
EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions
|
||||
-- Update
|
||||
UPDATE
|
||||
[Target]
|
||||
|
@ -1,15 +1,16 @@
|
||||
CREATE TABLE [dbo].[OrganizationUser] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[Email] NVARCHAR (50) NULL,
|
||||
[Key] VARCHAR (MAX) NULL,
|
||||
[Status] TINYINT NOT NULL,
|
||||
[Type] TINYINT NOT NULL,
|
||||
[AccessAll] BIT NOT NULL,
|
||||
[ExternalId] NVARCHAR (300) NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
[RevisionDate] DATETIME2 (7) NOT NULL,
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[Email] NVARCHAR (50) NULL,
|
||||
[Key] VARCHAR (MAX) NULL,
|
||||
[Status] TINYINT NOT NULL,
|
||||
[Type] TINYINT NOT NULL,
|
||||
[AccessAll] BIT NOT NULL,
|
||||
[ExternalId] NVARCHAR (300) NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
[RevisionDate] DATETIME2 (7) NOT NULL,
|
||||
[Permissions] NVARCHAR (MAX) NULL,
|
||||
CONSTRAINT [PK_OrganizationUser] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_OrganizationUser_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_OrganizationUser_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
|
||||
|
@ -22,7 +22,8 @@ SELECT
|
||||
OU.[Key],
|
||||
OU.[Status],
|
||||
OU.[Type],
|
||||
SU.[ExternalId] SsoExternalId
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions]
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
|
@ -12,7 +12,8 @@ SELECT
|
||||
OU.[Type],
|
||||
OU.[AccessAll],
|
||||
OU.[ExternalId],
|
||||
SU.[ExternalId] SsoExternalId
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions]
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
LEFT JOIN
|
||||
|
@ -51,7 +51,7 @@ namespace Bit.Core.Test.AutoFixture.CipherFixtures
|
||||
{
|
||||
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
|
||||
{ new SutProviderCustomization(), new UserCipher { UserId = new Guid(userId) } }, values)
|
||||
{ }
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||
|
114
test/Core.Test/AutoFixture/OrganizationFixtures.cs
Normal file
114
test/Core.Test/AutoFixture/OrganizationFixtures.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.OrganizationFixtures
|
||||
{
|
||||
internal class PaidOrganization : ICustomization
|
||||
{
|
||||
public PlanType CheckedPlanType { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != Enums.PlanType.Free && !p.Disabled).Select(p => p.Type).ToList();
|
||||
var lowestActivePaidPlan = validUpgradePlans.First();
|
||||
CheckedPlanType = CheckedPlanType.Equals(Enums.PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
|
||||
validUpgradePlans.Remove(lowestActivePaidPlan);
|
||||
fixture.Customize<Organization>(composer => composer
|
||||
.With(o => o.PlanType, CheckedPlanType));
|
||||
fixture.Customize<OrganizationUpgrade>(composer => composer
|
||||
.With(ou => ou.Plan, validUpgradePlans.First()));
|
||||
}
|
||||
}
|
||||
|
||||
internal class FreeOrganizationUpgrade : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Organization>(composer => composer
|
||||
.With(o => o.PlanType, PlanType.Free));
|
||||
|
||||
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
|
||||
var validPlans = StaticStore.Plans.Where(p => !plansToIgnore.Contains(p.Type) && !p.Disabled).Select(p => p.Type).ToList();
|
||||
fixture.Customize<OrganizationUpgrade>(composer => composer
|
||||
.With(ou => ou.Plan, validPlans.Last()));
|
||||
fixture.Customize<Organization>(composer => composer
|
||||
.Without(o => o.GatewaySubscriptionId));
|
||||
}
|
||||
}
|
||||
internal class OrganizationInvite : ICustomization
|
||||
{
|
||||
public OrganizationUserType InviteeUserType { get; set; }
|
||||
public OrganizationUserType InvitorUserType { get; set; }
|
||||
public string PermissionsBlob { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var organizationId = new Guid();
|
||||
PermissionsBlob = PermissionsBlob ?? JsonSerializer.Serialize(new Permissions(), new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
fixture.Customize<Organization>(composer => composer
|
||||
.With(o => o.Id, organizationId));
|
||||
fixture.Customize<OrganizationUser>(composer => composer
|
||||
.With(ou => ou.OrganizationId, organizationId)
|
||||
.With(ou => ou.Type, InvitorUserType)
|
||||
.With(ou => ou.Permissions, PermissionsBlob));
|
||||
fixture.Customize<OrganizationUserInvite>(composer => composer
|
||||
.With(oi => oi.Type, InviteeUserType));
|
||||
}
|
||||
}
|
||||
|
||||
internal class PaidOrganizationAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public PaidOrganizationAutoDataAttribute(int planType = 0) : base(new SutProviderCustomization(),
|
||||
new PaidOrganization { CheckedPlanType = (PlanType)planType })
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlinePaidOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlinePaidOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(PaidOrganization) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class FreeOrganizationUpgradeAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public FreeOrganizationUpgradeAutoDataAttribute() : base(new SutProviderCustomization(), new FreeOrganizationUpgrade())
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineFreeOrganizationUpgradeAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineFreeOrganizationUpgradeAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(FreeOrganizationUpgrade) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class OrganizationInviteAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public OrganizationInviteAutoDataAttribute(int inviteeUserType = 0, int invitorUserType = 0, string permissionsBlob = null) : base(new SutProviderCustomization(),
|
||||
new OrganizationInvite
|
||||
{
|
||||
InviteeUserType = (OrganizationUserType)inviteeUserType,
|
||||
InvitorUserType = (OrganizationUserType)invitorUserType,
|
||||
PermissionsBlob = permissionsBlob,
|
||||
})
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineOrganizationInviteAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineOrganizationInviteAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(OrganizationInvite) }, values)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -3,11 +3,18 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
@ -138,5 +145,216 @@ namespace Bit.Core.Test.Services
|
||||
await orgUserRepo.Received(1).UpsertAsync(Arg.Any<OrganizationUser>());
|
||||
await orgUserRepo.Received(2).CreateAsync(Arg.Any<OrganizationUser>());
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task UpgradePlan_OrganizationIsNull_Throws(Guid organizationId, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(Task.FromResult<Organization>(null));
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organizationId, upgrade));
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task UpgradePlan_GatewayCustomIdIsNull_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
organization.GatewayCustomerId = string.Empty;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("no payment method", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task UpgradePlan_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
upgrade.Plan = organization.PlanType;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationAutoData]
|
||||
public async Task UpgradePlan_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("can only upgrade", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[FreeOrganizationUpgradeAutoData]
|
||||
public async Task UpgradePlan_Passes(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).ReplaceAsync(organization);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData]
|
||||
public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor,
|
||||
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
invite.Emails = null;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData(
|
||||
inviteeUserType: (int)OrganizationUserType.Owner,
|
||||
invitorUserType: (int)OrganizationUserType.Admin
|
||||
)]
|
||||
public async Task InviteUser_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite,
|
||||
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
organizationUserRepository.GetManyByUserAsync(invitor.Id).Returns(new List<OrganizationUser> { invitor });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
|
||||
Assert.Contains("only an owner", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData(
|
||||
inviteeUserType: (int)OrganizationUserType.Custom,
|
||||
invitorUserType: (int)OrganizationUserType.Admin
|
||||
)]
|
||||
public async Task InviteUser_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
|
||||
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
organizationUserRepository.GetManyByUserAsync(invitor.Id).Returns(new List<OrganizationUser> { invitor });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
|
||||
Assert.Contains("only owners and admins", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData(
|
||||
inviteeUserType: (int)OrganizationUserType.Manager,
|
||||
invitorUserType: (int)OrganizationUserType.Custom
|
||||
)]
|
||||
public async Task InviteUser_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite,
|
||||
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = false },
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List<OrganizationUser> { invitor });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
|
||||
Assert.Contains("account does not have permission", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData(
|
||||
inviteeUserType: (int)OrganizationUserType.Admin,
|
||||
invitorUserType: (int)OrganizationUserType.Custom
|
||||
)]
|
||||
public async Task InviteUser_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
|
||||
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List<OrganizationUser> { invitor });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
|
||||
Assert.Contains("can not manage admins", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteAutoData(
|
||||
inviteeUserType: (int)OrganizationUserType.User,
|
||||
invitorUserType: (int)OrganizationUserType.Custom
|
||||
)]
|
||||
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite,
|
||||
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var eventService = sutProvider.GetDependency<IEventService>();
|
||||
|
||||
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List<OrganizationUser> { invitor });
|
||||
|
||||
await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite);
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task SaveUser_NoUserId_Throws(OrganizationUser user, Guid? savingUserId,
|
||||
IEnumerable<SelectionReadOnly> collections, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
user.Id = default(Guid);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections));
|
||||
Assert.Contains("invite the user first", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task SaveUser_NoChangeToData_Throws(OrganizationUser user, Guid? savingUserId,
|
||||
IEnumerable<SelectionReadOnly> collections, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
organizationUserRepository.GetByIdAsync(user.Id).Returns(user);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections));
|
||||
Assert.Contains("make changes before saving", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||
public async Task SaveUser_Passes(OrganizationUser oldUserData, OrganizationUser newUserData,
|
||||
IEnumerable<SelectionReadOnly> collections, OrganizationUser savingUser, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
|
||||
newUserData.Id = oldUserData.Id;
|
||||
newUserData.UserId = oldUserData.UserId;
|
||||
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId;
|
||||
savingUser.Type = OrganizationUserType.Owner;
|
||||
organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData);
|
||||
organizationUserRepository.GetManyByUserAsync(savingUser.UserId.Value).Returns(new List<OrganizationUser> { savingUser });
|
||||
|
||||
await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
331
util/Migrator/DbScripts/2020-12-14_00_Permissions.sql
Normal file
331
util/Migrator/DbScripts/2020-12-14_00_Permissions.sql
Normal file
@ -0,0 +1,331 @@
|
||||
IF COL_LENGTH('[dbo].[OrganizationUser]', 'Permissions') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE
|
||||
[dbo].[OrganizationUser]
|
||||
ADD
|
||||
[Permissions] NVARCHAR(MAX) NULL
|
||||
END
|
||||
GO
|
||||
|
||||
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserView')
|
||||
BEGIN
|
||||
DROP VIEW [dbo].[OrganizationUserView];
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE VIEW [dbo].[OrganizationUserView]
|
||||
AS
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
|
||||
GO
|
||||
|
||||
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView')
|
||||
BEGIN
|
||||
DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView];
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
OU.[UserId],
|
||||
OU.[OrganizationId],
|
||||
O.[Name],
|
||||
O.[Enabled],
|
||||
O.[UsePolicies],
|
||||
O.[UseSso],
|
||||
O.[UseGroups],
|
||||
O.[UseDirectory],
|
||||
O.[UseEvents],
|
||||
O.[UseTotp],
|
||||
O.[Use2fa],
|
||||
O.[UseApi],
|
||||
O.[SelfHost],
|
||||
O.[UsersGetPremium],
|
||||
O.[Seats],
|
||||
O.[MaxCollections],
|
||||
O.[MaxStorageGb],
|
||||
O.[Identifier],
|
||||
OU.[Key],
|
||||
OU.[Status],
|
||||
OU.[Type],
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions]
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
|
||||
GO
|
||||
|
||||
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserUserDetailsView')
|
||||
BEGIN
|
||||
DROP VIEW [dbo].[OrganizationUserUserDetailsView];
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE VIEW [dbo].[OrganizationUserUserDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
OU.[Id],
|
||||
OU.[UserId],
|
||||
OU.[OrganizationId],
|
||||
U.[Name],
|
||||
ISNULL(U.[Email], OU.[Email]) Email,
|
||||
U.[TwoFactorProviders],
|
||||
U.[Premium],
|
||||
OU.[Status],
|
||||
OU.[Type],
|
||||
OU.[AccessAll],
|
||||
OU.[ExternalId],
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions]
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
LEFT JOIN
|
||||
[dbo].[User] U ON U.[Id] = OU.[UserId]
|
||||
LEFT JOIN
|
||||
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_Create]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_Create]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_Create]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Email NVARCHAR(50),
|
||||
@Key VARCHAR(MAX),
|
||||
@Status TINYINT,
|
||||
@Type TINYINT,
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
INSERT INTO [dbo].[OrganizationUser]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[UserId],
|
||||
[Email],
|
||||
[Key],
|
||||
[Status],
|
||||
[Type],
|
||||
[AccessAll],
|
||||
[ExternalId],
|
||||
[CreationDate],
|
||||
[RevisionDate],
|
||||
[Permissions]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@Id,
|
||||
@OrganizationId,
|
||||
@UserId,
|
||||
@Email,
|
||||
@Key,
|
||||
@Status,
|
||||
@Type,
|
||||
@AccessAll,
|
||||
@ExternalId,
|
||||
@CreationDate,
|
||||
@RevisionDate,
|
||||
@Permissions
|
||||
)
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_CreateWithCollections]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_CreateWithCollections]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Email NVARCHAR(50),
|
||||
@Key VARCHAR(MAX),
|
||||
@Status TINYINT,
|
||||
@Type TINYINT,
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX),
|
||||
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions
|
||||
|
||||
;WITH [AvailableCollectionsCTE] AS(
|
||||
SELECT
|
||||
[Id]
|
||||
FROM
|
||||
[dbo].[Collection]
|
||||
WHERE
|
||||
[OrganizationId] = @OrganizationId
|
||||
)
|
||||
INSERT INTO [dbo].[CollectionUser]
|
||||
(
|
||||
[CollectionId],
|
||||
[OrganizationUserId],
|
||||
[ReadOnly],
|
||||
[HidePasswords]
|
||||
)
|
||||
SELECT
|
||||
[Id],
|
||||
@Id,
|
||||
[ReadOnly],
|
||||
[HidePasswords]
|
||||
FROM
|
||||
@Collections
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE])
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_Update]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_Update]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_Update]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Email NVARCHAR(50),
|
||||
@Key VARCHAR(MAX),
|
||||
@Status TINYINT,
|
||||
@Type TINYINT,
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationUser]
|
||||
SET
|
||||
[OrganizationId] = @OrganizationId,
|
||||
[UserId] = @UserId,
|
||||
[Email] = @Email,
|
||||
[Key] = @Key,
|
||||
[Status] = @Status,
|
||||
[Type] = @Type,
|
||||
[AccessAll] = @AccessAll,
|
||||
[ExternalId] = @ExternalId,
|
||||
[CreationDate] = @CreationDate,
|
||||
[RevisionDate] = @RevisionDate,
|
||||
[Permissions] = @Permissions
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_UpdateWithCollections]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@Email NVARCHAR(50),
|
||||
@Key VARCHAR(MAX),
|
||||
@Status TINYINT,
|
||||
@Type TINYINT,
|
||||
@AccessAll BIT,
|
||||
@ExternalId NVARCHAR(300),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@Permissions NVARCHAR(MAX),
|
||||
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions
|
||||
|
||||
|
||||
-- Update
|
||||
UPDATE
|
||||
[Target]
|
||||
SET
|
||||
[Target].[ReadOnly] = [Source].[ReadOnly],
|
||||
[Target].[HidePasswords] = [Source].[HidePasswords]
|
||||
FROM
|
||||
[dbo].[CollectionUser] AS [Target]
|
||||
INNER JOIN
|
||||
@Collections AS [Source] ON [Source].[Id] = [Target].[CollectionId]
|
||||
WHERE
|
||||
[Target].[OrganizationUserId] = @Id
|
||||
AND (
|
||||
[Target].[ReadOnly] != [Source].[ReadOnly]
|
||||
OR [Target].[HidePasswords] != [Source].[HidePasswords]
|
||||
)
|
||||
|
||||
-- Insert
|
||||
INSERT INTO
|
||||
[dbo].[CollectionUser]
|
||||
SELECT
|
||||
[Source].[Id],
|
||||
@Id,
|
||||
[Source].[ReadOnly],
|
||||
[Source].[HidePasswords]
|
||||
FROM
|
||||
@Collections AS [Source]
|
||||
INNER JOIN
|
||||
[dbo].[Collection] C ON C.[Id] = [Source].[Id] AND C.[OrganizationId] = @OrganizationId
|
||||
WHERE
|
||||
NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
[dbo].[CollectionUser]
|
||||
WHERE
|
||||
[CollectionId] = [Source].[Id]
|
||||
AND [OrganizationUserId] = @Id
|
||||
)
|
||||
|
||||
-- Delete
|
||||
DELETE
|
||||
CU
|
||||
FROM
|
||||
[dbo].[CollectionUser] CU
|
||||
WHERE
|
||||
CU.[OrganizationUserId] = @Id
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
@Collections
|
||||
WHERE
|
||||
[Id] = CU.[CollectionId]
|
||||
)
|
||||
END
|
||||
GO
|
Loading…
Reference in New Issue
Block a user