1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 17:12:49 -05:00

Merge remote-tracking branch 'origin/master' into feature/flexible-collections

This commit is contained in:
Thomas Rittson
2023-10-23 08:47:11 +10:00
196 changed files with 1219 additions and 817 deletions

View File

@ -1,4 +1,5 @@
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.AdminConsole.Models.Response;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Accounts;
using Bit.Api.Models.Response;

View File

@ -1,4 +1,5 @@
using Bit.Api.Models.Response;
using Bit.Api.Utilities;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
@ -41,7 +42,7 @@ public class EventsController : Controller
public async Task<ListResponseModel<EventResponseModel>> GetUser(
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null)
{
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var userId = _userService.GetProperUserId(User).Value;
var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
@ -75,7 +76,7 @@ public class EventsController : Controller
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
@ -92,7 +93,7 @@ public class EventsController : Controller
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
@ -110,7 +111,7 @@ public class EventsController : Controller
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId,
organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
@ -127,7 +128,7 @@ public class EventsController : Controller
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
@ -145,33 +146,11 @@ public class EventsController : Controller
throw new NotFoundException();
}
var dateRange = GetDateRange(start, end);
var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId,
providerUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
private Tuple<DateTime, DateTime> GetDateRange(DateTime? start, DateTime? end)
{
if (!end.HasValue || !start.HasValue)
{
end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);
start = DateTime.UtcNow.Date.AddDays(-30);
}
else if (start.Value > end.Value)
{
var newEnd = start;
start = end;
end = newEnd;
}
if ((end.Value - start.Value) > TimeSpan.FromDays(367))
{
throw new BadRequestException("Range too large.");
}
return new Tuple<DateTime, DateTime>(start.Value, end.Value);
}
}

View File

@ -1,186 +0,0 @@
using Bit.Api.Models.Request;
using Bit.Api.Models.Response;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/groups")]
[Authorize("Application")]
public class GroupsController : Controller
{
private readonly IGroupRepository _groupRepository;
private readonly IGroupService _groupService;
private readonly IDeleteGroupCommand _deleteGroupCommand;
private readonly IOrganizationRepository _organizationRepository;
private readonly ICurrentContext _currentContext;
private readonly ICreateGroupCommand _createGroupCommand;
private readonly IUpdateGroupCommand _updateGroupCommand;
public GroupsController(
IGroupRepository groupRepository,
IGroupService groupService,
IOrganizationRepository organizationRepository,
ICurrentContext currentContext,
ICreateGroupCommand createGroupCommand,
IUpdateGroupCommand updateGroupCommand,
IDeleteGroupCommand deleteGroupCommand)
{
_groupRepository = groupRepository;
_groupService = groupService;
_organizationRepository = organizationRepository;
_currentContext = currentContext;
_createGroupCommand = createGroupCommand;
_updateGroupCommand = updateGroupCommand;
_deleteGroupCommand = deleteGroupCommand;
}
[HttpGet("{id}")]
public async Task<GroupResponseModel> Get(string orgId, string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
return new GroupResponseModel(group);
}
[HttpGet("{id}/details")]
public async Task<GroupDetailsResponseModel> GetDetails(string orgId, string id)
{
var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(new Guid(id));
if (groupDetails?.Item1 == null || !await _currentContext.ManageGroups(groupDetails.Item1.OrganizationId))
{
throw new NotFoundException();
}
return new GroupDetailsResponseModel(groupDetails.Item1, groupDetails.Item2);
}
[HttpGet("")]
public async Task<ListResponseModel<GroupDetailsResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
await _currentContext.ViewAssignedCollections(orgIdGuid) ||
await _currentContext.ViewAllCollections(orgIdGuid) ||
await _currentContext.ManageUsers(orgIdGuid);
if (!canAccess)
{
throw new NotFoundException();
}
var groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgIdGuid);
var responses = groups.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2));
return new ListResponseModel<GroupDetailsResponseModel>(responses);
}
[HttpGet("{id}/users")]
public async Task<IEnumerable<Guid>> GetUsers(string orgId, string id)
{
var idGuid = new Guid(id);
var group = await _groupRepository.GetByIdAsync(idGuid);
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
var groupIds = await _groupRepository.GetManyUserIdsByIdAsync(idGuid);
return groupIds;
}
[HttpPost("")]
public async Task<GroupResponseModel> Post(string orgId, [FromBody] GroupRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManageGroups(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
var group = model.ToGroup(orgIdGuid);
await _createGroupCommand.CreateGroupAsync(group, organization, model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Users);
return new GroupResponseModel(group);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<GroupResponseModel> Put(string orgId, string id, [FromBody] GroupRequestModel model)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
var orgIdGuid = new Guid(orgId);
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
await _updateGroupCommand.UpdateGroupAsync(model.ToGroup(group), organization, model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Users);
return new GroupResponseModel(group);
}
[HttpPut("{id}/users")]
public async Task PutUsers(string orgId, string id, [FromBody] IEnumerable<Guid> model)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _groupRepository.UpdateUsersAsync(group.Id, model);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _deleteGroupCommand.DeleteAsync(group);
}
[HttpDelete("")]
[HttpPost("delete")]
public async Task BulkDelete([FromBody] GroupBulkRequestModel model)
{
var groups = await _groupRepository.GetManyByManyIds(model.Ids);
foreach (var group in groups)
{
if (!await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
}
await _deleteGroupCommand.DeleteManyAsync(groups);
}
[HttpDelete("{id}/user/{orgUserId}")]
[HttpPost("{id}/delete-user/{orgUserId}")]
public async Task Delete(string orgId, string id, string orgUserId)
{
var group = await _groupRepository.GetByIdAsync(new Guid(id));
if (group == null || !await _currentContext.ManageGroups(group.OrganizationId))
{
throw new NotFoundException();
}
await _groupService.DeleteUserAsync(group, new Guid(orgUserId));
}
}

View File

@ -78,7 +78,12 @@ public class OrganizationConnectionsController : Controller
[HttpPut("{organizationConnectionId}")]
public async Task<OrganizationConnectionResponseModel> UpdateConnection(Guid organizationConnectionId, [FromBody] OrganizationConnectionRequestModel model)
{
var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId);
if (model == null)
{
throw new NotFoundException();
}
var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdOrganizationIdAsync(organizationConnectionId, model.OrganizationId);
if (existingOrganizationConnection == null)
{
throw new NotFoundException();

View File

@ -19,7 +19,7 @@ public class OrganizationDomainController : Controller
private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand;
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand;
private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery;
private readonly IGetOrganizationDomainByIdOrganizationIdQuery _getOrganizationDomainByIdAndOrganizationIdQuery;
private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
@ -29,7 +29,7 @@ public class OrganizationDomainController : Controller
ICreateOrganizationDomainCommand createOrganizationDomainCommand,
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand,
IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery,
IGetOrganizationDomainByIdOrganizationIdQuery getOrganizationDomainByIdAndOrganizationIdQuery,
IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery,
ICurrentContext currentContext,
IOrganizationRepository organizationRepository,
@ -38,7 +38,7 @@ public class OrganizationDomainController : Controller
_createOrganizationDomainCommand = createOrganizationDomainCommand;
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
_deleteOrganizationDomainCommand = deleteOrganizationDomainCommand;
_getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery;
_getOrganizationDomainByIdAndOrganizationIdQuery = getOrganizationDomainByIdAndOrganizationIdQuery;
_getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery;
_currentContext = currentContext;
_organizationRepository = organizationRepository;
@ -46,71 +46,78 @@ public class OrganizationDomainController : Controller
}
[HttpGet("{orgId}/domain")]
public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(string orgId)
public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(Guid orgId)
{
var orgIdGuid = new Guid(orgId);
await ValidateOrganizationAccessAsync(orgIdGuid);
await ValidateOrganizationAccessAsync(orgId);
var domains = await _getOrganizationDomainByOrganizationIdQuery
.GetDomainsByOrganizationId(orgIdGuid);
.GetDomainsByOrganizationIdAsync(orgId);
var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList();
return new ListResponseModel<OrganizationDomainResponseModel>(response);
}
[HttpGet("{orgId}/domain/{id}")]
public async Task<OrganizationDomainResponseModel> Get(string orgId, string id)
public async Task<OrganizationDomainResponseModel> Get(Guid orgId, Guid id)
{
var orgIdGuid = new Guid(orgId);
var IdGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
await ValidateOrganizationAccessAsync(orgId);
var domain = await _getOrganizationDomainByIdQuery.GetOrganizationDomainById(IdGuid);
var organizationDomain = await _getOrganizationDomainByIdAndOrganizationIdQuery
.GetOrganizationDomainByIdOrganizationIdAsync(id, orgId);
if (organizationDomain is null)
{
throw new NotFoundException();
}
return new OrganizationDomainResponseModel(organizationDomain);
}
[HttpPost("{orgId}/domain")]
public async Task<OrganizationDomainResponseModel> Post(Guid orgId,
[FromBody] OrganizationDomainRequestModel model)
{
await ValidateOrganizationAccessAsync(orgId);
var organizationDomain = new OrganizationDomain
{
OrganizationId = orgId,
Txt = model.Txt,
DomainName = model.DomainName.ToLower()
};
organizationDomain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain);
return new OrganizationDomainResponseModel(organizationDomain);
}
[HttpPost("{orgId}/domain/{id}/verify")]
public async Task<OrganizationDomainResponseModel> Verify(Guid orgId, Guid id)
{
await ValidateOrganizationAccessAsync(orgId);
var organizationDomain = await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, orgId);
if (organizationDomain is null)
{
throw new NotFoundException();
}
organizationDomain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomainAsync(organizationDomain);
return new OrganizationDomainResponseModel(organizationDomain);
}
[HttpDelete("{orgId}/domain/{id}")]
[HttpPost("{orgId}/domain/{id}/remove")]
public async Task RemoveDomain(Guid orgId, Guid id)
{
await ValidateOrganizationAccessAsync(orgId);
var domain = await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, orgId);
if (domain is null)
{
throw new NotFoundException();
}
return new OrganizationDomainResponseModel(domain);
}
[HttpPost("{orgId}/domain")]
public async Task<OrganizationDomainResponseModel> Post(string orgId,
[FromBody] OrganizationDomainRequestModel model)
{
var orgIdGuid = new Guid(orgId);
await ValidateOrganizationAccessAsync(orgIdGuid);
var organizationDomain = new OrganizationDomain
{
OrganizationId = orgIdGuid,
Txt = model.Txt,
DomainName = model.DomainName.ToLower()
};
var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain);
return new OrganizationDomainResponseModel(domain);
}
[HttpPost("{orgId}/domain/{id}/verify")]
public async Task<OrganizationDomainResponseModel> Verify(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
return new OrganizationDomainResponseModel(domain);
}
[HttpDelete("{orgId}/domain/{id}")]
[HttpPost("{orgId}/domain/{id}/remove")]
public async Task RemoveDomain(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
await _deleteOrganizationDomainCommand.DeleteAsync(idGuid);
await _deleteOrganizationDomainCommand.DeleteAsync(domain);
}
[AllowAnonymous]

View File

@ -1,501 +0,0 @@
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/users")]
[Authorize("Application")]
public class OrganizationUsersController : Controller
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository;
private readonly ICurrentContext _currentContext;
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
IPolicyRepository policyRepository,
ICurrentContext currentContext,
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_userService = userService;
_policyRepository = policyRepository;
_currentContext = currentContext;
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
}
[HttpGet("{id}")]
public async Task<OrganizationUserDetailsResponseModel> Get(string id, bool includeGroups = false)
{
var organizationUser = await _organizationUserRepository.GetDetailsByIdWithCollectionsAsync(new Guid(id));
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
{
throw new NotFoundException();
}
var response = new OrganizationUserDetailsResponseModel(organizationUser.Item1, organizationUser.Item2);
if (includeGroups)
{
response.Groups = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Item1.Id);
}
return response;
}
[HttpGet("")]
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId, bool includeGroups = false, bool includeCollections = false)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ViewAllCollections(orgGuidId) &&
!await _currentContext.ViewAssignedCollections(orgGuidId) &&
!await _currentContext.ManageGroups(orgGuidId) &&
!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId, includeGroups, includeCollections);
var responseTasks = organizationUsers.Select(async o => new OrganizationUserUserDetailsResponseModel(o,
await _userService.TwoFactorIsEnabledAsync(o)));
var responses = await Task.WhenAll(responseTasks);
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
}
[HttpGet("{id}/groups")]
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || (!await _currentContext.ManageGroups(organizationUser.OrganizationId) &&
!await _currentContext.ManageUsers(organizationUser.OrganizationId)))
{
throw new NotFoundException();
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
var responses = groupIds.Select(g => g.ToString());
return responses;
}
[HttpGet("{id}/reset-password-details")]
public async Task<OrganizationUserResetPasswordDetailsResponseModel> GetResetPasswordDetails(string orgId, string id)
{
// Make sure the calling user can reset passwords for this org
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || !organizationUser.UserId.HasValue)
{
throw new NotFoundException();
}
// Retrieve data necessary for response (KDF, KDF Iterations, ResetPasswordKey)
// TODO Reset Password - Revisit this and create SPROC to reduce DB calls
var user = await _userService.GetUserByIdAsync(organizationUser.UserId.Value);
if (user == null)
{
throw new NotFoundException();
}
// Retrieve Encrypted Private Key from organization
var org = await _organizationRepository.GetByIdAsync(orgGuidId);
if (org == null)
{
throw new NotFoundException();
}
return new OrganizationUserResetPasswordDetailsResponseModel(new OrganizationUserResetPasswordDetails(organizationUser, user, org));
}
[HttpPost("invite")]
public async Task Invite(string orgId, [FromBody] OrganizationUserInviteRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.InviteUsersAsync(orgGuidId, userId.Value,
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
}
[HttpPost("reinvite")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ResendInvitesAsync(orgGuidId, userId.Value, model.Ids);
return new ListResponseModel<OrganizationUserBulkResponseModel>(
result.Select(t => new OrganizationUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
[HttpPost("{id}/reinvite")]
public async Task Reinvite(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id));
}
[HttpPost("{organizationUserId}/accept-init")]
public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
await _organizationService.InitPendingOrganization(user.Id, orgId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id, _userService);
}
[HttpPost("{organizationUserId}/accept")]
public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
masterPasswordPolicy.Enabled &&
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey))
{
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
}
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
if (useMasterPasswordPolicy)
{
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
}
[HttpPost("{id}/confirm")]
public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value,
_userService);
}
[HttpPost("confirm")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(string orgId,
[FromBody] OrganizationUserBulkConfirmRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value,
_userService);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[HttpPost("public-keys")]
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var result = await _organizationUserRepository.GetManyPublicKeysByOrganizationUserAsync(orgGuidId, model.Ids);
var responses = result.Select(r => new OrganizationUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<OrganizationUserPublicKeyResponseModel>(responses);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task Put(string orgId, string id, [FromBody] OrganizationUserUpdateRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Groups);
}
[HttpPut("{id}/groups")]
[HttpPost("{id}/groups")]
public async Task PutGroups(string orgId, string id, [FromBody] OrganizationUserUpdateGroupsRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var loggedInUserId = _userService.GetProperUserId(User);
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
}
[HttpPut("{userId}/reset-password-enrollment")]
public async Task PutResetPasswordEnrollment(Guid orgId, Guid userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var callingUserId = user.Id;
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(
orgId, userId, model.ResetPasswordKey, callingUserId);
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, user.Id);
if (orgUser.Status == OrganizationUserStatusType.Invited)
{
await _organizationService.AcceptUserAsync(orgId, user, _userService);
}
}
[HttpPut("{id}/reset-password")]
public async Task PutResetPassword(string orgId, string id, [FromBody] OrganizationUserResetPasswordRequestModel model)
{
var orgGuidId = new Guid(orgId);
// Calling user must have Manage Reset Password permission
if (!await _currentContext.ManageResetPassword(orgGuidId))
{
throw new NotFoundException();
}
// Get the users role, since provider users aren't a member of the organization we use the owner check
var orgUserType = await _currentContext.OrganizationOwner(orgGuidId)
? OrganizationUserType.Owner
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgGuidId)?.Type;
if (orgUserType == null)
{
throw new NotFoundException();
}
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgGuidId, new Guid(id), model.NewMasterPasswordHash, model.Key);
if (result.Succeeded)
{
return;
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
await Task.Delay(2000);
throw new BadRequestException(ModelState);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string orgId, string id)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.DeleteUserAsync(orgGuidId, new Guid(id), userId.Value);
}
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDelete(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.DeleteUsersAsync(orgGuidId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[HttpPatch("{id}/revoke")]
[HttpPut("{id}/revoke")]
public async Task RevokeAsync(Guid orgId, Guid id)
{
await RestoreOrRevokeUserAsync(orgId, id, _organizationService.RevokeUserAsync);
}
[HttpPatch("revoke")]
[HttpPut("revoke")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await RestoreOrRevokeUsersAsync(orgId, model, _organizationService.RevokeUsersAsync);
}
[HttpPatch("{id}/restore")]
[HttpPut("{id}/restore")]
public async Task RestoreAsync(Guid orgId, Guid id)
{
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _organizationService.RestoreUserAsync(orgUser, userId, _userService));
}
[HttpPatch("restore")]
[HttpPut("restore")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRestoreAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
}
[HttpPatch("enable-secrets-manager")]
[HttpPut("enable-secrets-manager")]
public async Task BulkEnableSecretsManagerAsync(Guid orgId,
[FromBody] OrganizationUserBulkRequestModel model)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var orgUsers = (await _organizationUserRepository.GetManyAsync(model.Ids))
.Where(ou => ou.OrganizationId == orgId && !ou.AccessSecretsManager).ToList();
if (orgUsers.Count == 0)
{
throw new BadRequestException("Users invalid.");
}
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(orgId,
orgUsers.Count);
if (additionalSmSeatsRequired > 0)
{
var organization = await _organizationRepository.GetByIdAsync(orgId);
var update = new SecretsManagerSubscriptionUpdate(organization, true)
.AdjustSeats(additionalSmSeatsRequired);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
}
foreach (var orgUser in orgUsers)
{
orgUser.AccessSecretsManager = true;
}
await _organizationUserRepository.ReplaceManyAsync(orgUsers);
}
private async Task RestoreOrRevokeUserAsync(
Guid orgId,
Guid id,
Func<Core.Entities.OrganizationUser, Guid?, Task> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != orgId)
{
throw new NotFoundException();
}
await statusAction(orgUser, userId);
}
private async Task<ListResponseModel<OrganizationUserBulkResponseModel>> RestoreOrRevokeUsersAsync(
Guid orgId,
OrganizationUserBulkRequestModel model,
Func<Guid, IEnumerable<Guid>, Guid?, Task<List<Tuple<Core.Entities.OrganizationUser, string>>>> statusAction)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await statusAction(orgId, model.Ids, userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
}

View File

@ -1,785 +0,0 @@
using System.Text.Json;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.Auth.Models.Request.Organizations;
using Bit.Api.Auth.Models.Response.Organizations;
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Accounts;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Core;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.Services;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations")]
[Authorize("Application")]
public class OrganizationsController : Controller
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPolicyRepository _policyRepository;
private readonly IProviderRepository _providerRepository;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly IPaymentService _paymentService;
private readonly ICurrentContext _currentContext;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IGetOrganizationApiKeyQuery _getOrganizationApiKeyQuery;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IUpgradeOrganizationPlanCommand _upgradeOrganizationPlanCommand;
private readonly IAddSecretsManagerSubscriptionCommand _addSecretsManagerSubscriptionCommand;
public OrganizationsController(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository,
IProviderRepository providerRepository,
IOrganizationService organizationService,
IUserService userService,
IPaymentService paymentService,
ICurrentContext currentContext,
ISsoConfigRepository ssoConfigRepository,
ISsoConfigService ssoConfigService,
IGetOrganizationApiKeyQuery getOrganizationApiKeyQuery,
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository,
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IFeatureService featureService,
GlobalSettings globalSettings,
ILicensingService licensingService,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand,
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_policyRepository = policyRepository;
_providerRepository = providerRepository;
_organizationService = organizationService;
_userService = userService;
_paymentService = paymentService;
_currentContext = currentContext;
_ssoConfigRepository = ssoConfigRepository;
_ssoConfigService = ssoConfigService;
_getOrganizationApiKeyQuery = getOrganizationApiKeyQuery;
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository;
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_featureService = featureService;
_globalSettings = globalSettings;
_licensingService = licensingService;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_upgradeOrganizationPlanCommand = upgradeOrganizationPlanCommand;
_addSecretsManagerSubscriptionCommand = addSecretsManagerSubscriptionCommand;
}
[HttpGet("{id}")]
public async Task<OrganizationResponseModel> Get(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
return new OrganizationResponseModel(organization);
}
[HttpGet("{id}/billing")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<BillingResponseModel> GetBilling(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ViewBillingHistory(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var billingInfo = await _paymentService.GetBillingAsync(organization);
return new BillingResponseModel(billingInfo);
}
[HttpGet("{id}/subscription")]
public async Task<OrganizationSubscriptionResponseModel> GetSubscription(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.ViewSubscription(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
if (!_globalSettings.SelfHosted && organization.Gateway != null)
{
var subscriptionInfo = await _paymentService.GetSubscriptionAsync(organization);
if (subscriptionInfo == null)
{
throw new NotFoundException();
}
var hideSensitiveData = !await _currentContext.EditSubscription(orgIdGuid);
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
}
if (_globalSettings.SelfHosted)
{
var orgLicense = await _licensingService.ReadOrganizationLicenseAsync(organization);
return new OrganizationSubscriptionResponseModel(organization, orgLicense);
}
return new OrganizationSubscriptionResponseModel(organization);
}
[HttpGet("{id}/license")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<OrganizationLicense> GetLicense(string id, [FromQuery] Guid installationId)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(org, installationId);
if (license == null)
{
throw new NotFoundException();
}
return license;
}
[HttpGet("")]
public async Task<ListResponseModel<ProfileOrganizationResponseModel>> GetUser()
{
var userId = _userService.GetProperUserId(User).Value;
var organizations = await _organizationUserRepository.GetManyDetailsByUserAsync(userId,
OrganizationUserStatusType.Confirmed);
var responses = organizations.Select(o => new ProfileOrganizationResponseModel(o));
return new ListResponseModel<ProfileOrganizationResponseModel>(responses);
}
[HttpGet("{identifier}/auto-enroll-status")]
public async Task<OrganizationAutoEnrollStatusResponseModel> GetAutoEnrollStatus(string identifier)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var organization = await _organizationRepository.GetByIdentifierAsync(identifier);
if (organization == null)
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organization.Id, user.Id);
if (organizationUser == null)
{
throw new NotFoundException();
}
var resetPasswordPolicy =
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled || resetPasswordPolicy.Data == null)
{
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false);
}
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false);
}
[HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<OrganizationResponseModel> Post([FromBody] OrganizationCreateRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var organizationSignup = model.ToOrganizationSignup(user);
var result = await _organizationService.SignUpAsync(organizationSignup);
return new OrganizationResponseModel(result.Item1);
}
[HttpPut("{id}")]
[HttpPost("{id}")]
public async Task<OrganizationResponseModel> Put(string id, [FromBody] OrganizationUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.BusinessName ||
model.BillingEmail != organization.BillingEmail);
var hasRequiredPermissions = updateBilling
? await _currentContext.EditSubscription(orgIdGuid)
: await _currentContext.OrganizationOwner(orgIdGuid);
if (!hasRequiredPermissions)
{
throw new NotFoundException();
}
await _organizationService.UpdateAsync(model.ToOrganization(organization, _globalSettings), updateBilling);
return new OrganizationResponseModel(organization);
}
[HttpPost("{id}/payment")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostPayment(string id, [FromBody] PaymentRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditPaymentMethods(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.ReplacePaymentMethodAsync(orgIdGuid, model.PaymentToken,
model.PaymentMethodType.Value, new TaxInfo
{
BillingAddressLine1 = model.Line1,
BillingAddressLine2 = model.Line2,
BillingAddressState = model.State,
BillingAddressCity = model.City,
BillingAddressPostalCode = model.PostalCode,
BillingAddressCountry = model.Country,
TaxIdNumber = model.TaxId,
});
}
[HttpPost("{id}/upgrade")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] OrganizationUpgradeRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
var result = await _upgradeOrganizationPlanCommand.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 };
}
[HttpPost("{id}/subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptionUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.UpdateSubscription(orgIdGuid, model.SeatAdjustment, model.MaxAutoscaleSeats);
}
[HttpPost("{id}/sm-subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostSmSubscription(Guid id, [FromBody] SecretsManagerSubscriptionUpdateRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.EditSubscription(id))
{
throw new NotFoundException();
}
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(organizationUpdate);
}
[HttpPost("{id}/subscribe-secrets-manager")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<ProfileOrganizationResponseModel> PostSubscribeSecretsManagerAsync(Guid id, [FromBody] SecretsManagerSubscribeRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.EditSubscription(id))
{
throw new NotFoundException();
}
await _addSecretsManagerSubscriptionCommand.SignUpAsync(organization, model.AdditionalSmSeats,
model.AdditionalServiceAccounts);
var userId = _userService.GetProperUserId(User).Value;
var organizationDetails = await _organizationUserRepository.GetDetailsByUserAsync(userId, organization.Id,
OrganizationUserStatusType.Confirmed);
return new ProfileOrganizationResponseModel(organizationDetails);
}
[HttpPost("{id}/seat")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
var result = await _organizationService.AdjustSeatsAsync(orgIdGuid, model.SeatAdjustment.Value);
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
}
[HttpPost("{id}/storage")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody] StorageRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
var result = await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value);
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
}
[HttpPost("{id}/verify-bank")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostVerifyBank(string id, [FromBody] OrganizationVerifyBankRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.VerifyBankAsync(orgIdGuid, model.Amount1.Value, model.Amount2.Value);
}
[HttpPost("{id}/cancel")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostCancel(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.CancelSubscriptionAsync(orgIdGuid);
}
[HttpPost("{id}/reinstate")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostReinstate(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.EditSubscription(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.ReinstateSubscriptionAsync(orgIdGuid);
}
[HttpPost("{id}/leave")]
public async Task Leave(string id)
{
var orgGuidId = new Guid(id);
if (!await _currentContext.OrganizationUser(orgGuidId))
{
throw new NotFoundException();
}
var user = await _userService.GetUserByPrincipalAsync(User);
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgGuidId);
if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector && user.UsesKeyConnector)
{
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
}
await _organizationService.DeleteUserAsync(orgGuidId, user.Id);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id, [FromBody] SecretVerificationRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "User verification failed.");
}
else
{
await _organizationService.DeleteAsync(organization);
}
}
[HttpPost("{id}/import")]
public async Task Import(string id, [FromBody] ImportOrganizationUsersRequestModel model)
{
if (!_globalSettings.SelfHosted && !model.LargeImport &&
(model.Groups.Count() > 2000 || model.Users.Count(u => !u.Deleted) > 2000))
{
throw new BadRequestException("You cannot import this much data at once.");
}
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.ImportAsync(
orgIdGuid,
userId.Value,
model.Groups.Select(g => g.ToImportedGroup(orgIdGuid)),
model.Users.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()),
model.Users.Where(u => u.Deleted).Select(u => u.ExternalId),
model.OverwriteExisting);
}
[HttpPost("{id}/api-key")]
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await HasApiKeyAccessAsync(orgIdGuid, model.Type))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim)
{
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types
var plan = StaticStore.GetPlan(organization.PlanType);
if (plan.Product != ProductType.Enterprise)
{
throw new NotFoundException();
}
}
var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (model.Type != OrganizationApiKeyType.Scim
&& !await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
}
else
{
var response = new ApiKeyResponseModel(organizationApiKey);
return response;
}
}
[HttpGet("{id}/api-key-information/{type?}")]
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id,
[FromRoute] OrganizationApiKeyType? type)
{
if (!await HasApiKeyAccessAsync(id, type))
{
throw new NotFoundException();
}
var apiKeys = await _organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(id, type);
return new ListResponseModel<OrganizationApiKeyInformation>(
apiKeys.Select(k => new OrganizationApiKeyInformation(k)));
}
[HttpPost("{id}/rotate-api-key")]
public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await HasApiKeyAccessAsync(orgIdGuid, model.Type))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (model.Type != OrganizationApiKeyType.Scim
&& !await _userService.VerifySecretAsync(user, model.Secret))
{
await Task.Delay(2000);
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
}
else
{
await _rotateOrganizationApiKeyCommand.RotateApiKeyAsync(organizationApiKey);
var response = new ApiKeyResponseModel(organizationApiKey);
return response;
}
}
private async Task<bool> HasApiKeyAccessAsync(Guid orgId, OrganizationApiKeyType? type)
{
return type switch
{
OrganizationApiKeyType.Scim => await _currentContext.ManageScim(orgId),
_ => await _currentContext.OrganizationOwner(orgId),
};
}
[HttpGet("{id}/tax")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<TaxInfoResponseModel> GetTaxInfo(string id)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var taxInfo = await _paymentService.GetTaxInfoAsync(organization);
return new TaxInfoResponseModel(taxInfo);
}
[HttpPut("{id}/tax")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutTaxInfo(string id, [FromBody] OrganizationTaxInfoUpdateRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var taxInfo = new TaxInfo
{
TaxIdNumber = model.TaxId,
BillingAddressLine1 = model.Line1,
BillingAddressLine2 = model.Line2,
BillingAddressCity = model.City,
BillingAddressState = model.State,
BillingAddressPostalCode = model.PostalCode,
BillingAddressCountry = model.Country,
};
await _paymentService.SaveTaxInfoAsync(organization, taxInfo);
}
[HttpGet("{id}/public-key")]
public async Task<OrganizationPublicKeyResponseModel> GetPublicKey(string id)
{
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
if (org == null)
{
throw new NotFoundException();
}
return new OrganizationPublicKeyResponseModel(org);
}
[Obsolete("TDL-136 Renamed to public-key (2023.8), left for backwards compatability with older clients.")]
[HttpGet("{id}/keys")]
public async Task<OrganizationPublicKeyResponseModel> GetKeys(string id)
{
return await GetPublicKey(id);
}
[HttpPost("{id}/keys")]
public async Task<OrganizationKeysResponseModel> PostKeys(string id, [FromBody] OrganizationKeysRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
model.EncryptedPrivateKey);
return new OrganizationKeysResponseModel(org);
}
[HttpGet("{id:guid}/sso")]
public async Task<OrganizationSsoResponseModel> GetSso(Guid id)
{
if (!await _currentContext.ManageSso(id))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(id);
return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig);
}
[HttpPost("{id:guid}/sso")]
public async Task<OrganizationSsoResponseModel> PostSso(Guid id, [FromBody] OrganizationSsoRequestModel model)
{
if (!await _currentContext.ManageSso(id))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (model.Data.MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption &&
!_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext))
{
throw new BadRequestException(nameof(model.Data.MemberDecryptionType), "Invalid member decryption type.");
}
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(id);
ssoConfig = ssoConfig == null ? model.ToSsoConfig(id) : model.ToSsoConfig(ssoConfig);
organization.Identifier = model.Identifier;
await _ssoConfigService.SaveAsync(ssoConfig, organization);
await _organizationService.UpdateAsync(organization);
return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig);
}
[HttpPut("{id}/collection-management")]
[RequireFeature(FeatureFlagKeys.FlexibleCollections)]
public async Task<OrganizationResponseModel> PutCollectionManagement(Guid id, [FromBody] OrganizationCollectionManagementUpdateRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationOwner(id))
{
throw new NotFoundException();
}
await _organizationService.UpdateAsync(model.ToOrganization(organization));
return new OrganizationResponseModel(organization);
}
}

View File

@ -1,155 +0,0 @@
using Bit.Api.Models.Request;
using Bit.Api.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Response;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations/{orgId}/policies")]
[Authorize("Application")]
public class PoliciesController : Controller
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IDataProtector _organizationServiceDataProtector;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IOrganizationService organizationService,
IOrganizationUserRepository organizationUserRepository,
IUserService userService,
ICurrentContext currentContext,
GlobalSettings globalSettings,
IDataProtectionProvider dataProtectionProvider)
{
_policyRepository = policyRepository;
_policyService = policyService;
_organizationService = organizationService;
_organizationUserRepository = organizationUserRepository;
_userService = userService;
_currentContext = currentContext;
_globalSettings = globalSettings;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
}
[HttpGet("{type}")]
public async Task<PolicyResponseModel> Get(string orgId, int type)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgIdGuid, (PolicyType)type);
if (policy == null)
{
throw new NotFoundException();
}
return new PolicyResponseModel(policy);
}
[HttpGet("")]
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[AllowAnonymous]
[HttpGet("token")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(string orgId, [FromQuery] string email,
[FromQuery] string token, [FromQuery] string organizationUserId)
{
var orgUserId = new Guid(organizationUserId);
var tokenValid = CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token,
email, orgUserId, _globalSettings);
if (!tokenValid)
{
throw new NotFoundException();
}
var orgIdGuid = new Guid(orgId);
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId);
if (orgUser == null || orgUser.OrganizationId != orgIdGuid)
{
throw new NotFoundException();
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[AllowAnonymous]
[HttpGet("invited-user")]
public async Task<ListResponseModel<PolicyResponseModel>> GetByInvitedUser(string orgId, [FromQuery] string userId)
{
var user = await _userService.GetUserByIdAsync(new Guid(userId));
if (user == null)
{
throw new UnauthorizedAccessException();
}
var orgIdGuid = new Guid(orgId);
var orgUsersByUserId = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var orgUser = orgUsersByUserId.SingleOrDefault(u => u.OrganizationId == orgIdGuid);
if (orgUser == null)
{
throw new NotFoundException();
}
if (orgUser.Status != OrganizationUserStatusType.Invited)
{
throw new UnauthorizedAccessException();
}
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
return new ListResponseModel<PolicyResponseModel>(responses);
}
[HttpPut("{type}")]
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody] PolicyRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(new Guid(orgId), (PolicyType)type);
if (policy == null)
{
policy = model.ToPolicy(orgIdGuid);
}
else
{
policy = model.ToPolicy(policy);
}
var userId = _userService.GetProperUserId(User);
await _policyService.SaveAsync(policy, _userService, _organizationService, userId);
return new PolicyResponseModel(policy);
}
}

View File

@ -1,92 +0,0 @@
using Bit.Api.Models.Request.Providers;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Providers;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers/{providerId:guid}/organizations")]
[Authorize("Application")]
public class ProviderOrganizationsController : Controller
{
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public ProviderOrganizationsController(
IProviderOrganizationRepository providerOrganizationRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
{
_providerOrganizationRepository = providerOrganizationRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
[HttpGet("")]
public async Task<ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>> Get(Guid providerId)
{
if (!_currentContext.AccessProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerOrganizations.Select(o => new ProviderOrganizationOrganizationDetailsResponseModel(o));
return new ListResponseModel<ProviderOrganizationOrganizationDetailsResponseModel>(responses);
}
[HttpPost("add")]
public async Task Add(Guid providerId, [FromBody] ProviderOrganizationAddRequestModel model)
{
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
await _providerService.AddOrganization(providerId, model.OrganizationId, model.Key);
}
[HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<ProviderOrganizationResponseModel> Post(Guid providerId, [FromBody] ProviderOrganizationCreateRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var organizationSignup = model.OrganizationCreateRequest.ToOrganizationSignup(user);
var result = await _providerService.CreateOrganizationAsync(providerId, organizationSignup, model.ClientOwnerEmail, user);
return new ProviderOrganizationResponseModel(result);
}
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
if (!_currentContext.ManageProviderOrganizations(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.RemoveOrganizationAsync(providerId, id, userId.Value);
}
}

View File

@ -1,199 +0,0 @@
using Bit.Api.Models.Request.Providers;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Providers;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business.Provider;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers/{providerId:guid}/users")]
[Authorize("Application")]
public class ProviderUsersController : Controller
{
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderService _providerService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
public ProviderUsersController(
IProviderUserRepository providerUserRepository,
IProviderService providerService,
IUserService userService,
ICurrentContext currentContext)
{
_providerUserRepository = providerUserRepository;
_providerService = providerService;
_userService = userService;
_currentContext = currentContext;
}
[HttpGet("{id:guid}")]
public async Task<ProviderUserResponseModel> Get(Guid providerId, Guid id)
{
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || !_currentContext.ProviderManageUsers(providerUser.ProviderId))
{
throw new NotFoundException();
}
return new ProviderUserResponseModel(providerUser);
}
[HttpGet("")]
public async Task<ListResponseModel<ProviderUserUserDetailsResponseModel>> Get(Guid providerId)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var providerUsers = await _providerUserRepository.GetManyDetailsByProviderAsync(providerId);
var responses = providerUsers.Select(o => new ProviderUserUserDetailsResponseModel(o));
return new ListResponseModel<ProviderUserUserDetailsResponseModel>(responses);
}
[HttpPost("invite")]
public async Task Invite(Guid providerId, [FromBody] ProviderUserInviteRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateInitialInvite(model.Emails, model.Type.Value,
_userService.GetProperUserId(User).Value, providerId);
await _providerService.InviteUserAsync(invite);
}
[HttpPost("reinvite")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkReinvite(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateReinvite(model.Ids, _userService.GetProperUserId(User).Value, providerId);
var result = await _providerService.ResendInvitesAsync(invite);
return new ListResponseModel<ProviderUserBulkResponseModel>(
result.Select(t => new ProviderUserBulkResponseModel(t.Item1.Id, t.Item2)));
}
[HttpPost("{id:guid}/reinvite")]
public async Task Reinvite(Guid providerId, Guid id)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var invite = ProviderUserInviteFactory.CreateReinvite(new[] { id },
_userService.GetProperUserId(User).Value, providerId);
await _providerService.ResendInvitesAsync(invite);
}
[HttpPost("{id:guid}/accept")]
public async Task Accept(Guid providerId, Guid id, [FromBody] ProviderUserAcceptRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
await _providerService.AcceptUserAsync(id, user, model.Token);
}
[HttpPost("{id:guid}/confirm")]
public async Task Confirm(Guid providerId, Guid id, [FromBody] ProviderUserConfirmRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.ConfirmUsersAsync(providerId, new Dictionary<Guid, string> { [id] = model.Key }, userId.Value);
}
[HttpPost("confirm")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkConfirm(Guid providerId,
[FromBody] ProviderUserBulkConfirmRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var results = await _providerService.ConfirmUsersAsync(providerId, model.ToDictionary(), userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(results.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
[HttpPost("public-keys")]
public async Task<ListResponseModel<ProviderUserPublicKeyResponseModel>> UserPublicKeys(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var result = await _providerUserRepository.GetManyPublicKeysByProviderUserAsync(providerId, model.Ids);
var responses = result.Select(r => new ProviderUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
return new ListResponseModel<ProviderUserPublicKeyResponseModel>(responses);
}
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task Put(Guid providerId, Guid id, [FromBody] ProviderUserUpdateRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var providerUser = await _providerUserRepository.GetByIdAsync(id);
if (providerUser == null || providerUser.ProviderId != providerId)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.SaveUserAsync(model.ToProviderUser(providerUser), userId.Value);
}
[HttpDelete("{id:guid}")]
[HttpPost("{id:guid}/delete")]
public async Task Delete(Guid providerId, Guid id)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _providerService.DeleteUsersAsync(providerId, new[] { id }, userId.Value);
}
[HttpDelete("")]
[HttpPost("delete")]
public async Task<ListResponseModel<ProviderUserBulkResponseModel>> BulkDelete(Guid providerId, [FromBody] ProviderUserBulkRequestModel model)
{
if (!_currentContext.ProviderManageUsers(providerId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
var result = await _providerService.DeleteUsersAsync(providerId, model.Ids, userId.Value);
return new ListResponseModel<ProviderUserBulkResponseModel>(result.Select(r =>
new ProviderUserBulkResponseModel(r.Item1.Id, r.Item2)));
}
}

View File

@ -1,90 +0,0 @@
using Bit.Api.Models.Request.Providers;
using Bit.Api.Models.Response.Providers;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("providers")]
[Authorize("Application")]
public class ProvidersController : Controller
{
private readonly IUserService _userService;
private readonly IProviderRepository _providerRepository;
private readonly IProviderService _providerService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public ProvidersController(IUserService userService, IProviderRepository providerRepository,
IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings)
{
_userService = userService;
_providerRepository = providerRepository;
_providerService = providerService;
_currentContext = currentContext;
_globalSettings = globalSettings;
}
[HttpGet("{id:guid}")]
public async Task<ProviderResponseModel> Get(Guid id)
{
if (!_currentContext.ProviderUser(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
return new ProviderResponseModel(provider);
}
[HttpPut("{id:guid}")]
[HttpPost("{id:guid}")]
public async Task<ProviderResponseModel> Put(Guid id, [FromBody] ProviderUpdateRequestModel model)
{
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
await _providerService.UpdateAsync(model.ToProvider(provider, _globalSettings));
return new ProviderResponseModel(provider);
}
[HttpPost("{id:guid}/setup")]
public async Task<ProviderResponseModel> Setup(Guid id, [FromBody] ProviderSetupRequestModel model)
{
if (!_currentContext.ProviderProviderAdmin(id))
{
throw new NotFoundException();
}
var provider = await _providerRepository.GetByIdAsync(id);
if (provider == null)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var response =
await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key);
return new ProviderResponseModel(response);
}
}

View File

@ -1,6 +1,6 @@
using Bit.Api.Models.Request;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response.Organizations;
using Bit.Api.Utilities;
using Bit.Core.Context;
using Bit.Core.Enums;