using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Bit.Core; using Bit.Core.Models.Api.Public; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.Public.Controllers { [Route("public/groups")] [Authorize("Organization")] public class GroupsController : Controller { private readonly IGroupRepository _groupRepository; private readonly IGroupService _groupService; private readonly CurrentContext _currentContext; public GroupsController( IGroupRepository groupRepository, IGroupService groupService, CurrentContext currentContext) { _groupRepository = groupRepository; _groupService = groupService; _currentContext = currentContext; } /// /// Retrieve a group. /// /// /// Retrieves the details of an existing group. You need only supply the unique group identifier /// that was returned upon group creation. /// /// The identifier of the group to be retrieved. [HttpGet("{id}")] [ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Get(Guid id) { var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(id); var group = groupDetails?.Item1; if(group == null || group.OrganizationId != _currentContext.OrganizationId) { return new NotFoundResult(); } var response = new GroupResponseModel(group, groupDetails.Item2); return new JsonResult(response); } /// /// Retrieve a groups's member ids /// /// /// Retrieves the unique identifiers for all members that are associated with this group. You need only /// supply the unique group identifier that was returned upon group creation. /// /// The identifier of the group to be retrieved. [HttpGet("{id}/member-ids")] [ProducesResponseType(typeof(HashSet), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task GetMemberIds(Guid id) { var group = await _groupRepository.GetByIdAsync(id); if(group == null || group.OrganizationId != _currentContext.OrganizationId) { return new NotFoundResult(); } var orgUserIds = await _groupRepository.GetManyUserIdsByIdAsync(id); return new JsonResult(orgUserIds); } /// /// List all groups. /// /// /// Returns a list of your organization's groups. /// Group objects listed in this call do not include information about their associated collections. /// [HttpGet] [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)] public async Task List() { var groups = await _groupRepository.GetManyByOrganizationIdAsync(_currentContext.OrganizationId.Value); // TODO: Get all CollectionGroup associations for the organization and marry them up here for the response. var groupResponses = groups.Select(g => new GroupResponseModel(g, null)); var response = new ListResponseModel(groupResponses); return new JsonResult(response); } /// /// Create a group. /// /// /// Creates a new group object. /// /// The request model. [HttpPost] [ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] public async Task Post([FromBody]GroupCreateUpdateRequestModel model) { var group = model.ToGroup(_currentContext.OrganizationId.Value); var associations = model.Collections?.Select(c => c.ToSelectionReadOnly()); await _groupService.SaveAsync(group, associations); var response = new GroupResponseModel(group, associations); return new JsonResult(response); } /// /// Update a group. /// /// /// Updates the specified group object. If a property is not provided, /// the value of the existing property will be reset. /// /// The identifier of the group to be updated. /// The request model. [HttpPut("{id}")] [ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Put(Guid id, [FromBody]GroupCreateUpdateRequestModel model) { var existingGroup = await _groupRepository.GetByIdAsync(id); if(existingGroup == null || existingGroup.OrganizationId != _currentContext.OrganizationId) { return new NotFoundResult(); } var updatedGroup = model.ToGroup(existingGroup); var associations = model.Collections?.Select(c => c.ToSelectionReadOnly()); await _groupService.SaveAsync(updatedGroup, associations); var response = new GroupResponseModel(updatedGroup, associations); return new JsonResult(response); } /// /// Update a group's members. /// /// /// Updates the specified group's member associations. /// /// The identifier of the group to be updated. /// The request model. [HttpPut("{id}/member-ids")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task PutMemberIds(Guid id, [FromBody]UpdateMemberIdsRequestModel model) { var existingGroup = await _groupRepository.GetByIdAsync(id); if(existingGroup == null || existingGroup.OrganizationId != _currentContext.OrganizationId) { return new NotFoundResult(); } await _groupRepository.UpdateUsersAsync(existingGroup.Id, model.MemberIds); return new OkResult(); } /// /// Delete a group. /// /// /// Permanently deletes a group. This cannot be undone. /// /// The identifier of the group to be deleted. [HttpDelete("{id}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Delete(Guid id) { var group = await _groupRepository.GetByIdAsync(id); if(group == null || group.OrganizationId != _currentContext.OrganizationId) { return new NotFoundResult(); } await _groupRepository.DeleteAsync(group); return new OkResult(); } } }