mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 22:23:18 +08:00
* [AC-1117] Add manage permission (#3126)
* Update sql files to add Manage permission
* Add migration script
* Rename collection manage migration file to remove duplicate migration date
* Migrations
* Add manage to models
* Add manage to repository
* Add constraint to Manage columns
* Migration lint fixes
* Add manage to OrganizationUserUserDetails_ReadWithCollectionsById
* Add missing manage fields
* Add 'Manage' to UserCollectionDetails
* Use CREATE OR ALTER where possible
* [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145)
* feat: update org table with new column, write migration, refs AC-1374
* feat: update views with new column, refs AC-1374
* feat: Alter sprocs (org create/update) to include new column, refs AC-1374
* feat: update entity/data/request/response models to handle new column, refs AC-1374
* feat: update necessary Provider related views during migration, refs AC-1374
* fix: update org create to default new column to false, refs AC-1374
* feat: added new API/request model for collection management and removed property from update request model, refs AC-1374
* fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374
* fix: dotnet format, refs AC-1374
* feat: add ef migrations to reflect mssql changes, refs AC-1374
* fix: dotnet format, refs AC-1374
* feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374
* fix: merge conflict resolution
* [AC-1174] CollectionUser and CollectionGroup authorization handlers (#3194)
* [AC-1174] Introduce BulkAuthorizationHandler.cs
* [AC-1174] Introduce CollectionUserAuthorizationHandler
* [AC-1174] Add CreateForNewCollection CollectionUser requirement
* [AC-1174] Add some more details to CollectionCustomization
* [AC-1174] Formatting
* [AC-1174] Add CollectionGroupOperation.cs
* [AC-1174] Introduce CollectionGroupAuthorizationHandler.cs
* [AC-1174] Cleanup CollectionFixture customization
Implement and use re-usable extension method to support seeded Guids
* [AC-1174] Introduce WithValueFromList AutoFixtureExtensions
Modify CollectionCustomization to use multiple organization Ids for auto generated test data
* [AC-1174] Simplify CollectionUserAuthorizationHandler.cs
Modify the authorization handler to only perform authorization logic. Validation logic will need to be handled by any calling commands/controllers instead.
* [AC-1174] Introduce shared CollectionAccessAuthorizationHandlerBase
A shared base authorization handler was created for both CollectionUser and CollectionGroup resources, as they share the same underlying management authorization logic.
* [AC-1174] Update CollectionUserAuthorizationHandler and CollectionGroupAuthorizationHandler to use the new CollectionAccessAuthorizationHandlerBase class
* [AC-1174] Formatting
* [AC-1174] Cleanup typo and redundant ToList() call
* [AC-1174] Add check for provider users
* [AC-1174] Reduce nested loops
* [AC-1174] Introduce ICollectionAccess.cs
* [AC-1174] Remove individual CollectionGroup and CollectionUser auth handlers and use base class instead
* [AC-1174] Tweak unit test to fail minimally
* [AC-1174] Reorganize authorization handlers in Core project
* [AC-1174] Introduce new AddCoreAuthorizationHandlers() extension method
* [AC-1174] Move CollectionAccessAuthorizationHandler into Api project
* [AC-1174] Move CollectionFixture to Vault folder
* [AC-1174] Rename operation to CreateUpdateDelete
* [AC-1174] Require single organization for collection access authorization handler
- Add requirement that all target collections must belong to the same organization
- Simplify logic related to multiple organizations
- Update tests and helpers
- Use ToHashSet to improve lookup time
* [AC-1174] Fix null reference exception
* [AC-1174] Throw bad request exception when collections belong to different organizations
* [AC-1174] Switch to CollectionAuthorizationHandler instead of CollectionAccessAuthorizationHandler to reduce complexity
* Fix improper merge conflict resolution
* fix: add permission check for collection management api, refs AC-1647 (#3252)
* [AC-1125] Enforce org setting for creating/deleting collections (#3241)
* [AC-1117] Add manage permission (#3126)
* Update sql files to add Manage permission
* Add migration script
* Rename collection manage migration file to remove duplicate migration date
* Migrations
* Add manage to models
* Add manage to repository
* Add constraint to Manage columns
* Migration lint fixes
* Add manage to OrganizationUserUserDetails_ReadWithCollectionsById
* Add missing manage fields
* Add 'Manage' to UserCollectionDetails
* Use CREATE OR ALTER where possible
* [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145)
* feat: update org table with new column, write migration, refs AC-1374
* feat: update views with new column, refs AC-1374
* feat: Alter sprocs (org create/update) to include new column, refs AC-1374
* feat: update entity/data/request/response models to handle new column, refs AC-1374
* feat: update necessary Provider related views during migration, refs AC-1374
* fix: update org create to default new column to false, refs AC-1374
* feat: added new API/request model for collection management and removed property from update request model, refs AC-1374
* fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374
* fix: dotnet format, refs AC-1374
* feat: add ef migrations to reflect mssql changes, refs AC-1374
* fix: dotnet format, refs AC-1374
* feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374
* feat: created collection auth handler/operations, added LimitCollectionCdOwnerAdmin to CurrentContentOrganization, refs AC-1125
* feat: create vault service collection extensions and register with base services, refs AC-1125
* feat: deprecated CurrentContext.CreateNewCollections, refs AC-1125
* feat: deprecate DeleteAnyCollection for single resource usages, refs AC-1125
* feat: move service registration to api, update references, refs AC-1125
* feat: add bulk delete authorization handler, refs AC-1125
* feat: always assign user and give manage access on create, refs AC-1125
* fix: updated CurrentContextOrganization type, refs AC-1125
* feat: combined existing collection authorization handlers/operations, refs AC-1125
* fix: OrganizationServiceTests -> CurrentContentOrganization typo, refs AC-1125
* fix: format, refs AC-1125
* fix: update collection controller tests, refs AC-1125
* fix: dotnet format, refs AC-1125
* feat: removed extra BulkAuthorizationHandler, refs AC-1125
* fix: dotnet format, refs AC-1125
* fix: change string to guid for org id, update bulk delete request model, refs AC-1125
* fix: remove delete many collection check, refs AC-1125
* fix: clean up collection auth handler, refs AC-1125
* fix: format fix for CollectionOperations, refs AC-1125
* fix: removed unnecessary owner check, add org null check to custom permission validation, refs AC-1125
* fix: remove unused methods in CurrentContext, refs AC-1125
* fix: removed obsolete test, fixed failling delete many test, refs AC-1125
* fix: CollectionAuthorizationHandlerTests fixes, refs AC-1125
* fix: OrganizationServiceTests fix broken test by mocking GetOrganization, refs AC-1125
* fix: CollectionAuthorizationHandler - remove unused repository, refs AC-1125
* feat: moved UserId null check to common method, refs AC-1125
* fix: updated auth handler tests to remove dependency on requirement for common code checks, refs AC-1125
* feat: updated conditionals/comments for create/delete methods within colleciton auth handler, refs AC-1125
* feat: added create/delete collection auth handler success methods, refs AC-1125
* fix: new up permissions to prevent excessive null checks, refs AC-1125
* fix: remove old reference to CreateNewCollections, refs AC-1125
* fix: typo within ViewAssignedCollections method, refs AC-1125
---------
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
* refactor: remove organizationId from CollectionBulkDeleteRequestModel, refs AC-1649 (#3282)
* [AC-1174] Bulk Collection Management (#3229)
* [AC-1174] Update SelectionReadOnlyRequestModel to use Guid for Id property
* [AC-1174] Introduce initial bulk-access collection endpoint
* [AC-1174] Introduce BulkAddCollectionAccessCommand and validation logic/tests
* [AC-1174] Add CreateOrUpdateAccessMany method to CollectionRepository
* [AC-1174] Add event logs for bulk add collection access command
* [AC-1174] Add User_BumpAccountRevisionDateByCollectionIds and database migration script
* [AC-1174] Implement EF repository method
* [AC-1174] Improve null checks
* [AC-1174] Remove unnecessary BulkCollectionAccessRequestModel helpers
* [AC-1174] Add unit tests for new controller endpoint
* [AC-1174] Fix formatting
* [AC-1174] Remove comment
* [AC-1174] Remove redundant organizationId parameter
* [AC-1174] Ensure user and group Ids are distinct
* [AC-1174] Cleanup tests based on PR feedback
* [AC-1174] Formatting
* [AC-1174] Update CollectionGroup alias in the sproc
* [AC-1174] Add some additional comments to SQL sproc
* [AC-1174] Add comment explaining additional SaveChangesAsync call
---------
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
* [AC-1646] Rename LimitCollectionCdOwnerAdmin column (#3300)
* Rename LimitCollectionCdOwnerAdmin -> LimitCollectionCreationDeletion
* Rename and bump migration script
* [AC-1666] Removed EditAnyCollection from Create/Delete permission checks (#3301)
* fix: remove EditAnyCollection from Create/Delete permission check, refs AC-1666
* fix: updated comment, refs AC-1666
* Add feature flags constants and flag new route
* Update feature flag keys
* Create LegacyCollectionsAuthorizationHandler and start to re-implement old logic
* [AC-1669] Bug - Remove obsolete assignUserId from CollectionService.SaveAsync(...) (#3312)
* fix: remove AssignUserId from CollectionService.SaveAsync, refs AC-1669
* fix: add manage access conditional before creating collection, refs AC-1669
* fix: move access logic for create/update, fix all tests, refs AC-1669
* fix: add CollectionAccessSelection fixture, update tests, update bad reqeuest message, refs AC-1669
* fix: format, refs AC-1669
* fix: update null params with specific arg.is null checks, refs Ac-1669
* fix: update attribute class name, refs AC-1669
* Revert "Create LegacyCollectionsAuthorizationHandler and start to re-implement old logic"
This reverts commit fbb19cdadd.
* Restore old logic behind flags
* Add missing flags
* Fix logic, add comment
* Fix tests
* Add EnableFeatureFlag extension method for tests
* Restore legacy tests
* Add FeatureServiceFixtures to set feature flags in test
* Remove unused method
* Fix formatting
* Set feature flag to ON for auth handler tests
* Use fixture instead of calling nsubstitute directly
* Change FlexibleCollectionsIsEnabled method to property
Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
* Finish changing to property
* [AC-1139] Marked as obsolete the methods EditAssignedCollections, DeleteAssignedCollections and ViewAssignedCollections on ICurrentContext
* [AC-1139] Disabled the ability to set the custom permissions 'Delete/Edit Assigned Collections' if flexible collections feature flag is enabled
* [AC-1713] [Flexible collections] Add feature flags to server (#3334)
* Add feature flags for FlexibleCollections and BulkCollectionAccess
* Flag new routes and behaviour
---------
Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
* [AC-1748] Updated CurrentContext EditAssignedCollections, DeleteAssignedCollections, ViewAssignedCollections to check for flexible collections feature flag
* [AC-1748] Created GroupAuthorizationHandler and modified GroupsController.Get to use it if flexible collections feature flag is enabled
* [AC-1748] Created OrganizationUserAuthorizationHandler and modified OrganizationUsersController.Get to use that if flexible collections feature flag is enabled
* [AC-1748] Reverted changes on OrganizationService
* [AC-1748] Removed GroupAuthorizationHandler
* [AC-1748] Set resource as null when reading OrganizationUserUserDetailsResponseModel list
* [AC-1139] Updated CollectionsController GetManyWithDetails and Get to check for flexible collections flag
* [AC-1139] Modified CollectionsController.Get to check access before getting collections
* [AC-1139] Updated CollectionsController to use CollectionAuthorizationHandler in all endpoints if flag is enabled
* [AC-1139] Lining up collection access data with Manage = true if feature flag is off
* Add joint codeownership for auth handlers (#3346)
* [AC-1139] Separated flexible collections logic from old logic in CollectionsController; Refactored CollectionAuthorizationHandler
* [AC-1139] Fixed formatting on OrganizationUsersController; renamed OrganizationUserOperations.Read to ReadAll
* [AC-1748] Fixed logic to set manage = true for collections if user has EditAssignedCollection permission
* [AC-1717] Update default values for LimitCollectionCreationDeletion (#3365)
* Change default value in organization create sproc to 1
* Drop old column name still present in some QA instances
* Set LimitCollectionCreationDeletion value in code based on feature flag
* Fix: add missing namespace after merging in master
* Fix: add missing namespace after merging in master
* [AC-1683] Fix DB migrations for new Manage permission (#3307)
* [AC-1683] Update migration script and introduce V2 procedures and types
* [AC-1683] Update repository calls to use new V2 procedures / types
* [AC-1684] Update bulk add collection migration script to use new V2 type
* [AC-1683] Undo Manage changes to more original procedures
* [AC-1683] Restore whitespace changes
* [AC-1683] Clarify comments regarding explicit column lists
* [AC-1683] Update migration script dates
* [AC-1683] Split the migration script for readability
* [AC-1683] Re-name SelectReadOnlyArray_V2 to CollectionAccessSelectionType
* [AC-1139] Added permission checks for GroupsController.Get if FC feature flag is enabled
* [AC-1139] Added an AuthorizationHandler for Collections and renamed existing to BulkCollectionAuthorizationHandler
* [AC-1648] [Flexible Collections] Bump migration scripts before feature branch merge (#3371)
* Bump dates on sql migration scripts
* Bump date on ef migrations
* [AC-1139] Renamed existing CollectionAuthorizationHandler to BulkCollectionAuthorizationHandler for collections and created CollectionAuthorizationHandler for single item access. Fixed unit tests and created more
* [AC-1139] Fixed Provider AuthorizationHandler logic for Groups and OrganizationUsers
* [AC-1139] Fixed CollectionAuthorizationHandler unit tests
* [AC-1139] Added unit tests for GroupAuthorizationHandler and OrganizationUserAuthorizationHandler
* [AC-1139] Added unit test to test setting users with EditAssignedCollections with Manage permission when saving a collection
* [AC-1139] Added unit tests for OrganizationService InviteUser and SaveUser with EditAssignedCollections = true
* [AC-1139] Reverted changes on OrganizationService
* [AC-1139] Marked obsolete Permissions EditAssignedCollections and DeleteAssignedCollections
* [AC-1139] Renamed FlexibleCollectionsIsEnabled properties to UseFlexibleCollections
* [AC-1139] Renamed new flexible collections controller methods to have 'vNext' in the name to indicate its a new version
* [AC-1139] Created AuthorizationServiceExtensions to have an extension method for AuthorizeAsync where the resource is null
* [AC-1139] Renamed CollectionsController method to delete collection users from 'Delete' to 'DeleteUser'
* [AC-1139] Refactored BulkCollectionAuthorizationHandler.CheckCollectionPermissionsAsync
* [AC-1139] Created new CollectionOperation ReadAccess and changed GetUsers_vNext to use it
* [AC-1139] Created new CollectionOperationRequirement ReadAllWithAccess
* [AC-1139] Addressing PR suggestions
* [AC-1139] Unit tests refactors and added tests
* [AC-1139] Updated BulkCollectionAuthorizationHandler to not fail if the resource list is null or empty.
* [AC-1139] Modified authorization handlers to not fail in case the resource is null
* [AC-1139] Reverted changes made to CollectionService and OrganizationService
* [AC-1139] Reverted changes to CollectionServiceTests and OrganizationServiceTests
* [AC-1139] Fixed OrganizationUser.ReadAll permissions
---------
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com>
Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
697 lines
31 KiB
C#
697 lines
31 KiB
C#
using AutoMapper;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Infrastructure.EntityFramework.Models;
|
|
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Bit.Infrastructure.EntityFramework.Repositories;
|
|
|
|
public class CollectionRepository : Repository<Core.Entities.Collection, Collection, Guid>, ICollectionRepository
|
|
{
|
|
public CollectionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
|
|
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Collections)
|
|
{ }
|
|
|
|
public override async Task<Core.Entities.Collection> CreateAsync(Core.Entities.Collection collection)
|
|
{
|
|
await base.CreateAsync(collection);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(collection.Id, collection.OrganizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
return collection;
|
|
}
|
|
|
|
public override async Task DeleteAsync(Core.Entities.Collection collection)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(collection.Id, collection.OrganizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
await base.DeleteAsync(collection);
|
|
}
|
|
|
|
public override async Task UpsertAsync(Core.Entities.Collection collection)
|
|
{
|
|
await base.UpsertAsync(collection);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(collection.Id, collection.OrganizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
public async Task CreateAsync(Core.Entities.Collection obj, IEnumerable<CollectionAccessSelection> groups, IEnumerable<CollectionAccessSelection> users)
|
|
{
|
|
await CreateAsync(obj);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
|
|
if (groups != null)
|
|
{
|
|
var availableGroups = await (from g in dbContext.Groups
|
|
where g.OrganizationId == obj.OrganizationId
|
|
select g.Id).ToListAsync();
|
|
var collectionGroups = groups
|
|
.Where(g => availableGroups.Contains(g.Id))
|
|
.Select(g => new CollectionGroup
|
|
{
|
|
CollectionId = obj.Id,
|
|
GroupId = g.Id,
|
|
ReadOnly = g.ReadOnly,
|
|
HidePasswords = g.HidePasswords,
|
|
Manage = g.Manage
|
|
});
|
|
await dbContext.AddRangeAsync(collectionGroups);
|
|
}
|
|
|
|
if (users != null)
|
|
{
|
|
var availableUsers = await (from g in dbContext.OrganizationUsers
|
|
where g.OrganizationId == obj.OrganizationId
|
|
select g.Id).ToListAsync();
|
|
var collectionUsers = users
|
|
.Where(u => availableUsers.Contains(u.Id))
|
|
.Select(u => new CollectionUser
|
|
{
|
|
CollectionId = obj.Id,
|
|
OrganizationUserId = u.Id,
|
|
ReadOnly = u.ReadOnly,
|
|
HidePasswords = u.HidePasswords,
|
|
Manage = u.Manage
|
|
});
|
|
await dbContext.AddRangeAsync(collectionUsers);
|
|
}
|
|
await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(obj.OrganizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
public async Task DeleteUserAsync(Guid collectionId, Guid organizationUserId)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var query = from cu in dbContext.CollectionUsers
|
|
where cu.CollectionId == collectionId &&
|
|
cu.OrganizationUserId == organizationUserId
|
|
select cu;
|
|
dbContext.RemoveRange(await query.ToListAsync());
|
|
await dbContext.UserBumpAccountRevisionDateByOrganizationUserIdAsync(organizationUserId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
public async Task<CollectionDetails> GetByIdAsync(Guid id, Guid userId)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
return (await GetManyByUserIdAsync(userId)).FirstOrDefault(c => c.Id == id);
|
|
}
|
|
}
|
|
|
|
public async Task<Tuple<Core.Entities.Collection, CollectionAccessDetails>> GetByIdWithAccessAsync(Guid id)
|
|
{
|
|
var collection = await base.GetByIdAsync(id);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var groupQuery = from cg in dbContext.CollectionGroups
|
|
where cg.CollectionId.Equals(id)
|
|
select new CollectionAccessSelection
|
|
{
|
|
Id = cg.GroupId,
|
|
ReadOnly = cg.ReadOnly,
|
|
HidePasswords = cg.HidePasswords,
|
|
Manage = cg.Manage
|
|
};
|
|
var groups = await groupQuery.ToArrayAsync();
|
|
|
|
var userQuery = from cg in dbContext.CollectionUsers
|
|
where cg.CollectionId.Equals(id)
|
|
select new CollectionAccessSelection
|
|
{
|
|
Id = cg.OrganizationUserId,
|
|
ReadOnly = cg.ReadOnly,
|
|
HidePasswords = cg.HidePasswords,
|
|
Manage = cg.Manage
|
|
};
|
|
var users = await userQuery.ToArrayAsync();
|
|
var access = new CollectionAccessDetails { Users = users, Groups = groups };
|
|
|
|
return new Tuple<Core.Entities.Collection, CollectionAccessDetails>(collection, access);
|
|
}
|
|
}
|
|
|
|
public async Task<Tuple<CollectionDetails, CollectionAccessDetails>> GetByIdWithAccessAsync(Guid id, Guid userId)
|
|
{
|
|
var collection = await GetByIdAsync(id, userId);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var groupQuery = from cg in dbContext.CollectionGroups
|
|
where cg.CollectionId.Equals(id)
|
|
select new CollectionAccessSelection
|
|
{
|
|
Id = cg.GroupId,
|
|
ReadOnly = cg.ReadOnly,
|
|
HidePasswords = cg.HidePasswords,
|
|
Manage = cg.Manage
|
|
};
|
|
var groups = await groupQuery.ToArrayAsync();
|
|
|
|
var userQuery = from cg in dbContext.CollectionUsers
|
|
where cg.CollectionId.Equals(id)
|
|
select new CollectionAccessSelection
|
|
{
|
|
Id = cg.OrganizationUserId,
|
|
ReadOnly = cg.ReadOnly,
|
|
HidePasswords = cg.HidePasswords,
|
|
Manage = cg.Manage,
|
|
};
|
|
var users = await userQuery.ToArrayAsync();
|
|
var access = new CollectionAccessDetails { Users = users, Groups = groups };
|
|
|
|
return new Tuple<CollectionDetails, CollectionAccessDetails>(collection, access);
|
|
}
|
|
}
|
|
|
|
public async Task<ICollection<Tuple<Core.Entities.Collection, CollectionAccessDetails>>> GetManyByOrganizationIdWithAccessAsync(Guid organizationId)
|
|
{
|
|
var collections = await GetManyByOrganizationIdAsync(organizationId);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var groups =
|
|
from c in collections
|
|
join cg in dbContext.CollectionGroups on c.Id equals cg.CollectionId
|
|
group cg by cg.CollectionId into g
|
|
select g;
|
|
var users =
|
|
from c in collections
|
|
join cu in dbContext.CollectionUsers on c.Id equals cu.CollectionId
|
|
group cu by cu.CollectionId into u
|
|
select u;
|
|
|
|
return collections.Select(collection =>
|
|
new Tuple<Core.Entities.Collection, CollectionAccessDetails>(
|
|
collection,
|
|
new CollectionAccessDetails
|
|
{
|
|
Groups = groups
|
|
.FirstOrDefault(g => g.Key == collection.Id)?
|
|
.Select(g => new CollectionAccessSelection
|
|
{
|
|
Id = g.GroupId,
|
|
HidePasswords = g.HidePasswords,
|
|
ReadOnly = g.ReadOnly,
|
|
Manage = g.Manage
|
|
}).ToList() ?? new List<CollectionAccessSelection>(),
|
|
Users = users
|
|
.FirstOrDefault(u => u.Key == collection.Id)?
|
|
.Select(c => new CollectionAccessSelection
|
|
{
|
|
Id = c.OrganizationUserId,
|
|
HidePasswords = c.HidePasswords,
|
|
ReadOnly = c.ReadOnly,
|
|
Manage = c.Manage
|
|
}).ToList() ?? new List<CollectionAccessSelection>()
|
|
}
|
|
)
|
|
).ToList();
|
|
}
|
|
}
|
|
|
|
public async Task<ICollection<Tuple<CollectionDetails, CollectionAccessDetails>>> GetManyByUserIdWithAccessAsync(Guid userId, Guid organizationId)
|
|
{
|
|
var collections = (await GetManyByUserIdAsync(userId)).Where(c => c.OrganizationId == organizationId).ToList();
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var groups =
|
|
from c in collections
|
|
join cg in dbContext.CollectionGroups on c.Id equals cg.CollectionId
|
|
group cg by cg.CollectionId into g
|
|
select g;
|
|
var users =
|
|
from c in collections
|
|
join cu in dbContext.CollectionUsers on c.Id equals cu.CollectionId
|
|
group cu by cu.CollectionId into u
|
|
select u;
|
|
|
|
return collections.Select(collection =>
|
|
new Tuple<CollectionDetails, CollectionAccessDetails>(
|
|
collection,
|
|
new CollectionAccessDetails
|
|
{
|
|
Groups = groups
|
|
.FirstOrDefault(g => g.Key == collection.Id)?
|
|
.Select(g => new CollectionAccessSelection
|
|
{
|
|
Id = g.GroupId,
|
|
HidePasswords = g.HidePasswords,
|
|
ReadOnly = g.ReadOnly,
|
|
Manage = g.Manage
|
|
}).ToList() ?? new List<CollectionAccessSelection>(),
|
|
Users = users
|
|
.FirstOrDefault(u => u.Key == collection.Id)?
|
|
.Select(c => new CollectionAccessSelection
|
|
{
|
|
Id = c.OrganizationUserId,
|
|
HidePasswords = c.HidePasswords,
|
|
ReadOnly = c.ReadOnly,
|
|
Manage = c.Manage
|
|
}).ToList() ?? new List<CollectionAccessSelection>()
|
|
}
|
|
)
|
|
).ToList();
|
|
}
|
|
}
|
|
|
|
public async Task<ICollection<Core.Entities.Collection>> GetManyByManyIdsAsync(IEnumerable<Guid> collectionIds)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var query = from c in dbContext.Collections
|
|
where collectionIds.Contains(c.Id)
|
|
select c;
|
|
var data = await query.ToArrayAsync();
|
|
return data;
|
|
}
|
|
}
|
|
|
|
public async Task<int> GetCountByOrganizationIdAsync(Guid organizationId)
|
|
{
|
|
var query = new CollectionReadCountByOrganizationIdQuery(organizationId);
|
|
return await GetCountFromQuery(query);
|
|
}
|
|
|
|
public async Task<ICollection<Core.Entities.Collection>> GetManyByOrganizationIdAsync(Guid organizationId)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var query = from c in dbContext.Collections
|
|
where c.OrganizationId == organizationId
|
|
select c;
|
|
var collections = await query.ToArrayAsync();
|
|
return collections;
|
|
}
|
|
}
|
|
|
|
public async Task<ICollection<CollectionDetails>> GetManyByUserIdAsync(Guid userId)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
|
|
var baseCollectionQuery = new UserCollectionDetailsQuery(userId).Run(dbContext);
|
|
|
|
if (dbContext.Database.IsSqlite())
|
|
{
|
|
return (await baseCollectionQuery.ToListAsync())
|
|
.GroupBy(c => new
|
|
{
|
|
c.Id,
|
|
c.OrganizationId,
|
|
c.Name,
|
|
c.CreationDate,
|
|
c.RevisionDate,
|
|
c.ExternalId
|
|
})
|
|
.Select(collectionGroup => new CollectionDetails
|
|
{
|
|
Id = collectionGroup.Key.Id,
|
|
OrganizationId = collectionGroup.Key.OrganizationId,
|
|
Name = collectionGroup.Key.Name,
|
|
CreationDate = collectionGroup.Key.CreationDate,
|
|
RevisionDate = collectionGroup.Key.RevisionDate,
|
|
ExternalId = collectionGroup.Key.ExternalId,
|
|
ReadOnly = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.ReadOnly))),
|
|
HidePasswords = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
|
|
Manage = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.Manage))),
|
|
})
|
|
.ToList();
|
|
}
|
|
|
|
return await (from c in baseCollectionQuery
|
|
group c by new
|
|
{
|
|
c.Id,
|
|
c.OrganizationId,
|
|
c.Name,
|
|
c.CreationDate,
|
|
c.RevisionDate,
|
|
c.ExternalId
|
|
} into collectionGroup
|
|
select new CollectionDetails
|
|
{
|
|
Id = collectionGroup.Key.Id,
|
|
OrganizationId = collectionGroup.Key.OrganizationId,
|
|
Name = collectionGroup.Key.Name,
|
|
CreationDate = collectionGroup.Key.CreationDate,
|
|
RevisionDate = collectionGroup.Key.RevisionDate,
|
|
ExternalId = collectionGroup.Key.ExternalId,
|
|
ReadOnly = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.ReadOnly))),
|
|
HidePasswords = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
|
|
Manage = Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.Manage))),
|
|
}).ToListAsync();
|
|
}
|
|
}
|
|
|
|
public async Task<ICollection<CollectionAccessSelection>> GetManyUsersByIdAsync(Guid id)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var query = from cu in dbContext.CollectionUsers
|
|
where cu.CollectionId == id
|
|
select cu;
|
|
var collectionUsers = await query.ToListAsync();
|
|
return collectionUsers.Select(cu => new CollectionAccessSelection
|
|
{
|
|
Id = cu.OrganizationUserId,
|
|
ReadOnly = cu.ReadOnly,
|
|
HidePasswords = cu.HidePasswords,
|
|
Manage = cu.Manage
|
|
}).ToArray();
|
|
}
|
|
}
|
|
|
|
public async Task ReplaceAsync(Core.Entities.Collection collection, IEnumerable<CollectionAccessSelection> groups,
|
|
IEnumerable<CollectionAccessSelection> users)
|
|
{
|
|
await UpsertAsync(collection);
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
await ReplaceCollectionGroupsAsync(dbContext, collection, groups);
|
|
await ReplaceCollectionUsersAsync(dbContext, collection, users);
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(collection.Id, collection.OrganizationId);
|
|
}
|
|
}
|
|
|
|
public async Task UpdateUsersAsync(Guid id, IEnumerable<CollectionAccessSelection> requestedUsers)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
|
|
var organizationId = await dbContext.Collections
|
|
.Where(c => c.Id == id)
|
|
.Select(c => c.OrganizationId)
|
|
.FirstOrDefaultAsync();
|
|
|
|
var existingCollectionUsers = await dbContext.CollectionUsers
|
|
.Where(cu => cu.CollectionId == id)
|
|
.ToListAsync();
|
|
|
|
foreach (var requestedUser in requestedUsers)
|
|
{
|
|
var existingCollectionUser = existingCollectionUsers.FirstOrDefault(cu => cu.OrganizationUserId == requestedUser.Id);
|
|
if (existingCollectionUser == null)
|
|
{
|
|
// This is a brand new entry
|
|
dbContext.CollectionUsers.Add(new CollectionUser
|
|
{
|
|
CollectionId = id,
|
|
OrganizationUserId = requestedUser.Id,
|
|
HidePasswords = requestedUser.HidePasswords,
|
|
ReadOnly = requestedUser.ReadOnly,
|
|
Manage = requestedUser.Manage
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// It already exists, update it
|
|
existingCollectionUser.HidePasswords = requestedUser.HidePasswords;
|
|
existingCollectionUser.ReadOnly = requestedUser.ReadOnly;
|
|
existingCollectionUser.Manage = requestedUser.Manage;
|
|
dbContext.CollectionUsers.Update(existingCollectionUser);
|
|
}
|
|
|
|
// Remove all existing ones that are no longer requested
|
|
var requestedUserIds = requestedUsers.Select(u => u.Id);
|
|
dbContext.CollectionUsers.RemoveRange(existingCollectionUsers.Where(cu => !requestedUserIds.Contains(cu.OrganizationUserId)));
|
|
// Need to save the new collection users before running the bump revision code
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(id, organizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
public async Task DeleteManyAsync(IEnumerable<Guid> collectionIds)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
var collectionGroupEntities = await dbContext.CollectionGroups
|
|
.Where(cg => collectionIds.Contains(cg.CollectionId))
|
|
.ToListAsync();
|
|
var collectionEntities = await dbContext.Collections
|
|
.Where(c => collectionIds.Contains(c.Id))
|
|
.ToListAsync();
|
|
|
|
dbContext.CollectionGroups.RemoveRange(collectionGroupEntities);
|
|
dbContext.Collections.RemoveRange(collectionEntities);
|
|
await dbContext.SaveChangesAsync();
|
|
|
|
foreach (var collection in collectionEntities.GroupBy(g => g.Organization.Id))
|
|
{
|
|
await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(collection.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable<Guid> collectionIds,
|
|
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups)
|
|
{
|
|
using (var scope = ServiceScopeFactory.CreateScope())
|
|
{
|
|
var dbContext = GetDatabaseContext(scope);
|
|
|
|
var collectionIdsList = collectionIds.ToList();
|
|
|
|
if (users != null)
|
|
{
|
|
var existingCollectionUsers = await dbContext.CollectionUsers
|
|
.Where(cu => collectionIdsList.Contains(cu.CollectionId))
|
|
.ToDictionaryAsync(x => (x.CollectionId, x.OrganizationUserId));
|
|
|
|
var requestedUsers = users.ToList();
|
|
|
|
foreach (var collectionId in collectionIdsList)
|
|
{
|
|
foreach (var requestedUser in requestedUsers)
|
|
{
|
|
if (!existingCollectionUsers.TryGetValue(
|
|
(collectionId, requestedUser.Id),
|
|
out var existingCollectionUser)
|
|
)
|
|
{
|
|
// This is a brand new entry
|
|
dbContext.CollectionUsers.Add(new CollectionUser
|
|
{
|
|
CollectionId = collectionId,
|
|
OrganizationUserId = requestedUser.Id,
|
|
HidePasswords = requestedUser.HidePasswords,
|
|
ReadOnly = requestedUser.ReadOnly,
|
|
Manage = requestedUser.Manage
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// It already exists, update it
|
|
existingCollectionUser.HidePasswords = requestedUser.HidePasswords;
|
|
existingCollectionUser.ReadOnly = requestedUser.ReadOnly;
|
|
existingCollectionUser.Manage = requestedUser.Manage;
|
|
dbContext.CollectionUsers.Update(existingCollectionUser);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (groups != null)
|
|
{
|
|
var existingCollectionGroups = await dbContext.CollectionGroups
|
|
.Where(cu => collectionIdsList.Contains(cu.CollectionId))
|
|
.ToDictionaryAsync(x => (x.CollectionId, x.GroupId));
|
|
|
|
var requestedGroups = groups.ToList();
|
|
|
|
foreach (var collectionId in collectionIdsList)
|
|
{
|
|
foreach (var requestedGroup in requestedGroups)
|
|
{
|
|
if (!existingCollectionGroups.TryGetValue(
|
|
(collectionId, requestedGroup.Id),
|
|
out var existingCollectionGroup)
|
|
)
|
|
{
|
|
// This is a brand new entry
|
|
dbContext.CollectionGroups.Add(new CollectionGroup()
|
|
{
|
|
CollectionId = collectionId,
|
|
GroupId = requestedGroup.Id,
|
|
HidePasswords = requestedGroup.HidePasswords,
|
|
ReadOnly = requestedGroup.ReadOnly,
|
|
Manage = requestedGroup.Manage
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// It already exists, update it
|
|
existingCollectionGroup.HidePasswords = requestedGroup.HidePasswords;
|
|
existingCollectionGroup.ReadOnly = requestedGroup.ReadOnly;
|
|
existingCollectionGroup.Manage = requestedGroup.Manage;
|
|
dbContext.CollectionGroups.Update(existingCollectionGroup);
|
|
}
|
|
}
|
|
}
|
|
// Need to save the new collection users/groups before running the bump revision code
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.UserBumpAccountRevisionDateByCollectionIdsAsync(collectionIdsList, organizationId);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
private async Task ReplaceCollectionGroupsAsync(DatabaseContext dbContext, Core.Entities.Collection collection, IEnumerable<CollectionAccessSelection> groups)
|
|
{
|
|
var groupsInOrg = dbContext.Groups.Where(g => g.OrganizationId == collection.OrganizationId);
|
|
var modifiedGroupEntities = dbContext.Groups.Where(x => groups.Select(x => x.Id).Contains(x.Id));
|
|
var target = (from cg in dbContext.CollectionGroups
|
|
join g in modifiedGroupEntities
|
|
on cg.CollectionId equals collection.Id into s_g
|
|
from g in s_g.DefaultIfEmpty()
|
|
where g == null || cg.GroupId == g.Id
|
|
select new { cg, g }).AsNoTracking();
|
|
var source = (from g in modifiedGroupEntities
|
|
from cg in dbContext.CollectionGroups
|
|
.Where(cg => cg.CollectionId == collection.Id && cg.GroupId == g.Id).DefaultIfEmpty()
|
|
select new { cg, g }).AsNoTracking();
|
|
var union = await target
|
|
.Union(source)
|
|
.Where(x =>
|
|
x.cg == null ||
|
|
((x.g == null || x.g.Id == x.cg.GroupId) &&
|
|
(x.cg.CollectionId == collection.Id)))
|
|
.AsNoTracking()
|
|
.ToListAsync();
|
|
var insert = union.Where(x => x.cg == null && groupsInOrg.Any(c => x.g.Id == c.Id))
|
|
.Select(x => new CollectionGroup
|
|
{
|
|
CollectionId = collection.Id,
|
|
GroupId = x.g.Id,
|
|
ReadOnly = groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly,
|
|
HidePasswords = groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords,
|
|
Manage = groups.FirstOrDefault(g => g.Id == x.g.Id).Manage
|
|
}).ToList();
|
|
var update = union
|
|
.Where(
|
|
x => x.g != null &&
|
|
x.cg != null &&
|
|
(x.cg.ReadOnly != groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly ||
|
|
x.cg.HidePasswords != groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords ||
|
|
x.cg.Manage != groups.FirstOrDefault(g => g.Id == x.g.Id).Manage)
|
|
)
|
|
.Select(x => new CollectionGroup
|
|
{
|
|
CollectionId = collection.Id,
|
|
GroupId = x.g.Id,
|
|
ReadOnly = groups.FirstOrDefault(g => g.Id == x.g.Id).ReadOnly,
|
|
HidePasswords = groups.FirstOrDefault(g => g.Id == x.g.Id).HidePasswords,
|
|
Manage = groups.FirstOrDefault(g => g.Id == x.g.Id).Manage,
|
|
});
|
|
var delete = union
|
|
.Where(
|
|
x => x.g == null &&
|
|
x.cg.CollectionId == collection.Id
|
|
)
|
|
.Select(x => new CollectionGroup
|
|
{
|
|
CollectionId = collection.Id,
|
|
GroupId = x.cg.GroupId,
|
|
})
|
|
.ToList();
|
|
|
|
await dbContext.AddRangeAsync(insert);
|
|
dbContext.UpdateRange(update);
|
|
dbContext.RemoveRange(delete);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
|
|
private async Task ReplaceCollectionUsersAsync(DatabaseContext dbContext, Core.Entities.Collection collection, IEnumerable<CollectionAccessSelection> users)
|
|
{
|
|
var usersInOrg = dbContext.OrganizationUsers.Where(u => u.OrganizationId == collection.OrganizationId);
|
|
var modifiedUserEntities = dbContext.OrganizationUsers.Where(x => users.Select(x => x.Id).Contains(x.Id));
|
|
var target = (from cu in dbContext.CollectionUsers
|
|
join u in modifiedUserEntities
|
|
on cu.CollectionId equals collection.Id into s_g
|
|
from u in s_g.DefaultIfEmpty()
|
|
where u == null || cu.OrganizationUserId == u.Id
|
|
select new { cu, u }).AsNoTracking();
|
|
var source = (from u in modifiedUserEntities
|
|
from cu in dbContext.CollectionUsers
|
|
.Where(cu => cu.CollectionId == collection.Id && cu.OrganizationUserId == u.Id).DefaultIfEmpty()
|
|
select new { cu, u }).AsNoTracking();
|
|
var union = await target
|
|
.Union(source)
|
|
.Where(x =>
|
|
x.cu == null ||
|
|
((x.u == null || x.u.Id == x.cu.OrganizationUserId) &&
|
|
(x.cu.CollectionId == collection.Id)))
|
|
.AsNoTracking()
|
|
.ToListAsync();
|
|
var insert = union.Where(x => x.u == null && usersInOrg.Any(c => x.u.Id == c.Id))
|
|
.Select(x => new CollectionUser
|
|
{
|
|
CollectionId = collection.Id,
|
|
OrganizationUserId = x.u.Id,
|
|
ReadOnly = users.FirstOrDefault(u => u.Id == x.u.Id).ReadOnly,
|
|
HidePasswords = users.FirstOrDefault(u => u.Id == x.u.Id).HidePasswords,
|
|
Manage = users.FirstOrDefault(u => u.Id == x.u.Id).Manage,
|
|
}).ToList();
|
|
var update = union
|
|
.Where(
|
|
x => x.u != null &&
|
|
x.cu != null &&
|
|
(x.cu.ReadOnly != users.FirstOrDefault(u => u.Id == x.u.Id).ReadOnly ||
|
|
x.cu.HidePasswords != users.FirstOrDefault(u => u.Id == x.u.Id).HidePasswords ||
|
|
x.cu.Manage != users.FirstOrDefault(u => u.Id == x.u.Id).Manage)
|
|
)
|
|
.Select(x => new CollectionUser
|
|
{
|
|
CollectionId = collection.Id,
|
|
OrganizationUserId = x.u.Id,
|
|
ReadOnly = users.FirstOrDefault(u => u.Id == x.u.Id).ReadOnly,
|
|
HidePasswords = users.FirstOrDefault(u => u.Id == x.u.Id).HidePasswords,
|
|
Manage = users.FirstOrDefault(u => u.Id == x.u.Id).Manage,
|
|
});
|
|
var delete = union
|
|
.Where(
|
|
x => x.u == null &&
|
|
x.cu.CollectionId == collection.Id
|
|
)
|
|
.Select(x => new CollectionUser
|
|
{
|
|
CollectionId = collection.Id,
|
|
OrganizationUserId = x.cu.OrganizationUserId,
|
|
})
|
|
.ToList();
|
|
|
|
await dbContext.AddRangeAsync(insert);
|
|
dbContext.UpdateRange(update);
|
|
dbContext.RemoveRange(delete);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|