1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05: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:
Addison Beck
2021-01-12 11:02:39 -05:00
committed by GitHub
parent 99b95b5330
commit 63fcdc1418
39 changed files with 1116 additions and 149 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,5 +6,6 @@
Admin = 1,
User = 2,
Manager = 3,
Custom = 4,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}
}

View File

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

View File

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

View 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; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -41,4 +42,4 @@ BEGIN
@Collections
WHERE
[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE])
END
END

View File

@ -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,9 +27,10 @@ BEGIN
[AccessAll] = @AccessAll,
[ExternalId] = @ExternalId,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate
[RevisionDate] = @RevisionDate,
[Permissions] = @Permissions
WHERE
[Id] = @Id
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END

View File

@ -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]
@ -72,4 +72,4 @@ BEGIN
WHERE
[Id] = CU.[CollectionId]
)
END
END

View File

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

View File

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

View File

@ -12,10 +12,11 @@ SELECT
OU.[Type],
OU.[AccessAll],
OU.[ExternalId],
SU.[ExternalId] SsoExternalId
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]
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]