mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 23:52:50 -05:00
[EC-647] OAVR v2 Feature Branch Merge (#2588)
* [EC-19] Move SSO Identifier to Org SSO endpoint (#2184) * [EC-19] Move SSO identifier to Org SSO config endpoint * [EC-19] Add Jira tech debt issue reference * [EC-542] Update email communications (#2348) * [EC-73] Add users alongside groups for collection details (#2358) * [EC-73] feat: add new stored procedures * [EC-73] feat: add migration * [EC-73] chore: rename collection group details * [EC-73] fix: migration * [EC-73] feat: return users from dapper repo * [EC-73] feat: EF support for collection users * [EC-73] feat: implement updating users in EF * [EC-73] feat: new collections with users in EF * [EC-73] feat: create with users in dapper * [EC-73] feat: update with users in dapper * [EC-73] fix: collection service tests * [EC-73] fix: lint * [EC-73] feat: add new data model and rename for clarity * [EC-73] chore: add future migrations * [EC-16 / EC-86] Implement Groups Table Endpoints (#2280) * [EC-16] Update Group endpoints/repositories to include necessary collection info * [EC-16] Add delete many groups endpoint and command * [EC-16] Add DeleteGroupCommand unit tests * [EC-16] Update migration script * [EC-16] Formatting * [EC-16] Support modifying users via Post Group endpoint - Add optional Users property to GroupRequestModel - Add users parameter to the GroupService.SaveAsync() method - Use the users argument to update the Group via the GroupRepository if present. * [EC-16] Add/update Sprocs for bulk group deletion - Add a new bump account revision date by multiple org ids sproc to be used by the delete many group sproc. - Update the delete many group sproc to no longer require the organization Id as authorization is a business concern. * [EC-16] No longer require org Id in delete many GroupRepository The group repository should not care about which organization a group belongs to when being deleted. That is a business logic concern and is not necessary at the repository level. * [EC-16] Remove org Id from delete many group command - Remove the organization Id from the delete many method. - Require Group entities instead of just group Ids so that group retrieval is completed outside the command. - No longer return deleted groups as they are now being passed into the command. - Update unit tests * [EC-16] Remove org id from bulk delete group endpoint - Remove the Org Id from the endpoint and make use of the updated delete many command * [EC-16] Rename delete many groups sproc * [EC-16] Update migration script * [EC-16] Fix typo in migration script * [EC-16] Fix order of operations in Group_DeleteByIds sproc * [EC-16] Formatting * [EC-86] Fix DeleteManyAsync parameter name Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * [EC-16] Add missing sproc to sqlproj file * [EC-16] Improve GroupRepository method performance Use GroupBy before marrying Groups and Collections to avoid iterating over all collections for every group) * [EC-16] Use ToListAsync() to be consistent in the repository * [EC-16] Fix collection grouping in the EF repository * [EC-16] Adjust DeleteGroup command namespace to be less verbose * [EC-16] Cleanup DeleteGroupCommandTests * [EC-16] Formatting * [EC-16] Ensure a non-null group collection list is provided * [EC-16] Add bulk GroupEvents method to EventService - Use the new method in the DeleteGroups command * [EC-16] Remove bulk delete group Api response The response is unnecessary and not used by the client * [EC-16] Log OrganizationUser_UpdateGroups event in GroupService Events are logged for users during both Group creation (all added users) and modification (only changed users). * [EC-16] Fix failing unit test * [EC-16] Rename newUsers variable per feedback * [EC-16] Assert delete many group log events Explicitly check for the event type and groups that are logged to the event service. * [EC-16] Update DeleteManyAsync signature Use ICollection<> instead of IEnumerable<> to avoid ambiguity of possible multiple enumeration * [EC-16] Increment migration script name Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * Add missing GO command to EC-73 migration script (#2433) * [EC-15] Members Grid Api Support (#2485) * [EC-15] Update OrganizationUser models to support list of collections and groups * [EC-15] Add sprocs to query GroupUser and CollectionUser entities * [EC-15] Update the OrganizationUserRepository to optionally fetch groups/collections * [EC-15] Formatting * [EC-15] Remove leftover repository method * [EC-15] Fix table identifier inconsistency in sproc/migration * Formatting * [EC-14]: Server changes for Collection rows in Vault (#2360) * [EC-14] add collection management methods to repo - delete many, get many by ids, and get many with groups by org * [EC-14] connection command tests had wrong folder name * [EC-14] add collection repo methods to interface * [EC-14] create DeleteCollectionCommand * [EC-14] add getManyWithDetails collections endpoint * [EC-14] add GetManyWithGroupsByUserId * [EC-14] add call to interface * [EC-14] add GetOrganizationCollectionsWIthGroups - gets groups with collections - add tests as well * [EC-14] add call to interface * [EC-14] add new coll call to controller - gets collections with groups * [EC-14] use new delete collection command * [EC-14] add CollectionBulkDeleteRequestModel * [EC-14] remove org from delete collection cmd - move all permission checks to controller - add tests to controller - remove org check from repository method * [EC-14] add migration and sprocs * [EC-14] formatting * [EC-14] revert delete permission check changes * [EC-14] rename SelectionReadOnly to CollectionAccessSelection * [EC-14] move GetOrganizationCollectionsWithGroups to controller - there's no reason to have this logic in the service layer - we can still test the permission check in the controller - also renamed repo methods and changed return types * [EC-14] include users in collection access details * [EC-14] fix migration names * [EC-14] bumpAccountRevisionDate when deleting collections * [EC-14] new line in collection service * [EC-14] formatting and add .sql to proc file * [EC-14] more formatting * [EC-14] formatting * [EC-14] fix whitespace * [EC-14] add datetime to event log of single delete * [EC-14] remove ToList() from enumerables not returned * [EC-14] fix permissions on "Create new collection" - a custom user with "Create new collections" should see all collections * [EC-14] add bulk events for collections * [EC-14] group collections from db before iterating * [EC-14] sql formatting and missing GO * [EC-14] fix tests * [EC-14] add null handling to repo methods * [EC-14] fix account revision call * [EC-14] formatting * [EC-548] Member Details Group Tab (#2508) * [EC-548] Update models to support groups * [EC-548] Include groups in invite and save organization user methods * [EC-548] Pass groups to service methods in member/user controllers * [EC-548] Fix failing tests * [EC-548] Add option to include groups for GET org user query * Formatting * [EC-887] Server fix for managers seeing options to edit/delete Collections they aren't assigned to (#2542) * [EC-887] Add Assigned property to CollectionResponseModel A new property to determine if a collection is assigned to the acting user; as some users, have the view all collections permission, but cannot see every collection's items * [EC-887] Update logic for retrieving GET all collection details - Only need to check the ViewAllCollections permission - Calculate new Assigned response property based on the assignedOrgCollections list * Formatting * [EC-887] Update unit tests Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Jacob Fink <jfink@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
@ -3,6 +3,7 @@ using Bit.Api.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -16,17 +17,20 @@ public class CollectionsController : Controller
|
||||
{
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly ICollectionService _collectionService;
|
||||
private readonly IDeleteCollectionCommand _deleteCollectionCommand;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public CollectionsController(
|
||||
ICollectionRepository collectionRepository,
|
||||
ICollectionService collectionService,
|
||||
IDeleteCollectionCommand deleteCollectionCommand,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_collectionRepository = collectionRepository;
|
||||
_collectionService = collectionService;
|
||||
_deleteCollectionCommand = deleteCollectionCommand;
|
||||
_userService = userService;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
@ -44,7 +48,7 @@ public class CollectionsController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}/details")]
|
||||
public async Task<CollectionGroupDetailsResponseModel> GetDetails(Guid orgId, Guid id)
|
||||
public async Task<CollectionAccessDetailsResponseModel> GetDetails(Guid orgId, Guid id)
|
||||
{
|
||||
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
@ -53,25 +57,58 @@ public class CollectionsController : Controller
|
||||
|
||||
if (await _currentContext.ViewAllCollections(orgId))
|
||||
{
|
||||
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id);
|
||||
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
|
||||
(var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id);
|
||||
if (collection == null || collection.OrganizationId != orgId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
|
||||
return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users);
|
||||
}
|
||||
else
|
||||
{
|
||||
var collectionDetails = await _collectionRepository.GetByIdWithGroupsAsync(id,
|
||||
(var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id,
|
||||
_currentContext.UserId.Value);
|
||||
if (collectionDetails?.Item1 == null || collectionDetails.Item1.OrganizationId != orgId)
|
||||
if (collection == null || collection.OrganizationId != orgId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return new CollectionGroupDetailsResponseModel(collectionDetails.Item1, collectionDetails.Item2);
|
||||
return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("details")]
|
||||
public async Task<ListResponseModel<CollectionAccessDetailsResponseModel>> GetManyWithDetails(Guid orgId)
|
||||
{
|
||||
if (!await ViewAtLeastOneCollectionAsync(orgId) && !await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// We always need to know which collections the current user is assigned to
|
||||
var assignedOrgCollections = await _collectionRepository.GetManyByUserIdWithAccessAsync(_currentContext.UserId.Value, orgId);
|
||||
|
||||
if (await _currentContext.ViewAllCollections(orgId))
|
||||
{
|
||||
// The user can view all collections, but they may not always be assigned to all of them
|
||||
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(orgId);
|
||||
|
||||
return new ListResponseModel<CollectionAccessDetailsResponseModel>(allOrgCollections.Select(c =>
|
||||
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
|
||||
{
|
||||
// Manually determine which collections they're assigned to
|
||||
Assigned = assignedOrgCollections.Any(ac => ac.Item1.Id == c.Item1.Id)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return new ListResponseModel<CollectionAccessDetailsResponseModel>(assignedOrgCollections.Select(c =>
|
||||
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
|
||||
{
|
||||
Assigned = true // Mapping from assignedOrgCollections implies they're all assigned
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<CollectionResponseModel>> Get(Guid orgId)
|
||||
{
|
||||
@ -110,11 +147,13 @@ public class CollectionsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var groups = model.Groups?.Select(g => g.ToSelectionReadOnly());
|
||||
var users = model.Users?.Select(g => g.ToSelectionReadOnly());
|
||||
|
||||
var assignUserToCollection = !(await _currentContext.EditAnyCollection(orgId)) &&
|
||||
await _currentContext.EditAssignedCollections(orgId);
|
||||
|
||||
await _collectionService.SaveAsync(collection, model.Groups?.Select(g => g.ToSelectionReadOnly()),
|
||||
assignUserToCollection ? _currentContext.UserId : null);
|
||||
await _collectionService.SaveAsync(collection, groups, users, assignUserToCollection ? _currentContext.UserId : null);
|
||||
return new CollectionResponseModel(collection);
|
||||
}
|
||||
|
||||
@ -128,8 +167,9 @@ public class CollectionsController : Controller
|
||||
}
|
||||
|
||||
var collection = await GetCollectionAsync(id, orgId);
|
||||
await _collectionService.SaveAsync(model.ToCollection(collection),
|
||||
model.Groups?.Select(g => g.ToSelectionReadOnly()));
|
||||
var groups = model.Groups?.Select(g => g.ToSelectionReadOnly());
|
||||
var users = model.Users?.Select(g => g.ToSelectionReadOnly());
|
||||
await _collectionService.SaveAsync(model.ToCollection(collection), groups, users);
|
||||
return new CollectionResponseModel(collection);
|
||||
}
|
||||
|
||||
@ -155,7 +195,29 @@ public class CollectionsController : Controller
|
||||
}
|
||||
|
||||
var collection = await GetCollectionAsync(id, orgId);
|
||||
await _collectionService.DeleteAsync(collection);
|
||||
await _deleteCollectionCommand.DeleteAsync(collection);
|
||||
}
|
||||
|
||||
[HttpDelete("")]
|
||||
[HttpPost("delete")]
|
||||
public async Task DeleteMany([FromBody] CollectionBulkDeleteRequestModel model)
|
||||
{
|
||||
var orgId = new Guid(model.OrganizationId);
|
||||
var collectionIds = model.Ids.Select(i => new Guid(i));
|
||||
if (!await _currentContext.DeleteAssignedCollections(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userCollections = await _collectionRepository.GetManyByUserIdAsync(_currentContext.UserId.Value);
|
||||
var filteredCollections = userCollections.Where(c => collectionIds.Contains(c.Id) && c.OrganizationId == orgId);
|
||||
|
||||
if (!filteredCollections.Any())
|
||||
{
|
||||
throw new BadRequestException("No collections found.");
|
||||
}
|
||||
|
||||
await _deleteCollectionCommand.DeleteManyAsync(filteredCollections);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}/user/{orgUserId}")]
|
||||
|
@ -16,6 +16,7 @@ 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;
|
||||
@ -27,7 +28,8 @@ public class GroupsController : Controller
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICurrentContext currentContext,
|
||||
ICreateGroupCommand createGroupCommand,
|
||||
IUpdateGroupCommand updateGroupCommand)
|
||||
IUpdateGroupCommand updateGroupCommand,
|
||||
IDeleteGroupCommand deleteGroupCommand)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
_groupService = groupService;
|
||||
@ -35,6 +37,7 @@ public class GroupsController : Controller
|
||||
_currentContext = currentContext;
|
||||
_createGroupCommand = createGroupCommand;
|
||||
_updateGroupCommand = updateGroupCommand;
|
||||
_deleteGroupCommand = deleteGroupCommand;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -62,7 +65,7 @@ public class GroupsController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<GroupResponseModel>> Get(string orgId)
|
||||
public async Task<ListResponseModel<GroupDetailsResponseModel>> Get(string orgId)
|
||||
{
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var canAccess = await _currentContext.ManageGroups(orgIdGuid) ||
|
||||
@ -75,9 +78,9 @@ public class GroupsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgIdGuid);
|
||||
var responses = groups.Select(g => new GroupResponseModel(g));
|
||||
return new ListResponseModel<GroupResponseModel>(responses);
|
||||
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")]
|
||||
@ -105,7 +108,7 @@ public class GroupsController : Controller
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
|
||||
var group = model.ToGroup(orgIdGuid);
|
||||
await _createGroupCommand.CreateGroupAsync(group, organization, model.Collections?.Select(c => c.ToSelectionReadOnly()));
|
||||
await _createGroupCommand.CreateGroupAsync(group, organization, model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Users);
|
||||
|
||||
return new GroupResponseModel(group);
|
||||
}
|
||||
@ -123,7 +126,7 @@ public class GroupsController : Controller
|
||||
var orgIdGuid = new Guid(orgId);
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
|
||||
|
||||
await _updateGroupCommand.UpdateGroupAsync(model.ToGroup(group), organization, model.Collections?.Select(c => c.ToSelectionReadOnly()));
|
||||
await _updateGroupCommand.UpdateGroupAsync(model.ToGroup(group), organization, model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Users);
|
||||
return new GroupResponseModel(group);
|
||||
}
|
||||
|
||||
@ -148,7 +151,24 @@ public class GroupsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await _groupService.DeleteAsync(group);
|
||||
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}")]
|
||||
|
@ -49,7 +49,7 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<OrganizationUserDetailsResponseModel> Get(string orgId, string id)
|
||||
public async Task<OrganizationUserDetailsResponseModel> Get(string id, bool includeGroups = false)
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByIdWithCollectionsAsync(new Guid(id));
|
||||
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
|
||||
@ -57,11 +57,18 @@ public class OrganizationUsersController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new OrganizationUserDetailsResponseModel(organizationUser.Item1, organizationUser.Item2);
|
||||
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)
|
||||
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId, bool includeGroups = false, bool includeCollections = false)
|
||||
{
|
||||
var orgGuidId = new Guid(orgId);
|
||||
if (!await _currentContext.ViewAllCollections(orgGuidId) &&
|
||||
@ -72,7 +79,7 @@ public class OrganizationUsersController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId);
|
||||
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);
|
||||
@ -262,7 +269,7 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
|
||||
model.Collections?.Select(c => c.ToSelectionReadOnly()));
|
||||
model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Groups);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/groups")]
|
||||
|
@ -4,9 +4,9 @@ namespace Bit.Api.Models.Public.Request;
|
||||
|
||||
public class AssociationWithPermissionsRequestModel : AssociationWithPermissionsBaseModel
|
||||
{
|
||||
public SelectionReadOnly ToSelectionReadOnly()
|
||||
public CollectionAccessSelection ToSelectionReadOnly()
|
||||
{
|
||||
return new SelectionReadOnly
|
||||
return new CollectionAccessSelection
|
||||
{
|
||||
Id = Id.Value,
|
||||
ReadOnly = ReadOnly.Value
|
||||
|
@ -9,6 +9,11 @@ public class MemberUpdateRequestModel : MemberBaseModel
|
||||
/// </summary>
|
||||
public IEnumerable<AssociationWithPermissionsRequestModel> Collections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ids of the associated groups that this member will belong to
|
||||
/// </summary>
|
||||
public IEnumerable<Guid> Groups { get; set; }
|
||||
|
||||
public virtual OrganizationUser ToOrganizationUser(OrganizationUser existingUser)
|
||||
{
|
||||
existingUser.Type = Type.Value;
|
||||
|
@ -4,7 +4,7 @@ namespace Bit.Api.Models.Public.Response;
|
||||
|
||||
public class AssociationWithPermissionsResponseModel : AssociationWithPermissionsBaseModel
|
||||
{
|
||||
public AssociationWithPermissionsResponseModel(SelectionReadOnly selection)
|
||||
public AssociationWithPermissionsResponseModel(CollectionAccessSelection selection)
|
||||
{
|
||||
if (selection == null)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Bit.Api.Models.Public.Response;
|
||||
/// </summary>
|
||||
public class CollectionResponseModel : CollectionBaseModel, IResponseModel
|
||||
{
|
||||
public CollectionResponseModel(Collection collection, IEnumerable<SelectionReadOnly> groups)
|
||||
public CollectionResponseModel(Collection collection, IEnumerable<CollectionAccessSelection> groups)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Bit.Api.Models.Public.Response;
|
||||
/// </summary>
|
||||
public class GroupResponseModel : GroupBaseModel, IResponseModel
|
||||
{
|
||||
public GroupResponseModel(Group group, IEnumerable<SelectionReadOnly> collections)
|
||||
public GroupResponseModel(Group group, IEnumerable<CollectionAccessSelection> collections)
|
||||
{
|
||||
if (group == null)
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ namespace Bit.Api.Models.Public.Response;
|
||||
/// </summary>
|
||||
public class MemberResponseModel : MemberBaseModel, IResponseModel
|
||||
{
|
||||
public MemberResponseModel(OrganizationUser user, IEnumerable<SelectionReadOnly> collections)
|
||||
public MemberResponseModel(OrganizationUser user, IEnumerable<CollectionAccessSelection> collections)
|
||||
: base(user)
|
||||
{
|
||||
if (user == null)
|
||||
@ -27,7 +27,7 @@ public class MemberResponseModel : MemberBaseModel, IResponseModel
|
||||
}
|
||||
|
||||
public MemberResponseModel(OrganizationUserUserDetails user, bool twoFactorEnabled,
|
||||
IEnumerable<SelectionReadOnly> collections)
|
||||
IEnumerable<CollectionAccessSelection> collections)
|
||||
: base(user)
|
||||
{
|
||||
if (user == null)
|
||||
|
@ -13,6 +13,7 @@ public class CollectionRequestModel
|
||||
[StringLength(300)]
|
||||
public string ExternalId { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Groups { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Users { get; set; }
|
||||
|
||||
public Collection ToCollection(Guid orgId)
|
||||
{
|
||||
@ -29,3 +30,10 @@ public class CollectionRequestModel
|
||||
return existingCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public class CollectionBulkDeleteRequestModel
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<string> Ids { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ public class GroupRequestModel
|
||||
[StringLength(300)]
|
||||
public string ExternalId { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
public IEnumerable<Guid> Users { get; set; }
|
||||
|
||||
public Group ToGroup(Guid orgId)
|
||||
{
|
||||
@ -30,3 +31,9 @@ public class GroupRequestModel
|
||||
return existingGroup;
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupBulkRequestModel
|
||||
{
|
||||
[Required]
|
||||
public IEnumerable<Guid> Ids { get; set; }
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ public class OrganizationUserInviteRequestModel
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
public IEnumerable<Guid> Groups { get; set; }
|
||||
|
||||
public OrganizationUserInviteData ToData()
|
||||
{
|
||||
@ -28,6 +29,7 @@ public class OrganizationUserInviteRequestModel
|
||||
Type = Type,
|
||||
AccessAll = AccessAll,
|
||||
Collections = Collections?.Select(c => c.ToSelectionReadOnly()),
|
||||
Groups = Groups,
|
||||
Permissions = Permissions,
|
||||
};
|
||||
}
|
||||
@ -73,6 +75,7 @@ public class OrganizationUserUpdateRequestModel
|
||||
public bool AccessAll { get; set; }
|
||||
public Permissions Permissions { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
|
||||
public IEnumerable<Guid> Groups { get; set; }
|
||||
|
||||
public OrganizationUser ToOrganizationUser(OrganizationUser existingUser)
|
||||
{
|
||||
|
@ -10,9 +10,9 @@ public class SelectionReadOnlyRequestModel
|
||||
public bool ReadOnly { get; set; }
|
||||
public bool HidePasswords { get; set; }
|
||||
|
||||
public SelectionReadOnly ToSelectionReadOnly()
|
||||
public CollectionAccessSelection ToSelectionReadOnly()
|
||||
{
|
||||
return new SelectionReadOnly
|
||||
return new CollectionAccessSelection
|
||||
{
|
||||
Id = new Guid(Id),
|
||||
ReadOnly = ReadOnly,
|
||||
|
@ -39,13 +39,20 @@ public class CollectionDetailsResponseModel : CollectionResponseModel
|
||||
public bool HidePasswords { get; set; }
|
||||
}
|
||||
|
||||
public class CollectionGroupDetailsResponseModel : CollectionResponseModel
|
||||
public class CollectionAccessDetailsResponseModel : CollectionResponseModel
|
||||
{
|
||||
public CollectionGroupDetailsResponseModel(Collection collection, IEnumerable<SelectionReadOnly> groups)
|
||||
: base(collection, "collectionGroupDetails")
|
||||
public CollectionAccessDetailsResponseModel(Collection collection, IEnumerable<CollectionAccessSelection> groups, IEnumerable<CollectionAccessSelection> users)
|
||||
: base(collection, "collectionAccessDetails")
|
||||
{
|
||||
Groups = groups.Select(g => new SelectionReadOnlyResponseModel(g));
|
||||
Users = users.Select(g => new SelectionReadOnlyResponseModel(g));
|
||||
}
|
||||
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Groups { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Users { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the acting user is explicitly assigned to the collection
|
||||
/// </summary>
|
||||
public bool Assigned { get; set; }
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public class GroupResponseModel : ResponseModel
|
||||
|
||||
public class GroupDetailsResponseModel : GroupResponseModel
|
||||
{
|
||||
public GroupDetailsResponseModel(Group group, IEnumerable<SelectionReadOnly> collections)
|
||||
public GroupDetailsResponseModel(Group group, IEnumerable<CollectionAccessSelection> collections)
|
||||
: base(group, "groupDetails")
|
||||
{
|
||||
Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
@ -57,13 +58,16 @@ public class OrganizationUserResponseModel : ResponseModel
|
||||
public class OrganizationUserDetailsResponseModel : OrganizationUserResponseModel
|
||||
{
|
||||
public OrganizationUserDetailsResponseModel(OrganizationUser organizationUser,
|
||||
IEnumerable<SelectionReadOnly> collections)
|
||||
IEnumerable<CollectionAccessSelection> collections)
|
||||
: base(organizationUser, "organizationUserDetails")
|
||||
{
|
||||
Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
}
|
||||
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<Guid> Groups { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponseModel
|
||||
@ -81,6 +85,8 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
|
||||
Email = organizationUser.Email;
|
||||
TwoFactorEnabled = twoFactorEnabled;
|
||||
SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId);
|
||||
Collections = organizationUser.Collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||
Groups = organizationUser.Groups;
|
||||
// Prevent reset password when using key connector.
|
||||
ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector;
|
||||
}
|
||||
@ -89,6 +95,8 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
|
||||
public string Email { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public bool SsoBound { get; set; }
|
||||
public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; }
|
||||
public IEnumerable<Guid> Groups { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationUserResetPasswordDetailsResponseModel : ResponseModel
|
||||
|
@ -4,7 +4,7 @@ namespace Bit.Api.Models.Response;
|
||||
|
||||
public class SelectionReadOnlyResponseModel
|
||||
{
|
||||
public SelectionReadOnlyResponseModel(SelectionReadOnly selection)
|
||||
public SelectionReadOnlyResponseModel(CollectionAccessSelection selection)
|
||||
{
|
||||
if (selection == null)
|
||||
{
|
||||
|
@ -40,13 +40,12 @@ public class CollectionsController : Controller
|
||||
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||
public async Task<IActionResult> Get(Guid id)
|
||||
{
|
||||
var collectionWithGroups = await _collectionRepository.GetByIdWithGroupsAsync(id);
|
||||
var collection = collectionWithGroups?.Item1;
|
||||
(var collection, var access) = await _collectionRepository.GetByIdWithAccessAsync(id);
|
||||
if (collection == null || collection.OrganizationId != _currentContext.OrganizationId)
|
||||
{
|
||||
return new NotFoundResult();
|
||||
}
|
||||
var response = new CollectionResponseModel(collection, collectionWithGroups.Item2);
|
||||
var response = new CollectionResponseModel(collection, access.Groups);
|
||||
return new JsonResult(response);
|
||||
}
|
||||
|
||||
|
@ -83,15 +83,14 @@ public class GroupsController : Controller
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a list of your organization's groups.
|
||||
/// Group objects listed in this call do not include information about their associated collections.
|
||||
/// 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.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 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);
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ public class MembersController : Controller
|
||||
Collections = associations
|
||||
};
|
||||
var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null,
|
||||
model.Email, model.Type.Value, model.AccessAll.Value, model.ExternalId, associations);
|
||||
model.Email, model.Type.Value, model.AccessAll.Value, model.ExternalId, associations, model.Groups);
|
||||
var response = new MemberResponseModel(user, associations);
|
||||
return new JsonResult(response);
|
||||
}
|
||||
@ -149,7 +149,7 @@ public class MembersController : Controller
|
||||
}
|
||||
var updatedUser = model.ToOrganizationUser(existingUser);
|
||||
var associations = model.Collections?.Select(c => c.ToSelectionReadOnly());
|
||||
await _organizationService.SaveUserAsync(updatedUser, null, associations);
|
||||
await _organizationService.SaveUserAsync(updatedUser, null, associations, model.Groups);
|
||||
MemberResponseModel response = null;
|
||||
if (existingUser.UserId.HasValue)
|
||||
{
|
||||
|
Reference in New Issue
Block a user