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

AC Team code ownership moves - Api project (#3351)

This commit is contained in:
Thomas Rittson
2023-10-19 01:27:56 +10:00
committed by GitHub
parent d230b10f82
commit 37e9d70bee
62 changed files with 109 additions and 89 deletions

View File

@ -1,190 +0,0 @@
using System.Net;
using Bit.Api.Models.Public.Request;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories;
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 IOrganizationRepository _organizationRepository;
private readonly ICurrentContext _currentContext;
private readonly ICreateGroupCommand _createGroupCommand;
private readonly IUpdateGroupCommand _updateGroupCommand;
public GroupsController(
IGroupRepository groupRepository,
IOrganizationRepository organizationRepository,
ICurrentContext currentContext,
ICreateGroupCommand createGroupCommand,
IUpdateGroupCommand updateGroupCommand)
{
_groupRepository = groupRepository;
_organizationRepository = organizationRepository;
_currentContext = currentContext;
_createGroupCommand = createGroupCommand;
_updateGroupCommand = updateGroupCommand;
}
/// <summary>
/// Retrieve a group.
/// </summary>
/// <remarks>
/// Retrieves the details of an existing group. You need only supply the unique group identifier
/// that was returned upon group creation.
/// </remarks>
/// <param name="id">The identifier of the group to be retrieved.</param>
[HttpGet("{id}")]
[ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> 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);
}
/// <summary>
/// Retrieve a groups's member ids
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="id">The identifier of the group to be retrieved.</param>
[HttpGet("{id}/member-ids")]
[ProducesResponseType(typeof(HashSet<Guid>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> 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);
}
/// <summary>
/// List all groups.
/// </summary>
/// <remarks>
/// Returns a list of your organization's groups.
/// Group objects listed in this call include information about their associated collections.
/// </remarks>
[HttpGet]
[ProducesResponseType(typeof(ListResponseModel<GroupResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List()
{
var groups = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(_currentContext.OrganizationId.Value);
var groupResponses = groups.Select(g => new GroupResponseModel(g.Item1, g.Item2));
var response = new ListResponseModel<GroupResponseModel>(groupResponses);
return new JsonResult(response);
}
/// <summary>
/// Create a group.
/// </summary>
/// <remarks>
/// Creates a new group object.
/// </remarks>
/// <param name="model">The request model.</param>
[HttpPost]
[ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody] GroupCreateUpdateRequestModel model)
{
var group = model.ToGroup(_currentContext.OrganizationId.Value);
var associations = model.Collections?.Select(c => c.ToSelectionReadOnly());
var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId.Value);
await _createGroupCommand.CreateGroupAsync(group, organization, associations);
var response = new GroupResponseModel(group, associations);
return new JsonResult(response);
}
/// <summary>
/// Update a group.
/// </summary>
/// <remarks>
/// Updates the specified group object. If a property is not provided,
/// the value of the existing property will be reset.
/// </remarks>
/// <param name="id">The identifier of the group to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}")]
[ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> 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());
var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId.Value);
await _updateGroupCommand.UpdateGroupAsync(updatedGroup, organization, associations);
var response = new GroupResponseModel(updatedGroup, associations);
return new JsonResult(response);
}
/// <summary>
/// Update a group's members.
/// </summary>
/// <remarks>
/// Updates the specified group's member associations.
/// </remarks>
/// <param name="id">The identifier of the group to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}/member-ids")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> 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();
}
/// <summary>
/// Delete a group.
/// </summary>
/// <remarks>
/// Permanently deletes a group. This cannot be undone.
/// </remarks>
/// <param name="id">The identifier of the group to be deleted.</param>
[HttpDelete("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> 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();
}
}

View File

@ -1,237 +0,0 @@
using System.Net;
using Bit.Api.Models.Public.Request;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.Models.Business;
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.Public.Controllers;
[Route("public/members")]
[Authorize("Organization")]
public class MembersController : Controller
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
public MembersController(
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository,
IOrganizationService organizationService,
IUserService userService,
ICurrentContext currentContext,
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand)
{
_organizationUserRepository = organizationUserRepository;
_groupRepository = groupRepository;
_organizationService = organizationService;
_userService = userService;
_currentContext = currentContext;
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
}
/// <summary>
/// Retrieve a member.
/// </summary>
/// <remarks>
/// Retrieves the details of an existing member of the organization. You need only supply the
/// unique member identifier that was returned upon member creation.
/// </remarks>
/// <param name="id">The identifier of the member to be retrieved.</param>
[HttpGet("{id}")]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Get(Guid id)
{
var userDetails = await _organizationUserRepository.GetDetailsByIdWithCollectionsAsync(id);
var orgUser = userDetails?.Item1;
if (orgUser == null || orgUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var response = new MemberResponseModel(orgUser, await _userService.TwoFactorIsEnabledAsync(orgUser),
userDetails.Item2);
return new JsonResult(response);
}
/// <summary>
/// Retrieve a member's group ids
/// </summary>
/// <remarks>
/// Retrieves the unique identifiers for all groups that are associated with this member. You need only
/// supply the unique member identifier that was returned upon member creation.
/// </remarks>
/// <param name="id">The identifier of the member to be retrieved.</param>
[HttpGet("{id}/group-ids")]
[ProducesResponseType(typeof(HashSet<Guid>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetGroupIds(Guid id)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(id);
return new JsonResult(groupIds);
}
/// <summary>
/// List all members.
/// </summary>
/// <remarks>
/// Returns a list of your organization's members.
/// Member objects listed in this call do not include information about their associated collections.
/// </remarks>
[HttpGet]
[ProducesResponseType(typeof(ListResponseModel<MemberResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List()
{
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(
_currentContext.OrganizationId.Value);
// TODO: Get all CollectionUser associations for the organization and marry them up here for the response.
var memberResponsesTasks = users.Select(async u => new MemberResponseModel(u,
await _userService.TwoFactorIsEnabledAsync(u), null));
var memberResponses = await Task.WhenAll(memberResponsesTasks);
var response = new ListResponseModel<MemberResponseModel>(memberResponses);
return new JsonResult(response);
}
/// <summary>
/// Create a member.
/// </summary>
/// <remarks>
/// Creates a new member object by inviting a user to the organization.
/// </remarks>
/// <param name="model">The request model.</param>
[HttpPost]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody] MemberCreateRequestModel model)
{
var associations = model.Collections?.Select(c => c.ToSelectionReadOnly());
var invite = new OrganizationUserInvite
{
Emails = new List<string> { model.Email },
Type = model.Type.Value,
AccessAll = model.AccessAll.Value,
Collections = associations
};
var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null,
model.Email, model.Type.Value, model.AccessAll.Value, model.ExternalId, associations, model.Groups);
var response = new MemberResponseModel(user, associations);
return new JsonResult(response);
}
/// <summary>
/// Update a member.
/// </summary>
/// <remarks>
/// Updates the specified member object. If a property is not provided,
/// the value of the existing property will be reset.
/// </remarks>
/// <param name="id">The identifier of the member to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}")]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Put(Guid id, [FromBody] MemberUpdateRequestModel model)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var updatedUser = model.ToOrganizationUser(existingUser);
var associations = model.Collections?.Select(c => c.ToSelectionReadOnly());
await _organizationService.SaveUserAsync(updatedUser, null, associations, model.Groups);
MemberResponseModel response = null;
if (existingUser.UserId.HasValue)
{
var existingUserDetails = await _organizationUserRepository.GetDetailsByIdAsync(id);
response = new MemberResponseModel(existingUserDetails,
await _userService.TwoFactorIsEnabledAsync(existingUserDetails), associations);
}
else
{
response = new MemberResponseModel(updatedUser, associations);
}
return new JsonResult(response);
}
/// <summary>
/// Update a member's groups.
/// </summary>
/// <remarks>
/// Updates the specified member's group associations.
/// </remarks>
/// <param name="id">The identifier of the member to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}/group-ids")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> PutGroupIds(Guid id, [FromBody] UpdateGroupIdsRequestModel model)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(existingUser, model.GroupIds, null);
return new OkResult();
}
/// <summary>
/// Delete a member.
/// </summary>
/// <remarks>
/// Permanently deletes a member from the organization. This cannot be undone.
/// The user account will still remain. The user is only removed from the organization.
/// </remarks>
/// <param name="id">The identifier of the member to be deleted.</param>
[HttpDelete("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Delete(Guid id)
{
var user = await _organizationUserRepository.GetByIdAsync(id);
if (user == null || user.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _organizationService.DeleteUserAsync(_currentContext.OrganizationId.Value, id, null);
return new OkResult();
}
/// <summary>
/// Re-invite a member.
/// </summary>
/// <remarks>
/// Re-sends the invitation email to an organization member.
/// </remarks>
/// <param name="id">The identifier of the member to re-invite.</param>
[HttpPost("{id}/reinvite")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> PostReinvite(Guid id)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _organizationService.ResendInviteAsync(_currentContext.OrganizationId.Value, null, id);
return new OkResult();
}
}

View File

@ -1,58 +0,0 @@
using System.Net;
using Bit.Api.Models.Public.Request;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers;
[Route("public/organization")]
[Authorize("Organization")]
public class OrganizationController : Controller
{
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
public OrganizationController(
IOrganizationService organizationService,
ICurrentContext currentContext,
GlobalSettings globalSettings)
{
_organizationService = organizationService;
_currentContext = currentContext;
_globalSettings = globalSettings;
}
/// <summary>
/// Import members and groups.
/// </summary>
/// <remarks>
/// Import members and groups from an external system.
/// </remarks>
/// <param name="model">The request model.</param>
[HttpPost("import")]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Import([FromBody] OrganizationImportRequestModel model)
{
if (!_globalSettings.SelfHosted && !model.LargeImport &&
(model.Groups.Count() > 2000 || model.Members.Count(u => !u.Deleted) > 2000))
{
throw new BadRequestException("You cannot import this much data at once.");
}
await _organizationService.ImportAsync(
_currentContext.OrganizationId.Value,
null,
model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)),
model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()),
model.Members.Where(u => u.Deleted).Select(u => u.ExternalId),
model.OverwriteExisting.GetValueOrDefault());
return new OkResult();
}
}

View File

@ -1,104 +0,0 @@
using System.Net;
using Bit.Api.Models.Public.Request;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers;
[Route("public/policies")]
[Authorize("Organization")]
public class PoliciesController : Controller
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IUserService userService,
IOrganizationService organizationService,
ICurrentContext currentContext)
{
_policyRepository = policyRepository;
_policyService = policyService;
_userService = userService;
_organizationService = organizationService;
_currentContext = currentContext;
}
/// <summary>
/// Retrieve a policy.
/// </summary>
/// <remarks>
/// Retrieves the details of a policy.
/// </remarks>
/// <param name="type">The type of policy to be retrieved.</param>
[HttpGet("{type}")]
[ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Get(PolicyType type)
{
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(
_currentContext.OrganizationId.Value, type);
if (policy == null)
{
return new NotFoundResult();
}
var response = new PolicyResponseModel(policy);
return new JsonResult(response);
}
/// <summary>
/// List all policies.
/// </summary>
/// <remarks>
/// Returns a list of your organization's policies.
/// </remarks>
[HttpGet]
[ProducesResponseType(typeof(ListResponseModel<PolicyResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List()
{
var policies = await _policyRepository.GetManyByOrganizationIdAsync(_currentContext.OrganizationId.Value);
var policyResponses = policies.Select(p => new PolicyResponseModel(p));
var response = new ListResponseModel<PolicyResponseModel>(policyResponses);
return new JsonResult(response);
}
/// <summary>
/// Update a policy.
/// </summary>
/// <remarks>
/// Updates the specified policy. If a property is not provided,
/// the value of the existing property will be reset.
/// </remarks>
/// <param name="type">The type of policy to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}")]
[ProducesResponseType(typeof(PolicyResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Put(PolicyType type, [FromBody] PolicyUpdateRequestModel model)
{
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(
_currentContext.OrganizationId.Value, type);
if (policy == null)
{
policy = model.ToPolicy(_currentContext.OrganizationId.Value);
}
else
{
policy = model.ToPolicy(policy);
}
await _policyService.SaveAsync(policy, _userService, _organizationService, null);
var response = new PolicyResponseModel(policy);
return new JsonResult(response);
}
}