1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-01 23:31:41 +01:00

[Provider] Create and access child organizations (#1427)

This commit is contained in:
Oscar Hinton 2021-07-08 17:05:32 +02:00 committed by GitHub
parent a6128c781a
commit feb3106f37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 756 additions and 232 deletions

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
@ -27,17 +28,19 @@ namespace Bit.CommCore.Services
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
IUserService userService, IMailService mailService, IDataProtectionProvider dataProtectionProvider,
IEventService eventService, GlobalSettings globalSettings)
IUserService userService, IOrganizationService organizationService, IMailService mailService,
IDataProtectionProvider dataProtectionProvider, IEventService eventService, GlobalSettings globalSettings)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_userRepository = userRepository;
_userService = userService;
_organizationService = organizationService;
_mailService = mailService;
_eventService = eventService;
_globalSettings = globalSettings;
@ -344,6 +347,21 @@ namespace Bit.CommCore.Services
await _providerOrganizationRepository.CreateAsync(providerOrganization);
}
public async Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, User user)
{
var (organization, _) = await _organizationService.SignUpAsync(organizationSignup, true);
var providerOrganization = new ProviderOrganization
{
ProviderId = providerId,
OrganizationId = organization.Id,
Key = organizationSignup.OwnerKey,
};
await _providerOrganizationRepository.CreateAsync(providerOrganization);
return providerOrganization;
}
// TODO: Implement this
public Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();

View File

@ -15,8 +15,8 @@ namespace Bit.Portal
{
private readonly IServiceProvider _serviceProvider;
public EnterprisePortalCurrentContext(IServiceProvider serviceProvider)
: base()
public EnterprisePortalCurrentContext(IProviderOrganizationRepository providerOrganizationRepository,
IServiceProvider serviceProvider) : base(providerOrganizationRepository)
{
_serviceProvider = serviceProvider;
}

View File

@ -19,15 +19,17 @@ namespace Bit.Admin.Controllers
private readonly IProviderRepository _providerRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly GlobalSettings _globalSettings;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IProviderService _providerService;
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderService providerService, GlobalSettings globalSettings)
IProviderService providerService, GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
_providerService = providerService;
_globalSettings = globalSettings;
_applicationCacheService = applicationCacheService;
}
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
@ -102,6 +104,23 @@ namespace Bit.Admin.Controllers
return View(new ProviderEditModel(provider, users));
}
[HttpPost]
[ValidateAntiForgeryToken]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<IActionResult> Edit(Guid id, ProviderEditModel model)
{
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
return RedirectToAction("Index");
}
model.ToProvider(provider);
await _providerRepository.ReplaceAsync(provider);
await _applicationCacheService.UpsertProviderAbilityAsync(provider);
return RedirectToAction("Edit", new { id });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Guid id)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Bit.Core.Enums.Provider;
using Bit.Core.Models.Data;
@ -8,6 +9,8 @@ namespace Bit.Admin.Models
{
public class ProviderEditModel : ProviderViewModel
{
public ProviderEditModel() { }
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
: base(provider, providerUsers)
{
@ -15,16 +18,24 @@ namespace Bit.Admin.Models
BusinessName = provider.BusinessName;
BillingEmail = provider.BillingEmail;
Enabled = provider.Enabled;
UseEvents = provider.UseEvents;
}
public string Administrators { get; set; }
public bool Enabled { get; set; }
public string BillingEmail { get; set; }
public string BusinessName { get; set; }
public string Name { get; set; }
[Display(Name = "Events")]
public bool UseEvents { get; set; }
public Provider ToProvider(Provider existingProvider)
{
existingProvider.Name = Name;
existingProvider.BusinessName = BusinessName;
existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
existingProvider.UseEvents = UseEvents;
existingProvider.Enabled = Enabled;
return existingProvider;
}
}
}

View File

@ -8,6 +8,8 @@ namespace Bit.Admin.Models
{
public class ProviderViewModel
{
public ProviderViewModel() { }
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
{
Provider = provider;

View File

@ -39,6 +39,11 @@
</div>
</div>
</div>
<h2>Features</h2>
<div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="UseEvents">
<label class="form-check-label" asp-for="UseEvents"></label>
</div>
</form>
<div class="d-flex mt-4">
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>

View File

@ -373,8 +373,11 @@ namespace Bit.Api.Controllers
OrganizationUserStatusType.Confirmed);
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var providerUserOrganizationDetails =
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
await _userService.TwoFactorIsEnabledAsync(user));
providerUserOrganizationDetails, await _userService.TwoFactorIsEnabledAsync(user));
return response;
}
@ -399,7 +402,7 @@ namespace Bit.Api.Controllers
}
await _userService.SaveUserAsync(model.ToUser(user));
var response = new ProfileResponseModel(user, null, null, await _userService.TwoFactorIsEnabledAsync(user));
var response = new ProfileResponseModel(user, null, null, null, await _userService.TwoFactorIsEnabledAsync(user));
return response;
}
@ -550,7 +553,7 @@ namespace Bit.Api.Controllers
BillingAddressCountry = model.Country,
BillingAddressPostalCode = model.PostalCode,
});
var profile = new ProfileResponseModel(user, null, null, await _userService.TwoFactorIsEnabledAsync(user));
var profile = new ProfileResponseModel(user, null, null, null, await _userService.TwoFactorIsEnabledAsync(user));
return new PaymentResponseModel
{
UserProfile = profile,

View File

@ -71,7 +71,7 @@ namespace Bit.Api.Controllers
{
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
if (cipher == null || !cipher.OrganizationId.HasValue ||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -119,7 +119,7 @@ namespace Bit.Api.Controllers
{
var userId = _userService.GetProperUserId(User).Value;
var cipher = model.ToCipherDetails(userId);
if (cipher.OrganizationId.HasValue && !_currentContext.OrganizationUser(cipher.OrganizationId.Value))
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -134,7 +134,7 @@ namespace Bit.Api.Controllers
{
var userId = _userService.GetProperUserId(User).Value;
var cipher = model.Cipher.ToCipherDetails(userId);
if (cipher.OrganizationId.HasValue && !_currentContext.OrganizationUser(cipher.OrganizationId.Value))
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -148,7 +148,7 @@ namespace Bit.Api.Controllers
public async Task<CipherMiniResponseModel> PostAdmin([FromBody]CipherCreateRequestModel model)
{
var cipher = model.Cipher.ToOrganizationCipher();
if (!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
if (!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -192,7 +192,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.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -211,7 +211,7 @@ namespace Bit.Api.Controllers
{
var userId = _userService.GetProperUserId(User).Value;
var orgIdGuid = new Guid(organizationId);
if (!_currentContext.ManageAllCollections(orgIdGuid) && !_currentContext.AccessReports(orgIdGuid))
if (!await _currentContext.ManageAllCollections(orgIdGuid) && !await _currentContext.AccessReports(orgIdGuid))
{
throw new NotFoundException();
}
@ -254,7 +254,7 @@ namespace Bit.Api.Controllers
}
var orgId = new Guid(organizationId);
if (!_currentContext.AccessImportExport(orgId))
if (!await _currentContext.AccessImportExport(orgId))
{
throw new NotFoundException();
}
@ -282,7 +282,7 @@ namespace Bit.Api.Controllers
var cipherId = new Guid(id);
var cipher = await _cipherRepository.GetByIdAsync(cipherId);
if (cipher == null || cipher.UserId != userId ||
!_currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
!await _currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
{
throw new NotFoundException();
}
@ -303,7 +303,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
if (cipher == null || !cipher.OrganizationId.HasValue ||
!_currentContext.OrganizationUser(cipher.OrganizationId.Value))
!await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -319,7 +319,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.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -349,7 +349,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.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -382,7 +382,7 @@ namespace Bit.Api.Controllers
}
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
!_currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
!await _currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
{
throw new NotFoundException();
}
@ -409,7 +409,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.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -438,7 +438,7 @@ namespace Bit.Api.Controllers
}
if (model == null || string.IsNullOrWhiteSpace(model.OrganizationId) ||
!_currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
!await _currentContext.ManageAllCollections(new Guid(model.OrganizationId)))
{
throw new NotFoundException();
}
@ -467,7 +467,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.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -514,7 +514,7 @@ namespace Bit.Api.Controllers
public async Task PutShareMany([FromBody]CipherBulkShareRequestModel model)
{
var organizationId = new Guid(model.Ciphers.First().OrganizationId);
if (!_currentContext.OrganizationUser(organizationId))
if (!await _currentContext.OrganizationUser(organizationId))
{
throw new NotFoundException();
}
@ -561,7 +561,7 @@ namespace Bit.Api.Controllers
else
{
var orgId = new Guid(organizationId);
if (!_currentContext.ManageAllCollections(orgId))
if (!await _currentContext.ManageAllCollections(orgId))
{
throw new NotFoundException();
}
@ -579,7 +579,7 @@ namespace Bit.Api.Controllers
await _cipherRepository.GetByIdAsync(idGuid, userId);
if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue ||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))))
{
throw new NotFoundException();
}
@ -686,7 +686,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid);
if (cipher == null || !cipher.OrganizationId.HasValue ||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
!await _currentContext.ManageAllCollections(cipher.OrganizationId.Value))
{
throw new NotFoundException();
}
@ -717,7 +717,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
if (cipher == null || cipher.UserId != userId || !_currentContext.OrganizationUser(organizationId))
if (cipher == null || cipher.UserId != userId || !await _currentContext.OrganizationUser(organizationId))
{
throw new NotFoundException();
}
@ -752,7 +752,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetByIdAsync(idGuid);
if (cipher == null || !cipher.OrganizationId.HasValue ||
!_currentContext.ManageAllCollections(cipher.OrganizationId.Value))
!await _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 (!ManageAnyCollections(orgIdGuid) && !_currentContext.ManageUsers(orgIdGuid))
if (!await ManageAnyCollections(orgIdGuid) && !await _currentContext.ManageUsers(orgIdGuid))
{
throw new NotFoundException();
}
var idGuid = new Guid(id);
if (_currentContext.ManageAllCollections(orgIdGuid))
if (await _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.ManageAllCollections(orgIdGuid) && !_currentContext.ManageUsers(orgIdGuid))
if (!await _currentContext.ManageAllCollections(orgIdGuid) && !await _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 (!ManageAnyCollections(orgIdGuid))
if (!await ManageAnyCollections(orgIdGuid))
{
throw new NotFoundException();
}
var collection = model.ToCollection(orgIdGuid);
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
!_currentContext.ManageAllCollections(orgIdGuid) ? _currentContext.UserId : null);
!await _currentContext.ManageAllCollections(orgIdGuid) ? _currentContext.UserId : null);
return new CollectionResponseModel(collection);
}
@ -154,12 +154,12 @@ namespace Bit.Api.Controllers
private async Task<Collection> GetCollectionAsync(Guid id, Guid orgId)
{
if (!ManageAnyCollections(orgId))
if (!await ManageAnyCollections(orgId))
{
throw new NotFoundException();
}
var collection = _currentContext.OrganizationAdmin(orgId) ?
var collection = await _currentContext.OrganizationAdmin(orgId) ?
await _collectionRepository.GetByIdAsync(id) :
await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value);
if (collection == null || collection.OrganizationId != orgId)
@ -170,9 +170,9 @@ namespace Bit.Api.Controllers
return collection;
}
private bool ManageAnyCollections(Guid orgId)
private async Task<bool> ManageAnyCollections(Guid orgId)
{
return _currentContext.ManageAssignedCollections(orgId) || _currentContext.ManageAllCollections(orgId);
return await _currentContext.ManageAssignedCollections(orgId) || await _currentContext.ManageAllCollections(orgId);
}
}
}

View File

@ -61,7 +61,7 @@ namespace Bit.Api.Controllers
var canView = false;
if (cipher.OrganizationId.HasValue)
{
canView = _currentContext.AccessEventLogs(cipher.OrganizationId.Value);
canView = await _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.AccessEventLogs(orgId))
if (!await _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.AccessEventLogs(organizationUser.OrganizationId))
!await _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.ManageGroups(group.OrganizationId))
if (group == null || !await _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.ManageGroups(groupDetails.Item1.OrganizationId))
if (groupDetails?.Item1 == null || !await _currentContext.ManageGroups(groupDetails.Item1.OrganizationId))
{
throw new NotFoundException();
}
@ -58,10 +58,10 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
var canAccess = _currentContext.ManageGroups(orgIdGuid) ||
_currentContext.ManageAssignedCollections(orgIdGuid) ||
_currentContext.ManageAllCollections(orgIdGuid) ||
_currentContext.ManageUsers(orgIdGuid);
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
await _currentContext.ManageAssignedCollections(orgIdGuid) ||
await _currentContext.ManageAllCollections(orgIdGuid) ||
await _currentContext.ManageUsers(orgIdGuid);
if (!canAccess)
{
@ -78,7 +78,7 @@ namespace Bit.Api.Controllers
{
var idGuid = new Guid(id);
var group = await _groupRepository.GetByIdAsync(idGuid);
if (group == null || !_currentContext.ManageGroups(group.OrganizationId))
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
@ -91,7 +91,7 @@ namespace Bit.Api.Controllers
public async Task<GroupResponseModel> Post(string orgId, [FromBody]GroupRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!_currentContext.ManageGroups(orgIdGuid))
if (!await _currentContext.ManageGroups(orgIdGuid))
{
throw new NotFoundException();
}
@ -106,7 +106,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.ManageGroups(group.OrganizationId))
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
@ -119,7 +119,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.ManageGroups(group.OrganizationId))
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
@ -131,7 +131,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.ManageGroups(group.OrganizationId))
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
@ -144,7 +144,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.ManageGroups(group.OrganizationId))
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}

View File

@ -49,7 +49,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.ManageUsers(organizationUser.Item1.OrganizationId))
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
{
throw new NotFoundException();
}
@ -61,9 +61,9 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageAssignedCollections(orgGuidId) &&
!_currentContext.ManageGroups(orgGuidId) &&
!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageAssignedCollections(orgGuidId) &&
!await _currentContext.ManageGroups(orgGuidId) &&
!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -79,8 +79,8 @@ 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.ManageGroups(organizationUser.OrganizationId) &&
!_currentContext.ManageUsers(organizationUser.OrganizationId)))
if (organizationUser == null || (!await _currentContext.ManageGroups(organizationUser.OrganizationId) &&
!await _currentContext.ManageUsers(organizationUser.OrganizationId)))
{
throw new NotFoundException();
}
@ -95,7 +95,7 @@ namespace Bit.Api.Controllers
{
// Make sure the calling user can reset passwords for this org
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageResetPassword(orgGuidId))
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
@ -128,7 +128,7 @@ namespace Bit.Api.Controllers
public async Task Invite(string orgId, [FromBody]OrganizationUserInviteRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -141,7 +141,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody]OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -156,7 +156,7 @@ namespace Bit.Api.Controllers
public async Task Reinvite(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -181,7 +181,7 @@ namespace Bit.Api.Controllers
public async Task Confirm(string orgId, string id, [FromBody]OrganizationUserConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -196,7 +196,7 @@ namespace Bit.Api.Controllers
[FromBody]OrganizationUserBulkConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -213,7 +213,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(string orgId, [FromBody]OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -228,7 +228,7 @@ namespace Bit.Api.Controllers
public async Task Put(string orgId, string id, [FromBody]OrganizationUserUpdateRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -249,7 +249,7 @@ namespace Bit.Api.Controllers
public async Task PutGroups(string orgId, string id, [FromBody]OrganizationUserUpdateGroupsRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -278,7 +278,7 @@ namespace Bit.Api.Controllers
var orgGuidId = new Guid(orgId);
// Calling user must have Manage Reset Password permission
if (!_currentContext.ManageResetPassword(orgGuidId))
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
@ -310,7 +310,7 @@ namespace Bit.Api.Controllers
public async Task Delete(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
@ -324,7 +324,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDelete(string orgId, [FromBody]OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!_currentContext.ManageUsers(orgGuidId))
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}

View File

@ -51,7 +51,7 @@ namespace Bit.Api.Controllers
public async Task<OrganizationResponseModel> Get(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -70,7 +70,7 @@ namespace Bit.Api.Controllers
public async Task<BillingResponseModel> GetBilling(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -89,7 +89,7 @@ namespace Bit.Api.Controllers
public async Task<OrganizationSubscriptionResponseModel> GetSubscription(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -120,7 +120,7 @@ namespace Bit.Api.Controllers
public async Task<OrganizationLicense> GetLicense(string id, [FromQuery]Guid installationId)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -154,12 +154,6 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException();
}
var plan = StaticStore.Plans.FirstOrDefault(plan => plan.Type == model.PlanType);
if (plan == null || plan.LegacyYear != null)
{
throw new Exception("Invalid plan selected.");
}
var organizationSignup = model.ToOrganizationSignup(user);
var result = await _organizationService.SignUpAsync(organizationSignup);
return new OrganizationResponseModel(result.Item1);
@ -191,7 +185,7 @@ namespace Bit.Api.Controllers
public async Task<OrganizationResponseModel> Put(string id, [FromBody]OrganizationUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -214,7 +208,7 @@ namespace Bit.Api.Controllers
public async Task PostPayment(string id, [FromBody]PaymentRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -237,7 +231,7 @@ namespace Bit.Api.Controllers
public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody]OrganizationUpgradeRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -255,7 +249,7 @@ namespace Bit.Api.Controllers
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody]OrganizationSeatRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -273,7 +267,7 @@ namespace Bit.Api.Controllers
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody]StorageRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -291,7 +285,7 @@ namespace Bit.Api.Controllers
public async Task PostVerifyBank(string id, [FromBody]OrganizationVerifyBankRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -304,7 +298,7 @@ namespace Bit.Api.Controllers
public async Task PostCancel(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -317,7 +311,7 @@ namespace Bit.Api.Controllers
public async Task PostReinstate(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -329,7 +323,7 @@ namespace Bit.Api.Controllers
public async Task Leave(string id)
{
var orgGuidId = new Guid(id);
if (!_currentContext.OrganizationUser(orgGuidId))
if (!await _currentContext.OrganizationUser(orgGuidId))
{
throw new NotFoundException();
}
@ -343,7 +337,7 @@ namespace Bit.Api.Controllers
public async Task Delete(string id, [FromBody]OrganizationDeleteRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -376,7 +370,7 @@ namespace Bit.Api.Controllers
public async Task PostLicense(string id, LicenseRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -400,7 +394,7 @@ namespace Bit.Api.Controllers
}
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationAdmin(orgIdGuid))
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
@ -419,7 +413,7 @@ namespace Bit.Api.Controllers
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody]ApiKeyRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -452,7 +446,7 @@ namespace Bit.Api.Controllers
public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody]ApiKeyRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -487,7 +481,7 @@ namespace Bit.Api.Controllers
public async Task<TaxInfoResponseModel> GetTaxInfo(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
@ -507,7 +501,7 @@ namespace Bit.Api.Controllers
public async Task PutTaxInfo(string id, [FromBody]OrganizationTaxInfoUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationOwner(orgIdGuid))
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}

View File

@ -53,7 +53,7 @@ namespace Bit.Api.Controllers
public async Task<PolicyResponseModel> Get(string orgId, int type)
{
var orgIdGuid = new Guid(orgId);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
@ -70,7 +70,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
@ -109,7 +109,7 @@ namespace Bit.Api.Controllers
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody]PolicyRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}

View File

@ -6,6 +6,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Api;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -58,5 +59,25 @@ namespace Bit.Api.Controllers
await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key);
}
[HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<ProviderOrganizationResponseModel> Post(Guid providerId, [FromBody]OrganizationCreateRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var organizationSignup = model.ToOrganizationSignup(user);
var result = await _providerService.CreateOrganizationAsync(providerId, organizationSignup, user);
return new ProviderOrganizationResponseModel(result);
}
}
}

View File

@ -100,11 +100,6 @@ namespace Bit.Api.Controllers
[HttpPost("{id:guid}/accept")]
public async Task Accept(Guid providerId, Guid id, [FromBody]ProviderUserAcceptRequestModel model)
{
if (!_currentContext.ManageProviderUsers(providerId))
{
throw new NotFoundException();
}
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{

View File

@ -68,6 +68,9 @@ namespace Bit.Api.Controllers
OrganizationUserStatusType.Confirmed);
var providerUserDetails = await _providerUserRepository.GetManyDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var providerUserOrganizationDetails =
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
ProviderUserStatusType.Confirmed);
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, hasEnabledOrgs);
@ -86,8 +89,8 @@ namespace Bit.Api.Controllers
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, organizationUserDetails,
providerUserDetails, folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains,
policies, sends);
providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers,
collectionCiphersGroupDict, excludeDomains, policies, sends);
return response;
}
}

View File

@ -63,7 +63,7 @@ namespace Bit.Api.Controllers
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> GetOrganization(string id)
{
var orgIdGuid = new Guid(id);
if (!_currentContext.OrganizationAdmin(orgIdGuid))
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
@ -169,7 +169,7 @@ namespace Bit.Api.Controllers
var user = await CheckAsync(model.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
@ -192,7 +192,7 @@ namespace Bit.Api.Controllers
var user = await CheckAsync(model.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
@ -332,7 +332,7 @@ namespace Bit.Api.Controllers
var user = await CheckAsync(model.MasterPasswordHash, false);
var orgIdGuid = new Guid(id);
if (!_currentContext.ManagePolicies(orgIdGuid))
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}

View File

@ -10,14 +10,17 @@ using System.Security.Claims;
using Bit.Core.Enums.Provider;
using Bit.Core.Utilities;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table.Provider;
using Bit.Core.Settings;
namespace Bit.Core.Context
{
public class CurrentContext : ICurrentContext
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private bool _builtHttpContext;
private bool _builtClaimsPrincipal;
private ICollection<ProviderOrganization> _providerOrganizations;
public virtual HttpContext HttpContext { get; set; }
public virtual Guid? UserId { get; set; }
@ -34,6 +37,11 @@ namespace Bit.Core.Context
public virtual bool MaybeBot { get; set; }
public virtual int? BotScore { get; set; }
public CurrentContext(IProviderOrganizationRepository providerOrganizationRepository)
{
_providerOrganizationRepository = providerOrganizationRepository;
}
public async virtual Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings)
{
if (_builtHttpContext)
@ -197,7 +205,7 @@ namespace Bit.Core.Context
Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict)
}));
}
return organizations;
}
@ -227,97 +235,106 @@ namespace Bit.Core.Context
return providers;
}
public bool OrganizationUser(Guid orgId)
public async Task<bool> OrganizationUser(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId) ?? false;
return (Organizations?.Any(o => o.Id == orgId) ?? false) || await OrganizationOwner(orgId);
}
public bool OrganizationManager(Guid orgId)
public async Task<bool> OrganizationManager(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId &&
(o.Type == OrganizationUserType.Owner || o.Type == OrganizationUserType.Admin ||
o.Type == OrganizationUserType.Manager)) ?? false;
return await OrganizationAdmin(orgId) ||
(Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Manager) ?? false);
}
public bool OrganizationAdmin(Guid orgId)
public async Task<bool> OrganizationAdmin(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId &&
(o.Type == OrganizationUserType.Owner || o.Type == OrganizationUserType.Admin)) ?? false;
return await OrganizationOwner(orgId) ||
(Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Admin) ?? false);
}
public bool OrganizationOwner(Guid orgId)
public async Task<bool> OrganizationOwner(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner) ?? false;
if (Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner) ?? false)
{
return true;
}
if (Providers.Any())
{
return (await GetProviderOrganizations()).Any(po => po.OrganizationId == orgId);
}
return false;
}
public bool OrganizationCustom(Guid orgId)
public Task<bool> OrganizationCustom(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Custom) ?? false;
return Task.FromResult(Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Custom) ?? false);
}
public bool AccessBusinessPortal(Guid orgId)
public async Task<bool> AccessBusinessPortal(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.AccessBusinessPortal ?? false)) ?? false);
}
public bool AccessEventLogs(Guid orgId)
public async Task<bool> AccessEventLogs(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.AccessEventLogs ?? false)) ?? false);
}
public bool AccessImportExport(Guid orgId)
public async Task<bool> AccessImportExport(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.AccessImportExport ?? false)) ?? false);
}
public bool AccessReports(Guid orgId)
public async Task<bool> AccessReports(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.AccessReports ?? false)) ?? false);
}
public bool ManageAllCollections(Guid orgId)
public async Task<bool> ManageAllCollections(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageAllCollections ?? false)) ?? false);
}
public bool ManageAssignedCollections(Guid orgId)
public async Task<bool> ManageAssignedCollections(Guid orgId)
{
return OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageAssignedCollections ?? false)) ?? false);
}
public bool ManageGroups(Guid orgId)
public async Task<bool> ManageGroups(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageGroups ?? false)) ?? false);
}
public bool ManagePolicies(Guid orgId)
public async Task<bool> ManagePolicies(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManagePolicies ?? false)) ?? false);
}
public bool ManageSso(Guid orgId)
public async Task<bool> ManageSso(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageSso ?? false)) ?? false);
}
public bool ManageUsers(Guid orgId)
public async Task<bool> ManageUsers(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageUsers ?? false)) ?? false);
}
public bool ManageResetPassword(Guid orgId)
public async Task<bool> ManageResetPassword(Guid orgId)
{
return OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId
&& (o.Permissions?.ManageResetPassword ?? false)) ?? false);
}
@ -403,5 +420,15 @@ namespace Bit.Core.Context
ManageResetPassword = hasClaim("manageresetpassword")
};
}
private async Task<ICollection<ProviderOrganization>> GetProviderOrganizations()
{
if (_providerOrganizations == null)
{
_providerOrganizations = await _providerOrganizationRepository.GetManyByUserIdAsync(UserId.Value);
}
return _providerOrganizations;
}
}
}

View File

@ -31,22 +31,22 @@ namespace Bit.Core.Context
Task SetContextAsync(ClaimsPrincipal user);
bool OrganizationUser(Guid orgId);
bool OrganizationManager(Guid orgId);
bool OrganizationAdmin(Guid orgId);
bool OrganizationOwner(Guid orgId);
bool OrganizationCustom(Guid orgId);
bool AccessBusinessPortal(Guid orgId);
bool AccessEventLogs(Guid orgId);
bool AccessImportExport(Guid orgId);
bool AccessReports(Guid orgId);
bool ManageAllCollections(Guid orgId);
bool ManageAssignedCollections(Guid orgId);
bool ManageGroups(Guid orgId);
bool ManagePolicies(Guid orgId);
bool ManageSso(Guid orgId);
bool ManageUsers(Guid orgId);
bool ManageResetPassword(Guid orgId);
Task<bool> OrganizationUser(Guid orgId);
Task<bool> OrganizationManager(Guid orgId);
Task<bool> OrganizationAdmin(Guid orgId);
Task<bool> OrganizationOwner(Guid orgId);
Task<bool> OrganizationCustom(Guid orgId);
Task<bool> AccessBusinessPortal(Guid orgId);
Task<bool> AccessEventLogs(Guid orgId);
Task<bool> AccessImportExport(Guid orgId);
Task<bool> AccessReports(Guid orgId);
Task<bool> ManageAllCollections(Guid orgId);
Task<bool> ManageAssignedCollections(Guid orgId);
Task<bool> ManageGroups(Guid orgId);
Task<bool> ManagePolicies(Guid orgId);
Task<bool> ManageSso(Guid orgId);
Task<bool> ManageUsers(Guid orgId);
Task<bool> ManageResetPassword(Guid orgId);
bool ProviderProviderAdmin(Guid providerId);
bool ProviderUser(Guid providerId);
bool ManageProviderUsers(Guid providerId);

View File

@ -26,6 +26,7 @@ namespace Bit.Core.IdentityServer
private readonly ICurrentContext _currentContext;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
public ClientStore(
IInstallationRepository installationRepository,
@ -36,7 +37,8 @@ namespace Bit.Core.IdentityServer
ILicensingService licensingService,
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository)
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository)
{
_installationRepository = installationRepository;
_organizationRepository = organizationRepository;
@ -47,6 +49,7 @@ namespace Bit.Core.IdentityServer
_currentContext = currentContext;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
}
public async Task<Client> FindClientByIdAsync(string clientId)

View File

@ -19,6 +19,7 @@ namespace Bit.Core.IdentityServer
private readonly IUserService _userService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly ILicensingService _licensingService;
private readonly ICurrentContext _currentContext;
@ -26,12 +27,14 @@ namespace Bit.Core.IdentityServer
IUserService userService,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
ILicensingService licensingService,
ICurrentContext currentContext)
{
_userService = userService;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_licensingService = licensingService;
_currentContext = currentContext;
}

View File

@ -0,0 +1,72 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Api
{
public class ProfileProviderOrganizationResponseModel : ResponseModel
{
public ProfileProviderOrganizationResponseModel(ProviderUserOrganizationDetails organization)
: base("profileProviderOrganization")
{
Id = organization.OrganizationId.ToString();
Name = organization.Name;
UsePolicies = organization.UsePolicies;
UseSso = organization.UseSso;
UseGroups = organization.UseGroups;
UseDirectory = organization.UseDirectory;
UseEvents = organization.UseEvents;
UseTotp = organization.UseTotp;
Use2fa = organization.Use2fa;
UseApi = organization.UseApi;
UseResetPassword = organization.UseResetPassword;
UsersGetPremium = organization.UsersGetPremium;
SelfHost = organization.SelfHost;
Seats = organization.Seats;
MaxCollections = organization.MaxCollections;
MaxStorageGb = organization.MaxStorageGb;
Key = organization.Key;
HasPublicAndPrivateKeys = organization.PublicKey != null && organization.PrivateKey != null;
Status = organization.Status;
Type = organization.Type;
Enabled = organization.Enabled;
SsoBound = !string.IsNullOrWhiteSpace(organization.SsoExternalId);
Identifier = organization.Identifier;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organization.Permissions);
ResetPasswordEnrolled = organization.ResetPasswordKey != null;
UserId = organization.UserId?.ToString();
ProviderId = organization.ProviderId?.ToString();
ProviderName = organization.ProviderName;
}
public string Id { get; set; }
public string Name { get; set; }
public bool UsePolicies { get; set; }
public bool UseSso { get; set; }
public bool UseGroups { get; set; }
public bool UseDirectory { get; set; }
public bool UseEvents { get; set; }
public bool UseTotp { get; set; }
public bool Use2fa { get; set; }
public bool UseApi { get; set; }
public bool UseResetPassword { get; set; }
public bool UseBusinessPortal => UsePolicies || UseSso; // TODO add events if needed
public bool UsersGetPremium { get; set; }
public bool SelfHost { get; set; }
public int Seats { get; set; }
public int MaxCollections { get; set; }
public short? MaxStorageGb { get; set; }
public string Key { get; set; }
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }
public bool Enabled { get; set; }
public bool SsoBound { get; set; }
public string Identifier { get; set; }
public Permissions Permissions { get; set; }
public bool ResetPasswordEnrolled { get; set; }
public string UserId { get; set; }
public bool HasPublicAndPrivateKeys { get; set; }
public string ProviderId { get; set; }
public string ProviderName { get; set; }
}
}

View File

@ -3,7 +3,6 @@ using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Models.Data;
using Bit.Core.Services;
namespace Bit.Core.Models.Api
{
@ -11,7 +10,9 @@ namespace Bit.Core.Models.Api
{
public ProfileResponseModel(User user,
IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails,
IEnumerable<ProviderUserProviderDetails> providerUserDetails, bool twoFactorEnabled) : base("profile")
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
bool twoFactorEnabled) : base("profile")
{
if (user == null)
{
@ -30,7 +31,9 @@ namespace Bit.Core.Models.Api
PrivateKey = user.PrivateKey;
SecurityStamp = user.SecurityStamp;
Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o));
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
Providers = providerUserDetails?.Where(p => p.Enabled).Select(p => new ProfileProviderResponseModel(p));
ProviderOrganizations =
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
}
public string Id { get; set; }
@ -46,5 +49,6 @@ namespace Bit.Core.Models.Api
public string SecurityStamp { get; set; }
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
public IEnumerable<ProfileProviderResponseModel> Providers { get; set; }
public IEnumerable<ProfileProviderOrganizationResponseModel> ProviderOrganizations { get; set; }
}
}

View File

@ -17,6 +17,7 @@ namespace Bit.Core.Models.Api
Enabled = provider.Enabled;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(provider.Permissions);
UserId = provider.UserId?.ToString();
UseEvents = provider.UseEvents;
}
public string Id { get; set; }
@ -27,5 +28,6 @@ namespace Bit.Core.Models.Api
public bool Enabled { get; set; }
public Permissions Permissions { get; set; }
public string UserId { get; set; }
public bool UseEvents { get; set; }
}
}

View File

@ -1,22 +1,39 @@
using System;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table.Provider;
namespace Bit.Core.Models.Api
{
public class ProviderOrganizationOrganizationDetailsResponseModel : ResponseModel
public class ProviderOrganizationResponseModel : ResponseModel
{
public ProviderOrganizationOrganizationDetailsResponseModel(ProviderOrganizationOrganizationDetails providerOrganization,
public ProviderOrganizationResponseModel(ProviderOrganization providerOrganization,
string obj = "providerOrganization") : base(obj)
{
if (providerOrganization == null)
{
throw new ArgumentNullException(nameof(providerOrganization));
}
Id = providerOrganization.Id;
ProviderId = providerOrganization.ProviderId;
OrganizationId = providerOrganization.OrganizationId;
Key = providerOrganization.Key;
Settings = providerOrganization.Settings;
CreationDate = providerOrganization.CreationDate;
RevisionDate = providerOrganization.RevisionDate;
}
public ProviderOrganizationResponseModel(ProviderOrganizationOrganizationDetails providerOrganization,
string obj = "providerOrganization") : base(obj)
{
if (providerOrganization == null)
{
throw new ArgumentNullException(nameof(providerOrganization));
}
Id = providerOrganization.Id;
ProviderId = providerOrganization.ProviderId;
OrganizationId = providerOrganization.OrganizationId;
OrganizationName = providerOrganization.OrganizationName;
Key = providerOrganization.Key;
Settings = providerOrganization.Settings;
CreationDate = providerOrganization.CreationDate;
@ -26,10 +43,25 @@ namespace Bit.Core.Models.Api
public Guid Id { get; set; }
public Guid ProviderId { get; set; }
public Guid OrganizationId { get; set; }
public string OrganizationName { get; set; }
public string Key { get; set; }
public string Settings { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
}
public class ProviderOrganizationOrganizationDetailsResponseModel : ProviderOrganizationResponseModel
{
public ProviderOrganizationOrganizationDetailsResponseModel(ProviderOrganizationOrganizationDetails providerOrganization,
string obj = "providerOrganizationOrganizationDetail") : base(providerOrganization, obj)
{
if (providerOrganization == null)
{
throw new ArgumentNullException(nameof(providerOrganization));
}
OrganizationName = providerOrganization.OrganizationName;
}
public string OrganizationName { get; set; }
}
}

View File

@ -16,6 +16,7 @@ namespace Bit.Core.Models.Api
bool userTwoFactorEnabled,
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
IEnumerable<Folder> folders,
IEnumerable<CollectionDetails> collections,
IEnumerable<CipherDetails> ciphers,
@ -25,7 +26,8 @@ namespace Bit.Core.Models.Api
IEnumerable<Send> sends)
: base("sync")
{
Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, userTwoFactorEnabled);
Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
providerUserOrganizationDetails, userTwoFactorEnabled);
Folders = folders.Select(f => new FolderResponseModel(f));
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
Collections = collections?.Select(

View File

@ -0,0 +1,38 @@
using System;
namespace Bit.Core.Models.Data
{
public class ProviderUserOrganizationDetails
{
public Guid OrganizationId { get; set; }
public Guid? UserId { get; set; }
public string Name { get; set; }
public bool UsePolicies { get; set; }
public bool UseSso { get; set; }
public bool UseGroups { get; set; }
public bool UseDirectory { get; set; }
public bool UseEvents { get; set; }
public bool UseTotp { get; set; }
public bool Use2fa { get; set; }
public bool UseApi{ get; set; }
public bool UseResetPassword { get; set; }
public bool UseBusinessPortal => UsePolicies || UseSso;
public bool SelfHost { get; set; }
public bool UsersGetPremium { get; set; }
public int Seats { get; set; }
public int MaxCollections { get; set; }
public short? MaxStorageGb { get; set; }
public string Key { get; set; }
public Enums.OrganizationUserStatusType Status { get; set; }
public Enums.OrganizationUserType Type { get; set; }
public bool Enabled { get; set; }
public string SsoExternalId { get; set; }
public string Identifier { get; set; }
public string Permissions { get; set; }
public string ResetPasswordKey { get; set; }
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
public Guid? ProviderId { get; set; }
public string ProviderName { get; set; }
}
}

View File

@ -13,5 +13,6 @@ namespace Bit.Core.Models.Data
public ProviderUserType Type { get; set; }
public bool Enabled { get; set; }
public string Permissions { get; set; }
public bool UseEvents { get; set; }
}
}

View File

@ -9,5 +9,6 @@ namespace Bit.Core.Repositories
public interface IProviderOrganizationRepository : IRepository<ProviderOrganization, Guid>
{
Task<ICollection<ProviderOrganizationOrganizationDetails>> GetManyDetailsByProviderAsync(Guid providerId);
Task<ICollection<ProviderOrganization>> GetManyByUserIdAsync(Guid userId);
}
}

View File

@ -17,6 +17,7 @@ namespace Bit.Core.Repositories
Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId);
Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId,
ProviderUserStatusType? status = null);
Task<IEnumerable<ProviderUserOrganizationDetails>> GetManyOrganizationDetailsByUserAsync(Guid userId, ProviderUserStatusType? status = null);
Task DeleteManyAsync(IEnumerable<Guid> userIds);
Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(Guid providerId, IEnumerable<Guid> Ids);
}

View File

@ -33,5 +33,18 @@ namespace Bit.Core.Repositories.SqlServer
return results.ToList();
}
}
public async Task<ICollection<ProviderOrganization>> GetManyByUserIdAsync(Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<ProviderOrganization>(
"[dbo].[ProviderOrganization_ReadByUserId]",
new { UserId = userId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
}
}

View File

@ -115,6 +115,20 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<IEnumerable<ProviderUserOrganizationDetails>> GetManyOrganizationDetailsByUserAsync(Guid userId,
ProviderUserStatusType? status = null)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<ProviderUserOrganizationDetails>(
"[dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]",
new { UserId = userId, Status = status },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task DeleteManyAsync(IEnumerable<Guid> providerUserIds)
{
using (var connection = new SqlConnection(ConnectionString))

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
namespace Bit.Core.Services
{
@ -11,6 +12,7 @@ namespace Bit.Core.Services
Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync();
Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync();
Task UpsertOrganizationAbilityAsync(Organization organization);
Task UpsertProviderAbilityAsync(Provider provider);
Task DeleteOrganizationAbilityAsync(Guid organizationId);
}
}

View File

@ -19,7 +19,7 @@ namespace Bit.Core.Services
Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb);
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup, bool provider = false);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationLicense license, User owner,
string ownerKey, string collectionName, string publicKey, string privateKey);
Task UpdateLicenseAsync(Guid organizationId, OrganizationLicense license);

View File

@ -2,6 +2,7 @@
using Bit.Core.Models.Table;
using System.Collections.Generic;
using System;
using Bit.Core.Models.Business;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table.Provider;
@ -24,8 +25,7 @@ namespace Bit.Core.Services
Guid deletingUserId);
Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key);
Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, User user);
Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId);
// TODO: Figure out how ProviderOrganizationProviderUsers should be managed
}
}

View File

@ -297,7 +297,7 @@ namespace Bit.Core.Services
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
await _userRepository.ReplaceAsync(grantor);
// Remove grantor from all organisations unless Owner
// Remove grantor from all organizations unless Owner
var orgUser = await _organizationUserRepository.GetManyByUserAsync(grantor.Id);
foreach (var o in orgUser)
{

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
using Bit.Core.Repositories;
namespace Bit.Core.Services
@ -36,6 +37,21 @@ namespace Bit.Core.Services
await InitProviderAbilitiesAsync();
return _providerAbilities;
}
public virtual async Task UpsertProviderAbilityAsync(Provider provider)
{
await InitProviderAbilitiesAsync();
var newAbility = new ProviderAbility(provider);
if (_providerAbilities.ContainsKey(provider.Id))
{
_providerAbilities[provider.Id] = newAbility;
}
else
{
_providerAbilities.Add(provider.Id, newAbility);
}
}
public virtual async Task UpsertOrganizationAbilityAsync(Organization organization)
{

View File

@ -552,15 +552,25 @@ namespace Bit.Core.Services
}
}
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup)
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup,
bool provider = false)
{
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan && !p.Disabled);
if (plan == null)
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan);
if (!(plan is {LegacyYear: null}))
{
throw new BadRequestException("Invalid plan selected.");
}
if (plan.Disabled)
{
throw new BadRequestException("Plan not found.");
}
await ValidateSignUpPoliciesAsync(signup.Owner.Id);
if (!provider)
{
await ValidateSignUpPoliciesAsync(signup.Owner.Id);
}
ValidateOrganizationUpgradeParameters(plan, signup);
var organization = new Organization
@ -598,7 +608,7 @@ namespace Bit.Core.Services
RevisionDate = DateTime.UtcNow,
};
if (plan.Type == PlanType.Free)
if (plan.Type == PlanType.Free && !provider)
{
var adminCount =
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
@ -607,14 +617,15 @@ namespace Bit.Core.Services
throw new BadRequestException("You can only be an admin of one free organization.");
}
}
else
else if (plan.Type != PlanType.Free)
{
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PremiumAccessAddon, signup.TaxInfo);
}
var returnValue = await SignUpAsync(organization, signup.Owner.Id, signup.OwnerKey, signup.CollectionName, true);
var ownerId = provider ? default : signup.Owner.Id;
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.Signup, organization)
{
@ -725,20 +736,6 @@ namespace Bit.Core.Services
await _organizationRepository.CreateAsync(organization);
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
var orgUser = new OrganizationUser
{
OrganizationId = organization.Id,
UserId = ownerId,
Key = ownerKey,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed,
AccessAll = true,
CreationDate = organization.CreationDate,
RevisionDate = organization.CreationDate
};
await _organizationUserRepository.CreateAsync(orgUser);
if (!string.IsNullOrWhiteSpace(collectionName))
{
var defaultCollection = new Collection
@ -751,11 +748,28 @@ namespace Bit.Core.Services
await _collectionRepository.CreateAsync(defaultCollection);
}
// push
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds,
organization.Id.ToString());
await _pushNotificationService.PushSyncOrgKeysAsync(ownerId);
OrganizationUser orgUser = null;
if (ownerId != default)
{
orgUser = new OrganizationUser
{
OrganizationId = organization.Id,
UserId = ownerId,
Key = ownerKey,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed,
AccessAll = true,
CreationDate = organization.CreationDate,
RevisionDate = organization.CreationDate
};
await _organizationUserRepository.CreateAsync(orgUser);
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds,
organization.Id.ToString());
await _pushNotificationService.PushSyncOrgKeysAsync(ownerId);
}
return new Tuple<Organization, OrganizationUser>(organization, orgUser);
}
@ -1051,7 +1065,7 @@ namespace Bit.Core.Services
{
foreach (var type in inviteTypes)
{
ValidateOrganizationUserUpdatePermissions(organizationId, type, null);
await ValidateOrganizationUserUpdatePermissions(organizationId, type, null);
}
}
@ -1158,7 +1172,7 @@ namespace Bit.Core.Services
if (invitingUserId.HasValue && invite.Type.HasValue)
{
ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null);
await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null);
}
if (organization.Seats.HasValue)
@ -1172,6 +1186,12 @@ namespace Bit.Core.Services
}
}
var invitedIsOwner = invite.Type is OrganizationUserType.Owner;
if (!invitedIsOwner && !await HasConfirmedOwnersExceptAsync(organizationId, new Guid[] {}))
{
throw new BadRequestException("Organization must have at least one confirmed owner.");
}
var orgUsers = new List<OrganizationUser>();
var orgUserInvitedCount = 0;
foreach (var email in invite.Emails)
@ -1532,7 +1552,7 @@ namespace Bit.Core.Services
if (savingUserId.HasValue)
{
ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type);
await ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type);
}
if (user.Type != OrganizationUserType.Owner &&
@ -1564,7 +1584,7 @@ namespace Bit.Core.Services
}
if (orgUser.Type == OrganizationUserType.Owner && deletingUserId.HasValue &&
!_currentContext.OrganizationOwner(organizationId))
!await _currentContext.OrganizationOwner(organizationId))
{
throw new BadRequestException("Only owners can delete other owners.");
}
@ -1626,7 +1646,7 @@ namespace Bit.Core.Services
var deletingUserIsOwner = false;
if (deletingUserId.HasValue)
{
deletingUserIsOwner = _currentContext.OrganizationOwner(organizationId);
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
}
var result = new List<Tuple<OrganizationUser, string>>();
@ -1676,7 +1696,7 @@ namespace Bit.Core.Services
{
if (loggedInUserId.HasValue)
{
ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null);
await ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null);
}
await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds);
await _eventService.LogOrganizationUserEventAsync(organizationUser,
@ -1961,7 +1981,7 @@ namespace Bit.Core.Services
public async Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey)
{
if (!_currentContext.ManageResetPassword(orgId))
if (!await _currentContext.ManageResetPassword(orgId))
{
throw new UnauthorizedAccessException();
}
@ -1972,7 +1992,7 @@ namespace Bit.Core.Services
{
throw new BadRequestException("Organization Keys already exist");
}
// Update org with generated public/private key
org.PublicKey = publicKey;
org.PrivateKey = privateKey;
@ -2072,10 +2092,10 @@ namespace Bit.Core.Services
}
}
private void ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
private async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
OrganizationUserType? oldType)
{
if (_currentContext.OrganizationOwner(organizationId))
if (await _currentContext.OrganizationOwner(organizationId))
{
return;
}
@ -2085,7 +2105,7 @@ namespace Bit.Core.Services
throw new BadRequestException("Only an Owner can configure another Owner's account.");
}
if (_currentContext.OrganizationAdmin(organizationId))
if (await _currentContext.OrganizationAdmin(organizationId))
{
return;
}
@ -2095,7 +2115,7 @@ namespace Bit.Core.Services
throw new BadRequestException("Only Owners and Admins can configure Custom accounts.");
}
if (!_currentContext.ManageUsers(organizationId))
if (!await _currentContext.ManageUsers(organizationId))
{
throw new BadRequestException("Your account does not have permission to manage users.");
}

View File

@ -284,7 +284,7 @@ namespace Bit.Core.Services
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.DisableSend))
{
if (!_currentContext.ManagePolicies(policy.OrganizationId))
if (!await _currentContext.ManagePolicies(policy.OrganizationId))
{
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}
@ -292,8 +292,13 @@ namespace Bit.Core.Services
if (send.HideEmail.GetValueOrDefault())
{
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.SendOptions && !_currentContext.ManagePolicies(p.OrganizationId)))
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.SendOptions))
{
if (await _currentContext.ManagePolicies(policy.OrganizationId))
{
continue;
}
SendOptionsPolicyData data = null;
if (policy.Data != null)
{

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Models.Business;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Models.Table;
using Bit.Core.Models.Table.Provider;
@ -28,6 +29,7 @@ namespace Bit.Core.Services
public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException();
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
public Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, User user) => throw new NotImplementedException();
public Task RemoveOrganization(Guid providerOrganizationId, Guid removingUserId) => throw new NotImplementedException();
}

View File

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
@ -9,18 +10,21 @@ namespace Bit.Notifications
[Authorize("Application")]
public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly ConnectionCounter _connectionCounter;
private readonly GlobalSettings _globalSettings;
public NotificationsHub(ConnectionCounter connectionCounter, GlobalSettings globalSettings)
public NotificationsHub(IProviderOrganizationRepository providerOrganizationRepository,
ConnectionCounter connectionCounter, GlobalSettings globalSettings)
{
_providerOrganizationRepository = providerOrganizationRepository;
_connectionCounter = connectionCounter;
_globalSettings = globalSettings;
}
public override async Task OnConnectedAsync()
{
var currentContext = new CurrentContext();
var currentContext = new CurrentContext(_providerOrganizationRepository);
await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null)
{
@ -35,7 +39,7 @@ namespace Bit.Notifications
public override async Task OnDisconnectedAsync(Exception exception)
{
var currentContext = new CurrentContext();
var currentContext = new CurrentContext(_providerOrganizationRepository);
await currentContext.BuildAsync(Context.User, _globalSettings);
if (currentContext.Organizations != null)
{

View File

@ -70,6 +70,8 @@
</ItemGroup>
<ItemGroup>
<Build Include="dbo\Stored Procedures\EmergencyAccessDetails_ReadByIdGrantorId.sql" />
<Build Include="dbo\Stored Procedures\ProviderOrganization_ReadByUserId.sql" />
<Build Include="dbo\Stored Procedures\ProviderUserProviderOrganizationDetails_ReadByUserIdStatus.sql" />
<Build Include="dbo\Stored Procedures\SsoConfig_Create.sql" />
<Build Include="dbo\Stored Procedures\SsoConfig_ReadByIdentifier.sql" />
<Build Include="dbo\Stored Procedures\SsoConfig_ReadByOrganizationId.sql" />
@ -93,6 +95,7 @@
<Build Include="dbo\Views\GrantView.sql" />
<Build Include="dbo\Views\ProviderOrganizationOrganizationDetailsView.sql" />
<Build Include="dbo\Views\ProviderUserProviderDetailsView.sql" />
<Build Include="dbo\Views\ProviderUserProviderOrganizationDetailsView.sql" />
<Build Include="dbo\Views\SsoConfigView.sql" />
<Build Include="dbo\Views\UserView.sql" />
<Build Include="dbo\Views\U2fView.sql" />

View File

@ -0,0 +1,17 @@
CREATE PROCEDURE [dbo].[ProviderOrganization_ReadByUserId]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
PO.*
FROM
[dbo].[ProviderOrganizationView] PO
INNER JOIN
[dbo].[Provider] P ON PO.[ProviderId] = P.[Id]
INNER JOIN
[dbo].[ProviderUser] PU ON P.[Id] = PU.[ProviderId]
WHERE
PU.[UserId] = @UserId
END

View File

@ -0,0 +1,15 @@
CREATE PROCEDURE [dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]
@UserId UNIQUEIDENTIFIER,
@Status TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderUserProviderOrganizationDetailsView]
WHERE
[UserId] = @UserId
AND (@Status IS NULL OR [Status] = @Status)
END

View File

@ -8,7 +8,8 @@ SELECT
PU.[Status],
PU.[Type],
P.[Enabled],
PU.[Permissions]
PU.[Permissions],
P.[UseEvents]
FROM
[dbo].[ProviderUser] PU
LEFT JOIN

View File

@ -0,0 +1,37 @@
CREATE VIEW [dbo].[ProviderUserProviderOrganizationDetailsView]
AS
SELECT
PU.[UserId],
PO.[OrganizationId],
O.[Name],
O.[Enabled],
O.[UsePolicies],
O.[UseSso],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[UseResetPassword],
O.[SelfHost],
O.[UsersGetPremium],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
PO.[Key],
O.[PublicKey],
O.[PrivateKey],
PU.[Status],
PU.[Type],
PO.[ProviderId],
P.[Name] ProviderName
FROM
[dbo].[ProviderUser] PU
INNER JOIN
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
INNER JOIN
[dbo].[Provider] P ON P.[Id] = PU.[ProviderId]

View File

@ -23,7 +23,7 @@ namespace Bit.Core.Test.Services
_eventWriteService = Substitute.For<IEventWriteService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_applicationCacheService = Substitute.For<IApplicationCacheService>();
_currentContext = new CurrentContext();
_currentContext = new CurrentContext(null);
_globalSettings = new GlobalSettings();
_sut = new EventService(

View File

@ -188,6 +188,21 @@ namespace Bit.Core.Test.Services
await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
}
[Theory]
[OrganizationInviteAutoData(
inviteeUserType: (int)OrganizationUserType.Admin,
invitorUserType: (int)OrganizationUserType.Owner
)]
public async Task InviteUser_NoOwner_Throws(Organization organization, OrganizationUser invitor,
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory]
[OrganizationInviteAutoData(
@ -288,10 +303,14 @@ namespace Bit.Core.Test.Services
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invite.Permissions = null;
invitor.Status = OrganizationUserStatusType.Confirmed;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new [] {invitor});
currentContext.OrganizationOwner(organization.Id).Returns(true);
await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite);
@ -303,7 +322,9 @@ namespace Bit.Core.Test.Services
invitorUserType: (int)OrganizationUserType.Custom
)]
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite,
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)]OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
new JsonSerializerOptions
@ -312,9 +333,12 @@ namespace Bit.Core.Test.Services
});
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new [] {owner});
currentContext.ManageUsers(organization.Id).Returns(true);
await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite);

View File

@ -72,7 +72,7 @@ namespace Bit.Core.Test.Services
_policyRepository = Substitute.For<IPolicyRepository>();
_referenceEventService = Substitute.For<IReferenceEventService>();
_fido2 = Substitute.For<IFido2>();
_currentContext = new CurrentContext();
_currentContext = new CurrentContext(null);
_globalSettings = new GlobalSettings();
_organizationService = Substitute.For<IOrganizationService>();
_sendRepository = Substitute.For<ISendRepository>();

View File

@ -1097,7 +1097,8 @@ SELECT
PU.[Status],
PU.[Type],
P.[Enabled],
PU.[Permissions]
PU.[Permissions],
P.[UseEvents]
FROM
[dbo].[ProviderUser] PU
LEFT JOIN
@ -1122,8 +1123,8 @@ BEGIN
FROM
[dbo].[ProviderUserProviderDetailsView]
WHERE
[UserId] = @UserId
AND (@Status IS NULL OR [Status] = @Status)
[UserId] = @UserId
AND (@Status IS NULL OR [Status] = @Status)
END
GO
@ -1266,3 +1267,96 @@ LEFT JOIN
[dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
LEFT JOIN
[dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
GO
IF OBJECT_ID('[dbo].[ProviderOrganization_ReadByUserId]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderOrganization_ReadByUserId]
END
GO
CREATE PROCEDURE [dbo].[ProviderOrganization_ReadByUserId]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
PO.*
FROM
[dbo].[ProviderOrganizationView] PO
INNER JOIN
[dbo].[Provider] P ON PO.[ProviderId] = P.[Id]
INNER JOIN
[dbo].[ProviderUser] PU ON P.[Id] = PU.[ProviderId]
WHERE
PU.[UserId] = @UserId
END
GO
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'ProviderUserProviderOrganizationDetailsView')
BEGIN
DROP VIEW [dbo].[ProviderUserProviderOrganizationDetailsView];
END
GO
CREATE VIEW [dbo].[ProviderUserProviderOrganizationDetailsView]
AS
SELECT
PU.[UserId],
PO.[OrganizationId],
O.[Name],
O.[Enabled],
O.[UsePolicies],
O.[UseSso],
O.[UseGroups],
O.[UseDirectory],
O.[UseEvents],
O.[UseTotp],
O.[Use2fa],
O.[UseApi],
O.[UseResetPassword],
O.[SelfHost],
O.[UsersGetPremium],
O.[Seats],
O.[MaxCollections],
O.[MaxStorageGb],
O.[Identifier],
PO.[Key],
O.[PublicKey],
O.[PrivateKey],
PU.[Status],
PU.[Type],
PO.[ProviderId],
P.[Name] ProviderName
FROM
[dbo].[ProviderUser] PU
INNER JOIN
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
INNER JOIN
[dbo].[Provider] P ON P.[Id] = PU.[ProviderId]
GO
IF OBJECT_ID('[dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]
END
GO
CREATE PROCEDURE [dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]
@UserId UNIQUEIDENTIFIER,
@Status TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[ProviderUserProviderOrganizationDetailsView]
WHERE
[UserId] = @UserId
AND (@Status IS NULL OR [Status] = @Status)
END