mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[AC-1330] [AC-1815] [Server] Deprecate access control indicator - UserCipherDetails (#3372)
* Create UserCipherDetails_v2 and update logic to remove AccessAll * Create v2 variants of all sprocs that rely on it * Add feature flag logic to call old or new sproc * Make equivalent changes to EF queries
This commit is contained in:
parent
b062ab8043
commit
12667dbb3f
@ -2,6 +2,8 @@
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Admin.Services;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -21,19 +23,28 @@ public class UsersController : Controller
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IAccessControlService _accessControlService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public UsersController(
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IPaymentService paymentService,
|
||||
GlobalSettings globalSettings,
|
||||
IAccessControlService accessControlService)
|
||||
IAccessControlService accessControlService,
|
||||
ICurrentContext currentContext,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_paymentService = paymentService;
|
||||
_globalSettings = globalSettings;
|
||||
_accessControlService = accessControlService;
|
||||
_currentContext = currentContext;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.User_List_View)]
|
||||
@ -69,7 +80,7 @@ public class UsersController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id, useFlexibleCollections: UseFlexibleCollections);
|
||||
return View(new UserViewModel(user, ciphers));
|
||||
}
|
||||
|
||||
@ -82,7 +93,7 @@ public class UsersController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id, useFlexibleCollections: UseFlexibleCollections);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||
return View(new UserEditModel(user, ciphers, billingInfo, _globalSettings));
|
||||
}
|
||||
|
@ -58,6 +58,8 @@ public class AccountsController : Controller
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public AccountsController(
|
||||
GlobalSettings globalSettings,
|
||||
@ -415,7 +417,7 @@ public class AccountsController : Controller
|
||||
var ciphers = new List<Cipher>();
|
||||
if (model.Ciphers.Any())
|
||||
{
|
||||
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
|
||||
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: UseFlexibleCollections);
|
||||
ciphers.AddRange(existingCiphers
|
||||
.Join(model.Ciphers, c => c.Id, c => c.Id, (existing, c) => c.ToCipher(existing)));
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ public class CiphersController : Controller
|
||||
private readonly ILogger<CiphersController> _logger;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion);
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public CiphersController(
|
||||
ICipherRepository cipherRepository,
|
||||
@ -50,7 +54,8 @@ public class CiphersController : Controller
|
||||
IProviderService providerService,
|
||||
ICurrentContext currentContext,
|
||||
ILogger<CiphersController> logger,
|
||||
GlobalSettings globalSettings)
|
||||
GlobalSettings globalSettings,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_cipherRepository = cipherRepository;
|
||||
_collectionCipherRepository = collectionCipherRepository;
|
||||
@ -61,13 +66,14 @@ public class CiphersController : Controller
|
||||
_currentContext = currentContext;
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<CipherResponseModel> Get(string id)
|
||||
public async Task<CipherResponseModel> Get(Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -91,17 +97,16 @@ public class CiphersController : Controller
|
||||
|
||||
[HttpGet("{id}/full-details")]
|
||||
[HttpGet("{id}/details")]
|
||||
public async Task<CipherDetailsResponseModel> GetDetails(string id)
|
||||
public async Task<CipherDetailsResponseModel> GetDetails(Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipherId = new Guid(id);
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, cipherId);
|
||||
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id);
|
||||
return new CipherDetailsResponseModel(cipher, _globalSettings, collectionCiphers);
|
||||
}
|
||||
|
||||
@ -111,7 +116,7 @@ public class CiphersController : Controller
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var hasOrgs = _currentContext.Organizations?.Any() ?? false;
|
||||
// TODO: Use hasOrgs proper for cipher listing here?
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, true || hasOrgs);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: true || hasOrgs);
|
||||
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
|
||||
if (hasOrgs)
|
||||
{
|
||||
@ -175,7 +180,7 @@ public class CiphersController : Controller
|
||||
public async Task<CipherResponseModel> Put(Guid id, [FromBody] CipherRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(id, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -247,25 +252,23 @@ public class CiphersController : Controller
|
||||
|
||||
[HttpPut("{id}/partial")]
|
||||
[HttpPost("{id}/partial")]
|
||||
public async Task<CipherResponseModel> PutPartial(string id, [FromBody] CipherPartialRequestModel model)
|
||||
public async Task<CipherResponseModel> PutPartial(Guid id, [FromBody] CipherPartialRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var folderId = string.IsNullOrWhiteSpace(model.FolderId) ? null : (Guid?)new Guid(model.FolderId);
|
||||
var cipherId = new Guid(id);
|
||||
await _cipherRepository.UpdatePartialAsync(cipherId, userId, folderId, model.Favorite);
|
||||
await _cipherRepository.UpdatePartialAsync(id, userId, folderId, model.Favorite);
|
||||
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
var response = new CipherResponseModel(cipher, _globalSettings);
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpPut("{id}/share")]
|
||||
[HttpPost("{id}/share")]
|
||||
public async Task<CipherResponseModel> PutShare(string id, [FromBody] CipherShareRequestModel model)
|
||||
public async Task<CipherResponseModel> PutShare(Guid id, [FromBody] CipherShareRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipherId = new Guid(id);
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId);
|
||||
var cipher = await _cipherRepository.GetByIdAsync(id);
|
||||
if (cipher == null || cipher.UserId != userId ||
|
||||
!await _currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
|
||||
{
|
||||
@ -279,17 +282,17 @@ public class CiphersController : Controller
|
||||
await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
|
||||
model.CollectionIds.Select(c => new Guid(c)), userId, model.Cipher.LastKnownRevisionDate);
|
||||
|
||||
var sharedCipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
||||
var sharedCipher = await GetByIdAsync(id, userId);
|
||||
var response = new CipherResponseModel(sharedCipher, _globalSettings);
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpPut("{id}/collections")]
|
||||
[HttpPost("{id}/collections")]
|
||||
public async Task PutCollections(string id, [FromBody] CipherCollectionsRequestModel model)
|
||||
public async Task PutCollections(Guid id, [FromBody] CipherCollectionsRequestModel model)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
||||
{
|
||||
@ -318,10 +321,10 @@ public class CiphersController : Controller
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[HttpPost("{id}/delete")]
|
||||
public async Task Delete(string id)
|
||||
public async Task Delete(Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -380,10 +383,10 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
[HttpPut("{id}/delete")]
|
||||
public async Task PutDelete(string id)
|
||||
public async Task PutDelete(Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -436,10 +439,10 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
[HttpPut("{id}/restore")]
|
||||
public async Task<CipherResponseModel> PutRestore(string id)
|
||||
public async Task<CipherResponseModel> PutRestore(Guid id)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -526,7 +529,7 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, false);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: false);
|
||||
var ciphersDict = ciphers.ToDictionary(c => c.Id);
|
||||
|
||||
var shareCiphers = new List<(Cipher, DateTime?)>();
|
||||
@ -581,13 +584,12 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("{id}/attachment/v2")]
|
||||
public async Task<AttachmentUploadDataResponseModel> PostAttachment(string id, [FromBody] AttachmentRequestModel request)
|
||||
public async Task<AttachmentUploadDataResponseModel> PostAttachment(Guid id, [FromBody] AttachmentRequestModel request)
|
||||
{
|
||||
var idGuid = new Guid(id);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = request.AdminRequest ?
|
||||
await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid) :
|
||||
await _cipherRepository.GetByIdAsync(idGuid, userId);
|
||||
await _cipherRepository.GetOrganizationDetailsByIdAsync(id) :
|
||||
await GetByIdAsync(id, userId);
|
||||
|
||||
if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue ||
|
||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))))
|
||||
@ -615,11 +617,10 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}/attachment/{attachmentId}/renew")]
|
||||
public async Task<AttachmentUploadDataResponseModel> RenewFileUploadUrl(string id, string attachmentId)
|
||||
public async Task<AttachmentUploadDataResponseModel> RenewFileUploadUrl(Guid id, string attachmentId)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipherId = new Guid(id);
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
var attachments = cipher?.GetAttachments();
|
||||
|
||||
if (attachments == null || !attachments.ContainsKey(attachmentId) || attachments[attachmentId].Validated)
|
||||
@ -638,7 +639,7 @@ public class CiphersController : Controller
|
||||
[SelfHosted(SelfHostedOnly = true)]
|
||||
[RequestSizeLimit(Constants.FileSize501mb)]
|
||||
[DisableFormValueModelBinding]
|
||||
public async Task PostFileForExistingAttachment(string id, string attachmentId)
|
||||
public async Task PostFileForExistingAttachment(Guid id, string attachmentId)
|
||||
{
|
||||
if (!Request?.ContentType.Contains("multipart/") ?? true)
|
||||
{
|
||||
@ -646,7 +647,7 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
var attachments = cipher?.GetAttachments();
|
||||
if (attachments == null || !attachments.ContainsKey(attachmentId))
|
||||
{
|
||||
@ -664,13 +665,12 @@ public class CiphersController : Controller
|
||||
[Obsolete("Deprecated Attachments API", false)]
|
||||
[RequestSizeLimit(Constants.FileSize101mb)]
|
||||
[DisableFormValueModelBinding]
|
||||
public async Task<CipherResponseModel> PostAttachment(string id)
|
||||
public async Task<CipherResponseModel> PostAttachment(Guid id)
|
||||
{
|
||||
ValidateAttachment();
|
||||
|
||||
var idGuid = new Guid(id);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -711,10 +711,10 @@ public class CiphersController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}/attachment/{attachmentId}")]
|
||||
public async Task<AttachmentResponseModel> GetAttachmentData(string id, string attachmentId)
|
||||
public async Task<AttachmentResponseModel> GetAttachmentData(Guid id, string attachmentId)
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
var result = await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||
return new AttachmentResponseModel(result);
|
||||
}
|
||||
@ -742,11 +742,10 @@ public class CiphersController : Controller
|
||||
|
||||
[HttpDelete("{id}/attachment/{attachmentId}")]
|
||||
[HttpPost("{id}/attachment/{attachmentId}/delete")]
|
||||
public async Task DeleteAttachment(string id, string attachmentId)
|
||||
public async Task DeleteAttachment(Guid id, string attachmentId)
|
||||
{
|
||||
var idGuid = new Guid(id);
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
||||
var cipher = await GetByIdAsync(id, userId);
|
||||
if (cipher == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@ -836,4 +835,9 @@ public class CiphersController : Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CipherDetails> GetByIdAsync(Guid cipherId, Guid userId)
|
||||
{
|
||||
return await _cipherRepository.GetByIdAsync(cipherId, userId, UseFlexibleCollections);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Bit.Api.Vault.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -30,6 +32,11 @@ public class SyncController : Controller
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public SyncController(
|
||||
IUserService userService,
|
||||
@ -41,7 +48,9 @@ public class SyncController : Controller
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
ISendRepository sendRepository,
|
||||
GlobalSettings globalSettings)
|
||||
GlobalSettings globalSettings,
|
||||
ICurrentContext currentContext,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_userService = userService;
|
||||
_folderRepository = folderRepository;
|
||||
@ -53,6 +62,8 @@ public class SyncController : Controller
|
||||
_policyRepository = policyRepository;
|
||||
_sendRepository = sendRepository;
|
||||
_globalSettings = globalSettings;
|
||||
_currentContext = currentContext;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@ -74,7 +85,7 @@ public class SyncController : Controller
|
||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||
|
||||
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, hasEnabledOrgs);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: UseFlexibleCollections, withOrganizations: hasEnabledOrgs);
|
||||
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
|
||||
|
||||
IEnumerable<CollectionDetails> collections = null;
|
||||
|
@ -5,6 +5,7 @@ using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -33,6 +34,11 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
private readonly IPasswordHasher<User> _passwordHasher;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public EmergencyAccessService(
|
||||
IEmergencyAccessRepository emergencyAccessRepository,
|
||||
@ -46,7 +52,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
IPasswordHasher<User> passwordHasher,
|
||||
GlobalSettings globalSettings,
|
||||
IOrganizationService organizationService,
|
||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer)
|
||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
|
||||
ICurrentContext currentContext,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_emergencyAccessRepository = emergencyAccessRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -60,6 +68,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
_globalSettings = globalSettings;
|
||||
_organizationService = organizationService;
|
||||
_dataProtectorTokenizer = dataProtectorTokenizer;
|
||||
_currentContext = currentContext;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
public async Task<EmergencyAccess> InviteAsync(User invitingUser, string email, EmergencyAccessType type, int waitTime)
|
||||
@ -387,7 +397,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
throw new BadRequestException("Emergency Access not valid.");
|
||||
}
|
||||
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(emergencyAccess.GrantorId, false);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(emergencyAccess.GrantorId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: false);
|
||||
|
||||
return new EmergencyAccessViewData
|
||||
{
|
||||
@ -405,7 +415,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
throw new BadRequestException("Emergency Access not valid.");
|
||||
}
|
||||
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, emergencyAccess.GrantorId);
|
||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, emergencyAccess.GrantorId, UseFlexibleCollections);
|
||||
return await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,11 @@ namespace Bit.Core.Vault.Repositories;
|
||||
|
||||
public interface ICipherRepository : IRepository<Cipher, Guid>
|
||||
{
|
||||
Task<CipherDetails> GetByIdAsync(Guid id, Guid userId);
|
||||
Task<CipherDetails> GetByIdAsync(Guid id, Guid userId, bool useFlexibleCollections);
|
||||
Task<CipherOrganizationDetails> GetOrganizationDetailsByIdAsync(Guid id);
|
||||
Task<ICollection<CipherOrganizationDetails>> GetManyOrganizationDetailsByOrganizationIdAsync(Guid organizationId);
|
||||
Task<bool> GetCanEditByIdAsync(Guid userId, Guid cipherId);
|
||||
Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true);
|
||||
Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true);
|
||||
Task<ICollection<Cipher>> GetManyByOrganizationIdAsync(Guid organizationId);
|
||||
Task CreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds);
|
||||
Task CreateAsync(CipherDetails cipher);
|
||||
@ -23,9 +23,9 @@ public interface ICipherRepository : IRepository<Cipher, Guid>
|
||||
Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
||||
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
||||
Task DeleteAttachmentAsync(Guid cipherId, string attachmentId);
|
||||
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task DeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections);
|
||||
Task DeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId);
|
||||
Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId, bool useFlexibleCollections);
|
||||
Task DeleteByUserIdAsync(Guid userId);
|
||||
Task DeleteByOrganizationIdAsync(Guid organizationId);
|
||||
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, IEnumerable<Send> sends);
|
||||
@ -33,9 +33,9 @@ public interface ICipherRepository : IRepository<Cipher, Guid>
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<CollectionCipher> collectionCiphers, IEnumerable<CollectionUser> collectionUsers);
|
||||
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections);
|
||||
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId);
|
||||
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections);
|
||||
Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||
Task DeleteDeletedAsync(DateTime deletedDateBefore);
|
||||
}
|
||||
|
@ -38,6 +38,10 @@ public class CipherService : ICipherService
|
||||
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
private bool UseFlexibleCollections =>
|
||||
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
public CipherService(
|
||||
ICipherRepository cipherRepository,
|
||||
@ -54,7 +58,8 @@ public class CipherService : ICipherService
|
||||
IPolicyService policyService,
|
||||
GlobalSettings globalSettings,
|
||||
IReferenceEventService referenceEventService,
|
||||
ICurrentContext currentContext)
|
||||
ICurrentContext currentContext,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_cipherRepository = cipherRepository;
|
||||
_folderRepository = folderRepository;
|
||||
@ -71,6 +76,7 @@ public class CipherService : ICipherService
|
||||
_globalSettings = globalSettings;
|
||||
_referenceEventService = referenceEventService;
|
||||
_currentContext = currentContext;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
|
||||
@ -424,9 +430,10 @@ public class CipherService : ICipherService
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId, useFlexibleCollections: UseFlexibleCollections);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
|
||||
await _cipherRepository.DeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
|
||||
|
||||
await _cipherRepository.DeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId, UseFlexibleCollections);
|
||||
}
|
||||
|
||||
var events = deletingCiphers.Select(c =>
|
||||
@ -478,7 +485,7 @@ public class CipherService : ICipherService
|
||||
}
|
||||
}
|
||||
|
||||
await _cipherRepository.MoveAsync(cipherIds, destinationFolderId, movingUserId);
|
||||
await _cipherRepository.MoveAsync(cipherIds, destinationFolderId, movingUserId, UseFlexibleCollections);
|
||||
// push
|
||||
await _pushService.PushSyncCiphersAsync(movingUserId);
|
||||
}
|
||||
@ -865,9 +872,10 @@ public class CipherService : ICipherService
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId, useFlexibleCollections: UseFlexibleCollections);
|
||||
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
|
||||
await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
|
||||
|
||||
await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId, UseFlexibleCollections);
|
||||
}
|
||||
|
||||
var events = deletingCiphers.Select(c =>
|
||||
@ -930,9 +938,10 @@ public class CipherService : ICipherService
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId, useFlexibleCollections: UseFlexibleCollections);
|
||||
restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(c => (CipherOrganizationDetails)c).ToList();
|
||||
revisionDate = await _cipherRepository.RestoreAsync(restoringCiphers.Select(c => c.Id), restoringUserId);
|
||||
|
||||
revisionDate = await _cipherRepository.RestoreAsync(restoringCiphers.Select(c => c.Id), restoringUserId, UseFlexibleCollections);
|
||||
}
|
||||
|
||||
var events = restoringCiphers.Select(c =>
|
||||
@ -967,7 +976,7 @@ public class CipherService : ICipherService
|
||||
}
|
||||
else
|
||||
{
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, true);
|
||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: true);
|
||||
orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -18,19 +19,24 @@ public class CollectController : Controller
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
public CollectController(
|
||||
ICurrentContext currentContext,
|
||||
IEventService eventService,
|
||||
ICipherRepository cipherRepository,
|
||||
IOrganizationRepository organizationRepository)
|
||||
IOrganizationRepository organizationRepository,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_eventService = eventService;
|
||||
_cipherRepository = cipherRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
bool UseFlexibleCollections => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody] IEnumerable<EventModel> model)
|
||||
{
|
||||
@ -69,8 +75,10 @@ public class CollectController : Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
var useFlexibleCollections = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||
cipher = await _cipherRepository.GetByIdAsync(eventModel.CipherId.Value,
|
||||
_currentContext.UserId.Value);
|
||||
_currentContext.UserId.Value,
|
||||
useFlexibleCollections);
|
||||
}
|
||||
if (cipher == null)
|
||||
{
|
||||
|
@ -22,12 +22,16 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
: base(connectionString, readOnlyConnectionString)
|
||||
{ }
|
||||
|
||||
public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId)
|
||||
public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
var sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[CipherDetails_ReadByIdUserId_V2]"
|
||||
: $"[{Schema}].[CipherDetails_ReadByIdUserId]";
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<CipherDetails>(
|
||||
$"[{Schema}].[CipherDetails_ReadByIdUserId]",
|
||||
sprocName,
|
||||
new { Id = id, UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
@ -75,12 +79,14 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true)
|
||||
public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true)
|
||||
{
|
||||
string sprocName = null;
|
||||
if (withOrganizations)
|
||||
{
|
||||
sprocName = $"[{Schema}].[CipherDetails_ReadByUserId]";
|
||||
sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[CipherDetails_ReadByUserId_V2]"
|
||||
: $"[{Schema}].[CipherDetails_ReadByUserId]";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -228,12 +234,16 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
var sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[Cipher_Delete_V2]"
|
||||
: $"[{Schema}].[Cipher_Delete]";
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Cipher_Delete]",
|
||||
sprocName,
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
@ -261,12 +271,16 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId)
|
||||
public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
var sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[Cipher_Move_V2]"
|
||||
: $"[{Schema}].[Cipher_Move]";
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Cipher_Move]",
|
||||
sprocName,
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), FolderId = folderId, UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
@ -657,23 +671,31 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
var sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[Cipher_SoftDelete_V2]"
|
||||
: $"[{Schema}].[Cipher_SoftDelete]";
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Cipher_SoftDelete]",
|
||||
sprocName,
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
var sprocName = useFlexibleCollections
|
||||
? $"[{Schema}].[Cipher_Restore_V2]"
|
||||
: $"[{Schema}].[Cipher_Restore]";
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteScalarAsync<DateTime>(
|
||||
$"[{Schema}].[Cipher_Restore]",
|
||||
sprocName,
|
||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
|
@ -8,11 +8,21 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
||||
public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
{
|
||||
private readonly Guid? _userId;
|
||||
public UserCipherDetailsQuery(Guid? userId)
|
||||
private readonly bool _useFlexibleCollections;
|
||||
public UserCipherDetailsQuery(Guid? userId, bool useFlexibleCollections)
|
||||
{
|
||||
_userId = userId;
|
||||
_useFlexibleCollections = useFlexibleCollections;
|
||||
}
|
||||
|
||||
public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext)
|
||||
{
|
||||
return _useFlexibleCollections
|
||||
? Run_VNext(dbContext)
|
||||
: Run_VCurrent(dbContext);
|
||||
}
|
||||
|
||||
private IQueryable<CipherDetails> Run_VCurrent(DatabaseContext dbContext)
|
||||
{
|
||||
var query = from c in dbContext.Ciphers
|
||||
|
||||
@ -78,6 +88,71 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||
return union;
|
||||
}
|
||||
|
||||
private IQueryable<CipherDetails> Run_VNext(DatabaseContext dbContext)
|
||||
{
|
||||
var query = from c in dbContext.Ciphers
|
||||
|
||||
join ou in dbContext.OrganizationUsers
|
||||
on new { CipherUserId = c.UserId, c.OrganizationId, UserId = _userId, Status = OrganizationUserStatusType.Confirmed } equals
|
||||
new { CipherUserId = (Guid?)null, OrganizationId = (Guid?)ou.OrganizationId, ou.UserId, ou.Status }
|
||||
|
||||
join o in dbContext.Organizations
|
||||
on new { c.OrganizationId, OuOrganizationId = ou.OrganizationId, Enabled = true } equals
|
||||
new { OrganizationId = (Guid?)o.Id, OuOrganizationId = o.Id, o.Enabled }
|
||||
|
||||
join cc in dbContext.CollectionCiphers
|
||||
on c.Id equals cc.CipherId into cc_g
|
||||
from cc in cc_g.DefaultIfEmpty()
|
||||
|
||||
join cu in dbContext.CollectionUsers
|
||||
on new { cc.CollectionId, OrganizationUserId = ou.Id } equals
|
||||
new { cu.CollectionId, cu.OrganizationUserId } into cu_g
|
||||
from cu in cu_g.DefaultIfEmpty()
|
||||
|
||||
join gu in dbContext.GroupUsers
|
||||
on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals
|
||||
new { CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g
|
||||
from gu in gu_g.DefaultIfEmpty()
|
||||
|
||||
join g in dbContext.Groups
|
||||
on gu.GroupId equals g.Id into g_g
|
||||
from g in g_g.DefaultIfEmpty()
|
||||
|
||||
join cg in dbContext.CollectionGroups
|
||||
on new { cc.CollectionId, gu.GroupId } equals
|
||||
new { cg.CollectionId, cg.GroupId } into cg_g
|
||||
from cg in cg_g.DefaultIfEmpty()
|
||||
|
||||
where cu.CollectionId != null || cg.CollectionId != null
|
||||
|
||||
select c;
|
||||
|
||||
var query2 = from c in dbContext.Ciphers
|
||||
where c.UserId == _userId
|
||||
select c;
|
||||
|
||||
var union = query.Union(query2).Select(c => new CipherDetails
|
||||
{
|
||||
Id = c.Id,
|
||||
UserId = c.UserId,
|
||||
OrganizationId = c.OrganizationId,
|
||||
Type = c.Type,
|
||||
Data = c.Data,
|
||||
Attachments = c.Attachments,
|
||||
CreationDate = c.CreationDate,
|
||||
RevisionDate = c.RevisionDate,
|
||||
DeletedDate = c.DeletedDate,
|
||||
Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"),
|
||||
FolderId = GetFolderId(_userId, c),
|
||||
Edit = true,
|
||||
Reprompt = c.Reprompt,
|
||||
ViewPassword = true,
|
||||
OrganizationUseTotp = false,
|
||||
Key = c.Key
|
||||
});
|
||||
return union;
|
||||
}
|
||||
|
||||
private static Guid? GetFolderId(Guid? userId, Cipher cipher)
|
||||
{
|
||||
try
|
||||
|
@ -199,9 +199,9 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task DeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
await ToggleCipherStates(ids, userId, CipherStateAction.HardDelete);
|
||||
await ToggleCipherStates(ids, userId, CipherStateAction.HardDelete, useFlexibleCollections);
|
||||
}
|
||||
|
||||
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
|
||||
@ -302,12 +302,12 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId)
|
||||
public async Task<CipherDetails> GetByIdAsync(Guid id, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var userCipherDetails = new UserCipherDetailsQuery(userId);
|
||||
var userCipherDetails = new UserCipherDetailsQuery(userId, useFlexibleCollections);
|
||||
var data = await userCipherDetails.Run(dbContext).FirstOrDefaultAsync(c => c.Id == id);
|
||||
return data;
|
||||
}
|
||||
@ -347,13 +347,13 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true)
|
||||
public async Task<ICollection<CipherDetails>> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
IQueryable<CipherDetails> cipherDetailsView = withOrganizations ?
|
||||
new UserCipherDetailsQuery(userId).Run(dbContext) :
|
||||
new UserCipherDetailsQuery(userId, useFlexibleCollections).Run(dbContext) :
|
||||
new CipherDetailsQuery(userId).Run(dbContext);
|
||||
if (!withOrganizations)
|
||||
{
|
||||
@ -395,13 +395,13 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId)
|
||||
public async Task MoveAsync(IEnumerable<Guid> ids, Guid? folderId, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var cipherEntities = dbContext.Ciphers.Where(c => ids.Contains(c.Id));
|
||||
var userCipherDetails = new UserCipherDetailsQuery(userId).Run(dbContext);
|
||||
var userCipherDetails = new UserCipherDetailsQuery(userId, useFlexibleCollections).Run(dbContext);
|
||||
var idsToMove = from ucd in userCipherDetails
|
||||
join c in cipherEntities
|
||||
on ucd.Id equals c.Id
|
||||
@ -632,9 +632,9 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
return await ToggleCipherStates(ids, userId, CipherStateAction.Restore);
|
||||
return await ToggleCipherStates(ids, userId, CipherStateAction.Restore, useFlexibleCollections);
|
||||
}
|
||||
|
||||
public async Task<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId)
|
||||
@ -662,12 +662,12 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId)
|
||||
public async Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId, bool useFlexibleCollections)
|
||||
{
|
||||
await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete);
|
||||
await ToggleCipherStates(ids, userId, CipherStateAction.SoftDelete, useFlexibleCollections);
|
||||
}
|
||||
|
||||
private async Task<DateTime> ToggleCipherStates(IEnumerable<Guid> ids, Guid userId, CipherStateAction action)
|
||||
private async Task<DateTime> ToggleCipherStates(IEnumerable<Guid> ids, Guid userId, CipherStateAction action, bool useFlexibleCollections)
|
||||
{
|
||||
static bool FilterDeletedDate(CipherStateAction action, CipherDetails ucd)
|
||||
{
|
||||
@ -682,7 +682,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var userCipherDetailsQuery = new UserCipherDetailsQuery(userId);
|
||||
var userCipherDetailsQuery = new UserCipherDetailsQuery(userId, useFlexibleCollections);
|
||||
var cipherEntitiesToCheck = await (dbContext.Ciphers.Where(c => ids.Contains(c.Id))).ToListAsync();
|
||||
var query = from ucd in await (userCipherDetailsQuery.Run(dbContext)).ToListAsync()
|
||||
join c in cipherEntitiesToCheck
|
||||
|
53
src/Sql/Vault/dbo/Functions/UserCipherDetails_V2.sql
Normal file
53
src/Sql/Vault/dbo/Functions/UserCipherDetails_V2.sql
Normal file
@ -0,0 +1,53 @@
|
||||
CREATE FUNCTION [dbo].[UserCipherDetails_V2](@UserId UNIQUEIDENTIFIER)
|
||||
RETURNS TABLE
|
||||
AS RETURN
|
||||
WITH [CTE] AS (
|
||||
SELECT
|
||||
[Id],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[OrganizationUser]
|
||||
WHERE
|
||||
[UserId] = @UserId
|
||||
AND [Status] = 2 -- Confirmed
|
||||
)
|
||||
SELECT
|
||||
C.*,
|
||||
COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) AS [Edit],
|
||||
COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) AS [ViewPassword],
|
||||
CASE
|
||||
WHEN O.[UseTotp] = 1
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END [OrganizationUseTotp]
|
||||
FROM
|
||||
[dbo].[CipherDetails](@UserId) C
|
||||
INNER JOIN
|
||||
[CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE])
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
|
||||
WHERE
|
||||
CU.[CollectionId] IS NOT NULL
|
||||
OR CG.[CollectionId] IS NOT NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
*,
|
||||
1 [Edit],
|
||||
1 [ViewPassword],
|
||||
0 [OrganizationUseTotp]
|
||||
FROM
|
||||
[dbo].[CipherDetails](@UserId)
|
||||
WHERE
|
||||
[UserId] = @UserId
|
@ -0,0 +1,16 @@
|
||||
CREATE PROCEDURE [dbo].[CipherDetails_ReadByIdUserId_V2]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT TOP 1
|
||||
*
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
ORDER BY
|
||||
[Edit] DESC
|
||||
END
|
@ -0,0 +1,11 @@
|
||||
CREATE PROCEDURE [dbo].[CipherDetails_ReadByUserId_V2]
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
END
|
@ -0,0 +1,73 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_Delete_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[Attachments] BIT NOT NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId],
|
||||
CASE WHEN [Attachments] IS NULL THEN 0 ELSE 1 END
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
-- Delete ciphers
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Cleanup orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrgId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
-- Cleanup user
|
||||
DECLARE @UserCiphersWithStorageCount INT
|
||||
SELECT
|
||||
@UserCiphersWithStorageCount = COUNT(1)
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[UserId] IS NOT NULL
|
||||
AND [Attachments] = 1
|
||||
|
||||
IF @UserCiphersWithStorageCount > 0
|
||||
BEGIN
|
||||
EXEC [dbo].[User_UpdateStorage] @UserId
|
||||
END
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
END
|
@ -0,0 +1,36 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_Move_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@FolderId AS UNIQUEIDENTIFIER,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
|
||||
DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
|
||||
|
||||
;WITH [IdsToMoveCTE] AS (
|
||||
SELECT
|
||||
[Id]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
)
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[Folders] =
|
||||
CASE
|
||||
WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN
|
||||
CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}')
|
||||
WHEN @FolderId IS NOT NULL THEN
|
||||
JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50)))
|
||||
ELSE
|
||||
JSON_MODIFY([Folders], @UserIdPath, NULL)
|
||||
END
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM [IdsToMoveCTE])
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
END
|
@ -0,0 +1,62 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_Restore_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [DeletedDate] IS NOT NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = NULL,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Bump orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
-- Bump user
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
@ -0,0 +1,60 @@
|
||||
CREATE PROCEDURE [dbo].[Cipher_SoftDelete_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [DeletedDate] IS NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
-- Delete ciphers
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = @UtcNow,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Cleanup orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
END
|
@ -34,10 +34,10 @@ public class CiphersControllerTests
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>()
|
||||
.GetByIdAsync(cipherId, userId)
|
||||
.GetByIdAsync(cipherId, userId, Arg.Any<bool>())
|
||||
.Returns(Task.FromResult(cipherDetails));
|
||||
|
||||
var result = await sutProvider.Sut.PutPartial(cipherId.ToString(), new CipherPartialRequestModel { Favorite = isFavorite, FolderId = folderId.ToString() });
|
||||
var result = await sutProvider.Sut.PutPartial(cipherId, new CipherPartialRequestModel { Favorite = isFavorite, FolderId = folderId.ToString() });
|
||||
|
||||
Assert.Equal(folderId, result.FolderId);
|
||||
Assert.Equal(isFavorite, result.Favorite);
|
||||
|
@ -107,7 +107,7 @@ public class SyncControllerTests
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
@ -198,7 +198,7 @@ public class SyncControllerTests
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
@ -272,7 +272,7 @@ public class SyncControllerTests
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
@ -335,7 +335,7 @@ public class SyncControllerTests
|
||||
.GetManyByUserIdAsync(default);
|
||||
|
||||
await cipherRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
.GetManyByUserIdAsync(default, useFlexibleCollections: default);
|
||||
|
||||
await sendRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
|
@ -675,9 +675,9 @@ public class CipherServiceTests
|
||||
cipher.RevisionDate = previousRevisionDate;
|
||||
}
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(restoringUserId).Returns(ciphers);
|
||||
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(restoringUserId, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
|
||||
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId).Returns(revisionDate);
|
||||
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId, Arg.Any<bool>()).Returns(revisionDate);
|
||||
|
||||
await sutProvider.Sut.RestoreManyAsync(cipherIds, restoringUserId);
|
||||
|
||||
@ -789,8 +789,8 @@ public class CipherServiceTests
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyOrganizationDetailsByOrganizationIdAsync(default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreAsync(default, default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default, useFlexibleCollections: default);
|
||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreAsync(default, default, default);
|
||||
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default);
|
||||
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCiphersAsync(default);
|
||||
}
|
||||
|
@ -0,0 +1,334 @@
|
||||
-- Flexible Collections: create new UserCipherDetails sproc that doesn't use AccessAll logic
|
||||
|
||||
CREATE OR ALTER FUNCTION [dbo].[UserCipherDetails_V2](@UserId UNIQUEIDENTIFIER)
|
||||
RETURNS TABLE
|
||||
AS RETURN
|
||||
WITH [CTE] AS (
|
||||
SELECT
|
||||
[Id],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[OrganizationUser]
|
||||
WHERE
|
||||
[UserId] = @UserId
|
||||
AND [Status] = 2 -- Confirmed
|
||||
)
|
||||
SELECT
|
||||
C.*,
|
||||
COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) AS [Edit],
|
||||
COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) AS [ViewPassword],
|
||||
CASE
|
||||
WHEN O.[UseTotp] = 1
|
||||
THEN 1
|
||||
ELSE 0
|
||||
END [OrganizationUseTotp]
|
||||
FROM
|
||||
[dbo].[CipherDetails](@UserId) C
|
||||
INNER JOIN
|
||||
[CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE])
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
|
||||
LEFT JOIN
|
||||
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
|
||||
WHERE
|
||||
CU.[CollectionId] IS NOT NULL
|
||||
OR CG.[CollectionId] IS NOT NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
*,
|
||||
1 [Edit],
|
||||
1 [ViewPassword],
|
||||
0 [OrganizationUseTotp]
|
||||
FROM
|
||||
[dbo].[CipherDetails](@UserId)
|
||||
WHERE
|
||||
[UserId] = @UserId
|
||||
GO
|
||||
|
||||
-- Create v2 sprocs for all sprocs that call UserCipherDetails
|
||||
|
||||
-- CipherDetails_ReadByIdUserId_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_ReadByIdUserId_V2]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT TOP 1
|
||||
*
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
ORDER BY
|
||||
[Edit] DESC
|
||||
END
|
||||
GO
|
||||
|
||||
-- CipherDetails_ReadByUserId_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_ReadByUserId_V2]
|
||||
@UserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
END
|
||||
GO
|
||||
|
||||
-- Cipher_Delete_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Delete_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[Attachments] BIT NOT NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId],
|
||||
CASE WHEN [Attachments] IS NULL THEN 0 ELSE 1 END
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
-- Delete ciphers
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Cleanup orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[Organization_UpdateStorage] @OrgId
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
-- Cleanup user
|
||||
DECLARE @UserCiphersWithStorageCount INT
|
||||
SELECT
|
||||
@UserCiphersWithStorageCount = COUNT(1)
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[UserId] IS NOT NULL
|
||||
AND [Attachments] = 1
|
||||
|
||||
IF @UserCiphersWithStorageCount > 0
|
||||
BEGIN
|
||||
EXEC [dbo].[User_UpdateStorage] @UserId
|
||||
END
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
END
|
||||
GO
|
||||
|
||||
-- Cipher_Move_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Move_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@FolderId AS UNIQUEIDENTIFIER,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
|
||||
DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
|
||||
|
||||
;WITH [IdsToMoveCTE] AS (
|
||||
SELECT
|
||||
[Id]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM @Ids)
|
||||
)
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[Folders] =
|
||||
CASE
|
||||
WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN
|
||||
CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}')
|
||||
WHEN @FolderId IS NOT NULL THEN
|
||||
JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50)))
|
||||
ELSE
|
||||
JSON_MODIFY([Folders], @UserIdPath, NULL)
|
||||
END
|
||||
WHERE
|
||||
[Id] IN (SELECT * FROM [IdsToMoveCTE])
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
END
|
||||
GO
|
||||
|
||||
-- Cipher_Restore_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Restore_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [DeletedDate] IS NOT NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = NULL,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Bump orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
-- Bump user
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
|
||||
SELECT @UtcNow
|
||||
END
|
||||
GO
|
||||
|
||||
-- Cipher_SoftDelete_V2
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Cipher_SoftDelete_V2]
|
||||
@Ids AS [dbo].[GuidIdArray] READONLY,
|
||||
@UserId AS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
CREATE TABLE #Temp
|
||||
(
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[UserId] UNIQUEIDENTIFIER NULL,
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL
|
||||
)
|
||||
|
||||
INSERT INTO #Temp
|
||||
SELECT
|
||||
[Id],
|
||||
[UserId],
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[UserCipherDetails_V2](@UserId)
|
||||
WHERE
|
||||
[Edit] = 1
|
||||
AND [DeletedDate] IS NULL
|
||||
AND [Id] IN (SELECT * FROM @Ids)
|
||||
|
||||
-- Delete ciphers
|
||||
DECLARE @UtcNow DATETIME2(7) = GETUTCDATE();
|
||||
UPDATE
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
[DeletedDate] = @UtcNow,
|
||||
[RevisionDate] = @UtcNow
|
||||
WHERE
|
||||
[Id] IN (SELECT [Id] FROM #Temp)
|
||||
|
||||
-- Cleanup orgs
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER
|
||||
DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR
|
||||
SELECT
|
||||
[OrganizationId]
|
||||
FROM
|
||||
#Temp
|
||||
WHERE
|
||||
[OrganizationId] IS NOT NULL
|
||||
GROUP BY
|
||||
[OrganizationId]
|
||||
OPEN [OrgCursor]
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
WHILE @@FETCH_STATUS = 0 BEGIN
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
FETCH NEXT FROM [OrgCursor] INTO @OrgId
|
||||
END
|
||||
CLOSE [OrgCursor]
|
||||
DEALLOCATE [OrgCursor]
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
|
||||
|
||||
DROP TABLE #Temp
|
||||
END
|
||||
GO
|
Loading…
Reference in New Issue
Block a user