mirror of
https://github.com/bitwarden/server.git
synced 2025-05-22 12:04:27 -05: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.Models;
|
||||||
using Bit.Admin.Services;
|
using Bit.Admin.Services;
|
||||||
using Bit.Admin.Utilities;
|
using Bit.Admin.Utilities;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -21,19 +23,28 @@ public class UsersController : Controller
|
|||||||
private readonly IPaymentService _paymentService;
|
private readonly IPaymentService _paymentService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IAccessControlService _accessControlService;
|
private readonly IAccessControlService _accessControlService;
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public UsersController(
|
public UsersController(
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IAccessControlService accessControlService)
|
IAccessControlService accessControlService,
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_paymentService = paymentService;
|
_paymentService = paymentService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_accessControlService = accessControlService;
|
_accessControlService = accessControlService;
|
||||||
|
_currentContext = currentContext;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequirePermission(Permission.User_List_View)]
|
[RequirePermission(Permission.User_List_View)]
|
||||||
@ -69,7 +80,7 @@ public class UsersController : Controller
|
|||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id, useFlexibleCollections: UseFlexibleCollections);
|
||||||
return View(new UserViewModel(user, ciphers));
|
return View(new UserViewModel(user, ciphers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +93,7 @@ public class UsersController : Controller
|
|||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id, useFlexibleCollections: UseFlexibleCollections);
|
||||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||||
return View(new UserEditModel(user, ciphers, billingInfo, _globalSettings));
|
return View(new UserEditModel(user, ciphers, billingInfo, _globalSettings));
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,8 @@ public class AccountsController : Controller
|
|||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public AccountsController(
|
public AccountsController(
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
@ -415,7 +417,7 @@ public class AccountsController : Controller
|
|||||||
var ciphers = new List<Cipher>();
|
var ciphers = new List<Cipher>();
|
||||||
if (model.Ciphers.Any())
|
if (model.Ciphers.Any())
|
||||||
{
|
{
|
||||||
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
|
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: UseFlexibleCollections);
|
||||||
ciphers.AddRange(existingCiphers
|
ciphers.AddRange(existingCiphers
|
||||||
.Join(model.Ciphers, c => c.Id, c => c.Id, (existing, c) => c.ToCipher(existing)));
|
.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 ILogger<CiphersController> _logger;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion);
|
private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion);
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public CiphersController(
|
public CiphersController(
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
@ -50,7 +54,8 @@ public class CiphersController : Controller
|
|||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
ILogger<CiphersController> logger,
|
ILogger<CiphersController> logger,
|
||||||
GlobalSettings globalSettings)
|
GlobalSettings globalSettings,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_collectionCipherRepository = collectionCipherRepository;
|
_collectionCipherRepository = collectionCipherRepository;
|
||||||
@ -61,13 +66,14 @@ public class CiphersController : Controller
|
|||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<CipherResponseModel> Get(string id)
|
public async Task<CipherResponseModel> Get(Guid id)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -91,17 +97,16 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpGet("{id}/full-details")]
|
[HttpGet("{id}/full-details")]
|
||||||
[HttpGet("{id}/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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipherId = new Guid(id);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, cipherId);
|
var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id);
|
||||||
return new CipherDetailsResponseModel(cipher, _globalSettings, collectionCiphers);
|
return new CipherDetailsResponseModel(cipher, _globalSettings, collectionCiphers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +116,7 @@ public class CiphersController : Controller
|
|||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var hasOrgs = _currentContext.Organizations?.Any() ?? false;
|
var hasOrgs = _currentContext.Organizations?.Any() ?? false;
|
||||||
// TODO: Use hasOrgs proper for cipher listing here?
|
// 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;
|
Dictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
|
||||||
if (hasOrgs)
|
if (hasOrgs)
|
||||||
{
|
{
|
||||||
@ -175,7 +180,7 @@ public class CiphersController : Controller
|
|||||||
public async Task<CipherResponseModel> Put(Guid id, [FromBody] CipherRequestModel model)
|
public async Task<CipherResponseModel> Put(Guid id, [FromBody] CipherRequestModel model)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(id, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -247,25 +252,23 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpPut("{id}/partial")]
|
[HttpPut("{id}/partial")]
|
||||||
[HttpPost("{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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var folderId = string.IsNullOrWhiteSpace(model.FolderId) ? null : (Guid?)new Guid(model.FolderId);
|
var folderId = string.IsNullOrWhiteSpace(model.FolderId) ? null : (Guid?)new Guid(model.FolderId);
|
||||||
var cipherId = new Guid(id);
|
await _cipherRepository.UpdatePartialAsync(id, userId, folderId, model.Favorite);
|
||||||
await _cipherRepository.UpdatePartialAsync(cipherId, userId, folderId, model.Favorite);
|
|
||||||
|
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
var response = new CipherResponseModel(cipher, _globalSettings);
|
var response = new CipherResponseModel(cipher, _globalSettings);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}/share")]
|
[HttpPut("{id}/share")]
|
||||||
[HttpPost("{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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipherId = new Guid(id);
|
var cipher = await _cipherRepository.GetByIdAsync(id);
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId);
|
|
||||||
if (cipher == null || cipher.UserId != userId ||
|
if (cipher == null || cipher.UserId != userId ||
|
||||||
!await _currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId)))
|
!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),
|
await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
|
||||||
model.CollectionIds.Select(c => new Guid(c)), userId, model.Cipher.LastKnownRevisionDate);
|
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);
|
var response = new CipherResponseModel(sharedCipher, _globalSettings);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}/collections")]
|
[HttpPut("{id}/collections")]
|
||||||
[HttpPost("{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 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 ||
|
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||||
!await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
!await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
||||||
{
|
{
|
||||||
@ -318,10 +321,10 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[HttpPost("{id}/delete")]
|
[HttpPost("{id}/delete")]
|
||||||
public async Task Delete(string id)
|
public async Task Delete(Guid id)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -380,10 +383,10 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}/delete")]
|
[HttpPut("{id}/delete")]
|
||||||
public async Task PutDelete(string id)
|
public async Task PutDelete(Guid id)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -436,10 +439,10 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}/restore")]
|
[HttpPut("{id}/restore")]
|
||||||
public async Task<CipherResponseModel> PutRestore(string id)
|
public async Task<CipherResponseModel> PutRestore(Guid id)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -526,7 +529,7 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
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 ciphersDict = ciphers.ToDictionary(c => c.Id);
|
||||||
|
|
||||||
var shareCiphers = new List<(Cipher, DateTime?)>();
|
var shareCiphers = new List<(Cipher, DateTime?)>();
|
||||||
@ -581,13 +584,12 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/attachment/v2")]
|
[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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = request.AdminRequest ?
|
var cipher = request.AdminRequest ?
|
||||||
await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid) :
|
await _cipherRepository.GetOrganizationDetailsByIdAsync(id) :
|
||||||
await _cipherRepository.GetByIdAsync(idGuid, userId);
|
await GetByIdAsync(id, userId);
|
||||||
|
|
||||||
if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue ||
|
if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue ||
|
||||||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))))
|
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))))
|
||||||
@ -615,11 +617,10 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/attachment/{attachmentId}/renew")]
|
[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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipherId = new Guid(id);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(cipherId, userId);
|
|
||||||
var attachments = cipher?.GetAttachments();
|
var attachments = cipher?.GetAttachments();
|
||||||
|
|
||||||
if (attachments == null || !attachments.ContainsKey(attachmentId) || attachments[attachmentId].Validated)
|
if (attachments == null || !attachments.ContainsKey(attachmentId) || attachments[attachmentId].Validated)
|
||||||
@ -638,7 +639,7 @@ public class CiphersController : Controller
|
|||||||
[SelfHosted(SelfHostedOnly = true)]
|
[SelfHosted(SelfHostedOnly = true)]
|
||||||
[RequestSizeLimit(Constants.FileSize501mb)]
|
[RequestSizeLimit(Constants.FileSize501mb)]
|
||||||
[DisableFormValueModelBinding]
|
[DisableFormValueModelBinding]
|
||||||
public async Task PostFileForExistingAttachment(string id, string attachmentId)
|
public async Task PostFileForExistingAttachment(Guid id, string attachmentId)
|
||||||
{
|
{
|
||||||
if (!Request?.ContentType.Contains("multipart/") ?? true)
|
if (!Request?.ContentType.Contains("multipart/") ?? true)
|
||||||
{
|
{
|
||||||
@ -646,7 +647,7 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
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();
|
var attachments = cipher?.GetAttachments();
|
||||||
if (attachments == null || !attachments.ContainsKey(attachmentId))
|
if (attachments == null || !attachments.ContainsKey(attachmentId))
|
||||||
{
|
{
|
||||||
@ -664,13 +665,12 @@ public class CiphersController : Controller
|
|||||||
[Obsolete("Deprecated Attachments API", false)]
|
[Obsolete("Deprecated Attachments API", false)]
|
||||||
[RequestSizeLimit(Constants.FileSize101mb)]
|
[RequestSizeLimit(Constants.FileSize101mb)]
|
||||||
[DisableFormValueModelBinding]
|
[DisableFormValueModelBinding]
|
||||||
public async Task<CipherResponseModel> PostAttachment(string id)
|
public async Task<CipherResponseModel> PostAttachment(Guid id)
|
||||||
{
|
{
|
||||||
ValidateAttachment();
|
ValidateAttachment();
|
||||||
|
|
||||||
var idGuid = new Guid(id);
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
@ -711,10 +711,10 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/attachment/{attachmentId}")]
|
[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 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);
|
var result = await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||||
return new AttachmentResponseModel(result);
|
return new AttachmentResponseModel(result);
|
||||||
}
|
}
|
||||||
@ -742,11 +742,10 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpDelete("{id}/attachment/{attachmentId}")]
|
[HttpDelete("{id}/attachment/{attachmentId}")]
|
||||||
[HttpPost("{id}/attachment/{attachmentId}/delete")]
|
[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 userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(idGuid, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
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.Api.Vault.Models.Response;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -30,6 +32,11 @@ public class SyncController : Controller
|
|||||||
private readonly IPolicyRepository _policyRepository;
|
private readonly IPolicyRepository _policyRepository;
|
||||||
private readonly ISendRepository _sendRepository;
|
private readonly ISendRepository _sendRepository;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public SyncController(
|
public SyncController(
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
@ -41,7 +48,9 @@ public class SyncController : Controller
|
|||||||
IProviderUserRepository providerUserRepository,
|
IProviderUserRepository providerUserRepository,
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
ISendRepository sendRepository,
|
ISendRepository sendRepository,
|
||||||
GlobalSettings globalSettings)
|
GlobalSettings globalSettings,
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_folderRepository = folderRepository;
|
_folderRepository = folderRepository;
|
||||||
@ -53,6 +62,8 @@ public class SyncController : Controller
|
|||||||
_policyRepository = policyRepository;
|
_policyRepository = policyRepository;
|
||||||
_sendRepository = sendRepository;
|
_sendRepository = sendRepository;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
|
_currentContext = currentContext;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
@ -74,7 +85,7 @@ public class SyncController : Controller
|
|||||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||||
|
|
||||||
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
|
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);
|
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
|
||||||
|
|
||||||
IEnumerable<CollectionDetails> collections = null;
|
IEnumerable<CollectionDetails> collections = null;
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Auth.Enums;
|
|||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -33,6 +34,11 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
private readonly IPasswordHasher<User> _passwordHasher;
|
private readonly IPasswordHasher<User> _passwordHasher;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public EmergencyAccessService(
|
public EmergencyAccessService(
|
||||||
IEmergencyAccessRepository emergencyAccessRepository,
|
IEmergencyAccessRepository emergencyAccessRepository,
|
||||||
@ -46,7 +52,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
IPasswordHasher<User> passwordHasher,
|
IPasswordHasher<User> passwordHasher,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IOrganizationService organizationService,
|
IOrganizationService organizationService,
|
||||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer)
|
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_emergencyAccessRepository = emergencyAccessRepository;
|
_emergencyAccessRepository = emergencyAccessRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -60,6 +68,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
_dataProtectorTokenizer = dataProtectorTokenizer;
|
_dataProtectorTokenizer = dataProtectorTokenizer;
|
||||||
|
_currentContext = currentContext;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmergencyAccess> InviteAsync(User invitingUser, string email, EmergencyAccessType type, int waitTime)
|
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.");
|
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
|
return new EmergencyAccessViewData
|
||||||
{
|
{
|
||||||
@ -405,7 +415,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
throw new BadRequestException("Emergency Access not valid.");
|
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);
|
return await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@ namespace Bit.Core.Vault.Repositories;
|
|||||||
|
|
||||||
public interface ICipherRepository : IRepository<Cipher, Guid>
|
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<CipherOrganizationDetails> GetOrganizationDetailsByIdAsync(Guid id);
|
||||||
Task<ICollection<CipherOrganizationDetails>> GetManyOrganizationDetailsByOrganizationIdAsync(Guid organizationId);
|
Task<ICollection<CipherOrganizationDetails>> GetManyOrganizationDetailsByOrganizationIdAsync(Guid organizationId);
|
||||||
Task<bool> GetCanEditByIdAsync(Guid userId, Guid cipherId);
|
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<ICollection<Cipher>> GetManyByOrganizationIdAsync(Guid organizationId);
|
||||||
Task CreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds);
|
Task CreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds);
|
||||||
Task CreateAsync(CipherDetails cipher);
|
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 UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite);
|
||||||
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
Task UpdateAttachmentAsync(CipherAttachment attachment);
|
||||||
Task DeleteAttachmentAsync(Guid cipherId, string attachmentId);
|
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 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 DeleteByUserIdAsync(Guid userId);
|
||||||
Task DeleteByOrganizationIdAsync(Guid organizationId);
|
Task DeleteByOrganizationIdAsync(Guid organizationId);
|
||||||
Task UpdateUserKeysAndCiphersAsync(User user, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, IEnumerable<Send> sends);
|
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<Folder> folders);
|
||||||
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||||
IEnumerable<CollectionCipher> collectionCiphers, IEnumerable<CollectionUser> collectionUsers);
|
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 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<DateTime> RestoreByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
|
||||||
Task DeleteDeletedAsync(DateTime deletedDateBefore);
|
Task DeleteDeletedAsync(DateTime deletedDateBefore);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,10 @@ public class CipherService : ICipherService
|
|||||||
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
|
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
private readonly IReferenceEventService _referenceEventService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
|
private bool UseFlexibleCollections =>
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
public CipherService(
|
public CipherService(
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
@ -54,7 +58,8 @@ public class CipherService : ICipherService
|
|||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IReferenceEventService referenceEventService,
|
IReferenceEventService referenceEventService,
|
||||||
ICurrentContext currentContext)
|
ICurrentContext currentContext,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_folderRepository = folderRepository;
|
_folderRepository = folderRepository;
|
||||||
@ -71,6 +76,7 @@ public class CipherService : ICipherService
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_referenceEventService = referenceEventService;
|
_referenceEventService = referenceEventService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
|
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
|
||||||
@ -424,9 +430,10 @@ public class CipherService : ICipherService
|
|||||||
}
|
}
|
||||||
else
|
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();
|
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 =>
|
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
|
// push
|
||||||
await _pushService.PushSyncCiphersAsync(movingUserId);
|
await _pushService.PushSyncCiphersAsync(movingUserId);
|
||||||
}
|
}
|
||||||
@ -865,9 +872,10 @@ public class CipherService : ICipherService
|
|||||||
}
|
}
|
||||||
else
|
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();
|
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 =>
|
var events = deletingCiphers.Select(c =>
|
||||||
@ -930,9 +938,10 @@ public class CipherService : ICipherService
|
|||||||
}
|
}
|
||||||
else
|
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();
|
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 =>
|
var events = restoringCiphers.Select(c =>
|
||||||
@ -967,7 +976,7 @@ public class CipherService : ICipherService
|
|||||||
}
|
}
|
||||||
else
|
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);
|
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.Enums;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -18,19 +19,24 @@ public class CollectController : Controller
|
|||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly ICipherRepository _cipherRepository;
|
private readonly ICipherRepository _cipherRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public CollectController(
|
public CollectController(
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
ICipherRepository cipherRepository,
|
ICipherRepository cipherRepository,
|
||||||
IOrganizationRepository organizationRepository)
|
IOrganizationRepository organizationRepository,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_cipherRepository = cipherRepository;
|
_cipherRepository = cipherRepository;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UseFlexibleCollections => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Post([FromBody] IEnumerable<EventModel> model)
|
public async Task<IActionResult> Post([FromBody] IEnumerable<EventModel> model)
|
||||||
{
|
{
|
||||||
@ -69,8 +75,10 @@ public class CollectController : Controller
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var useFlexibleCollections = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext);
|
||||||
cipher = await _cipherRepository.GetByIdAsync(eventModel.CipherId.Value,
|
cipher = await _cipherRepository.GetByIdAsync(eventModel.CipherId.Value,
|
||||||
_currentContext.UserId.Value);
|
_currentContext.UserId.Value,
|
||||||
|
useFlexibleCollections);
|
||||||
}
|
}
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
|
@ -22,12 +22,16 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
|||||||
: base(connectionString, readOnlyConnectionString)
|
: 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))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.QueryAsync<CipherDetails>(
|
var results = await connection.QueryAsync<CipherDetails>(
|
||||||
$"[{Schema}].[CipherDetails_ReadByIdUserId]",
|
sprocName,
|
||||||
new { Id = id, UserId = userId },
|
new { Id = id, UserId = userId },
|
||||||
commandType: CommandType.StoredProcedure);
|
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;
|
string sprocName = null;
|
||||||
if (withOrganizations)
|
if (withOrganizations)
|
||||||
{
|
{
|
||||||
sprocName = $"[{Schema}].[CipherDetails_ReadByUserId]";
|
sprocName = useFlexibleCollections
|
||||||
|
? $"[{Schema}].[CipherDetails_ReadByUserId_V2]"
|
||||||
|
: $"[{Schema}].[CipherDetails_ReadByUserId]";
|
||||||
}
|
}
|
||||||
else
|
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))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.ExecuteAsync(
|
var results = await connection.ExecuteAsync(
|
||||||
$"[{Schema}].[Cipher_Delete]",
|
sprocName,
|
||||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||||
commandType: CommandType.StoredProcedure);
|
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))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.ExecuteAsync(
|
var results = await connection.ExecuteAsync(
|
||||||
$"[{Schema}].[Cipher_Move]",
|
sprocName,
|
||||||
new { Ids = ids.ToGuidIdArrayTVP(), FolderId = folderId, UserId = userId },
|
new { Ids = ids.ToGuidIdArrayTVP(), FolderId = folderId, UserId = userId },
|
||||||
commandType: CommandType.StoredProcedure);
|
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))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.ExecuteAsync(
|
var results = await connection.ExecuteAsync(
|
||||||
$"[{Schema}].[Cipher_SoftDelete]",
|
sprocName,
|
||||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||||
commandType: CommandType.StoredProcedure);
|
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))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.ExecuteScalarAsync<DateTime>(
|
var results = await connection.ExecuteScalarAsync<DateTime>(
|
||||||
$"[{Schema}].[Cipher_Restore]",
|
sprocName,
|
||||||
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId },
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
@ -8,11 +8,21 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
|
|||||||
public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
||||||
{
|
{
|
||||||
private readonly Guid? _userId;
|
private readonly Guid? _userId;
|
||||||
public UserCipherDetailsQuery(Guid? userId)
|
private readonly bool _useFlexibleCollections;
|
||||||
|
public UserCipherDetailsQuery(Guid? userId, bool useFlexibleCollections)
|
||||||
{
|
{
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
|
_useFlexibleCollections = useFlexibleCollections;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IQueryable<CipherDetails> Run(DatabaseContext dbContext)
|
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
|
var query = from c in dbContext.Ciphers
|
||||||
|
|
||||||
@ -78,6 +88,71 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
|
|||||||
return union;
|
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)
|
private static Guid? GetFolderId(Guid? userId, Cipher cipher)
|
||||||
{
|
{
|
||||||
try
|
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)
|
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())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
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);
|
var data = await userCipherDetails.Run(dbContext).FirstOrDefaultAsync(c => c.Id == id);
|
||||||
return data;
|
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())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
IQueryable<CipherDetails> cipherDetailsView = withOrganizations ?
|
IQueryable<CipherDetails> cipherDetailsView = withOrganizations ?
|
||||||
new UserCipherDetailsQuery(userId).Run(dbContext) :
|
new UserCipherDetailsQuery(userId, useFlexibleCollections).Run(dbContext) :
|
||||||
new CipherDetailsQuery(userId).Run(dbContext);
|
new CipherDetailsQuery(userId).Run(dbContext);
|
||||||
if (!withOrganizations)
|
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())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
var cipherEntities = dbContext.Ciphers.Where(c => ids.Contains(c.Id));
|
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
|
var idsToMove = from ucd in userCipherDetails
|
||||||
join c in cipherEntities
|
join c in cipherEntities
|
||||||
on ucd.Id equals c.Id
|
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)
|
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)
|
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())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
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 cipherEntitiesToCheck = await (dbContext.Ciphers.Where(c => ids.Contains(c.Id))).ToListAsync();
|
||||||
var query = from ucd in await (userCipherDetailsQuery.Run(dbContext)).ToListAsync()
|
var query = from ucd in await (userCipherDetailsQuery.Run(dbContext)).ToListAsync()
|
||||||
join c in cipherEntitiesToCheck
|
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>()
|
sutProvider.GetDependency<ICipherRepository>()
|
||||||
.GetByIdAsync(cipherId, userId)
|
.GetByIdAsync(cipherId, userId, Arg.Any<bool>())
|
||||||
.Returns(Task.FromResult(cipherDetails));
|
.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(folderId, result.FolderId);
|
||||||
Assert.Equal(isFavorite, result.Favorite);
|
Assert.Equal(isFavorite, result.Favorite);
|
||||||
|
@ -107,7 +107,7 @@ public class SyncControllerTests
|
|||||||
.Returns(providerUserOrganizationDetails);
|
.Returns(providerUserOrganizationDetails);
|
||||||
|
|
||||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||||
|
|
||||||
sendRepository
|
sendRepository
|
||||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||||
@ -198,7 +198,7 @@ public class SyncControllerTests
|
|||||||
.Returns(providerUserOrganizationDetails);
|
.Returns(providerUserOrganizationDetails);
|
||||||
|
|
||||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||||
|
|
||||||
sendRepository
|
sendRepository
|
||||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||||
@ -272,7 +272,7 @@ public class SyncControllerTests
|
|||||||
.Returns(providerUserOrganizationDetails);
|
.Returns(providerUserOrganizationDetails);
|
||||||
|
|
||||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any<bool>()).Returns(ciphers);
|
||||||
|
|
||||||
sendRepository
|
sendRepository
|
||||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||||
@ -335,7 +335,7 @@ public class SyncControllerTests
|
|||||||
.GetManyByUserIdAsync(default);
|
.GetManyByUserIdAsync(default);
|
||||||
|
|
||||||
await cipherRepository.ReceivedWithAnyArgs(1)
|
await cipherRepository.ReceivedWithAnyArgs(1)
|
||||||
.GetManyByUserIdAsync(default);
|
.GetManyByUserIdAsync(default, useFlexibleCollections: default);
|
||||||
|
|
||||||
await sendRepository.ReceivedWithAnyArgs(1)
|
await sendRepository.ReceivedWithAnyArgs(1)
|
||||||
.GetManyByUserIdAsync(default);
|
.GetManyByUserIdAsync(default);
|
||||||
|
@ -675,9 +675,9 @@ public class CipherServiceTests
|
|||||||
cipher.RevisionDate = previousRevisionDate;
|
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);
|
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);
|
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().GetManyOrganizationDetailsByOrganizationIdAsync(default);
|
||||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default);
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, 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().GetManyByUserIdAsync(default, useFlexibleCollections: default);
|
||||||
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreAsync(default, default);
|
await sutProvider.GetDependency<ICipherRepository>().DidNotReceiveWithAnyArgs().RestoreAsync(default, default, default);
|
||||||
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default);
|
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default);
|
||||||
await sutProvider.GetDependency<IPushNotificationService>().DidNotReceiveWithAnyArgs().PushSyncCiphersAsync(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…
x
Reference in New Issue
Block a user