using Bit.Api.AdminConsole.Models.Request; using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Models.Response; using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Groups; using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.AdminConsole.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; private readonly IFeatureService _featureService; private readonly IAuthorizationService _authorizationService; private bool UseFlexibleCollections => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); public GroupsController( IGroupRepository groupRepository, IGroupService groupService, IOrganizationRepository organizationRepository, ICurrentContext currentContext, ICreateGroupCommand createGroupCommand, IUpdateGroupCommand updateGroupCommand, IDeleteGroupCommand deleteGroupCommand, IFeatureService featureService, IAuthorizationService authorizationService) { _groupRepository = groupRepository; _groupService = groupService; _organizationRepository = organizationRepository; _currentContext = currentContext; _createGroupCommand = createGroupCommand; _updateGroupCommand = updateGroupCommand; _deleteGroupCommand = deleteGroupCommand; _featureService = featureService; _authorizationService = authorizationService; } [HttpGet("{id}")] public async Task 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 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> Get(Guid orgId) { if (UseFlexibleCollections) { // New flexible collections logic return await Get_vNext(orgId); } // Old pre-flexible collections logic follows var canAccess = await _currentContext.ManageGroups(orgId) || await _currentContext.ViewAssignedCollections(orgId) || await _currentContext.ViewAllCollections(orgId) || await _currentContext.ManageUsers(orgId); if (!canAccess) { throw new NotFoundException(); } var groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId); var responses = groups.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2)); return new ListResponseModel(responses); } [HttpGet("{id}/users")] public async Task> 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 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 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 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)); } private async Task> Get_vNext(Guid orgId) { var authorized = (await _authorizationService.AuthorizeAsync(User, GroupOperations.ReadAll(orgId))).Succeeded; if (!authorized) { throw new NotFoundException(); } var groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId); var responses = groups.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2)); return new ListResponseModel(responses); } }