Files
server/src/Core/Vault/Services/Implementations/CipherService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1114 lines
42 KiB
C#
Raw Normal View History

using System.Text.Json;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
2019-07-25 15:50:13 -04:00
using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Repositories;
namespace Bit.Core.Vault.Services;
2022-08-29 16:06:55 -04:00
public class CipherService : ICipherService
{
public const long MAX_FILE_SIZE = Constants.FileSize501mb;
public const string MAX_FILE_SIZE_READABLE = "500 MB";
private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository;
2018-07-17 13:34:12 -04:00
private readonly ICollectionRepository _collectionRepository;
2017-02-25 23:38:24 -05:00
private readonly IUserRepository _userRepository;
private readonly IOrganizationRepository _organizationRepository;
2017-04-27 09:19:30 -04:00
private readonly ICollectionCipherRepository _collectionCipherRepository;
2017-05-26 09:44:54 -04:00
private readonly IPushNotificationService _pushService;
private readonly IAttachmentStorageService _attachmentStorageService;
2017-12-01 14:06:16 -05:00
private readonly IEventService _eventService;
2018-08-28 17:40:08 -04:00
private readonly IUserService _userService;
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
private readonly IPolicyService _policyService;
private readonly GlobalSettings _globalSettings;
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
private readonly IReferenceEventService _referenceEventService;
private readonly ICurrentContext _currentContext;
2022-08-29 16:06:55 -04:00
public CipherService(
ICipherRepository cipherRepository,
IFolderRepository folderRepository,
2018-07-17 13:34:12 -04:00
ICollectionRepository collectionRepository,
2017-02-25 23:38:24 -05:00
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
2017-04-27 09:19:30 -04:00
ICollectionCipherRepository collectionCipherRepository,
IPushNotificationService pushService,
2017-12-01 14:06:16 -05:00
IAttachmentStorageService attachmentStorageService,
2018-08-28 17:40:08 -04:00
IEventService eventService,
2018-08-29 09:45:57 -04:00
IUserService userService,
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
IPolicyService policyService,
GlobalSettings globalSettings,
IReferenceEventService referenceEventService,
ICurrentContext currentContext)
2022-08-29 14:53:16 -04:00
{
_cipherRepository = cipherRepository;
_folderRepository = folderRepository;
2018-07-17 13:34:12 -04:00
_collectionRepository = collectionRepository;
2017-02-25 23:38:24 -05:00
_userRepository = userRepository;
_organizationRepository = organizationRepository;
2017-04-27 09:19:30 -04:00
_collectionCipherRepository = collectionCipherRepository;
_pushService = pushService;
2017-12-01 14:06:16 -05:00
_attachmentStorageService = attachmentStorageService;
2018-08-28 17:40:08 -04:00
_eventService = eventService;
_userService = userService;
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
_policyService = policyService;
2018-08-29 09:45:57 -04:00
_globalSettings = globalSettings;
_referenceEventService = referenceEventService;
_currentContext = currentContext;
2022-08-29 14:53:16 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
IEnumerable<Guid> collectionIds = null, bool skipPermissionCheck = false, bool limitCollectionScope = true)
2022-08-29 16:06:55 -04:00
{
if (!skipPermissionCheck && !(await UserCanEditAsync(cipher, savingUserId)))
2022-08-29 16:06:55 -04:00
{
2017-04-27 14:50:22 -04:00
throw new BadRequestException("You do not have permissions to edit this.");
2022-08-29 16:06:55 -04:00
}
if (cipher.Id == default(Guid))
2022-08-29 16:06:55 -04:00
{
if (cipher.OrganizationId.HasValue && collectionIds != null)
2017-04-27 14:50:22 -04:00
{
if (limitCollectionScope)
{
// Set user ID to limit scope of collection ids in the create sproc
cipher.UserId = savingUserId;
}
2017-12-01 14:06:16 -05:00
await _cipherRepository.CreateAsync(cipher, collectionIds);
2017-04-27 14:50:22 -04:00
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.CipherCreated, await _organizationRepository.GetByIdAsync(cipher.OrganizationId.Value), _currentContext));
2017-04-27 14:50:22 -04:00
}
else
{
await _cipherRepository.CreateAsync(cipher);
}
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Created);
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherCreateAsync(cipher, null);
2017-04-27 14:50:22 -04:00
}
2022-08-29 16:06:55 -04:00
else
{
2017-04-27 14:50:22 -04:00
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
cipher.RevisionDate = DateTime.UtcNow;
2017-07-10 16:21:18 -04:00
await _cipherRepository.ReplaceAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Updated);
2017-04-27 14:50:22 -04:00
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, collectionIds);
2022-08-29 16:06:55 -04:00
}
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
public async Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId, DateTime? lastKnownRevisionDate,
IEnumerable<Guid> collectionIds = null, bool skipPermissionCheck = false)
2022-08-29 16:06:55 -04:00
{
if (!skipPermissionCheck && !(await UserCanEditAsync(cipher, savingUserId)))
{
throw new BadRequestException("You do not have permissions to edit this.");
}
cipher.UserId = savingUserId;
if (cipher.Id == default(Guid))
2022-08-29 16:06:55 -04:00
{
if (cipher.OrganizationId.HasValue && collectionIds != null)
{
var existingCollectionIds = (await _collectionRepository.GetManyByOrganizationIdAsync(cipher.OrganizationId.Value)).Select(c => c.Id);
if (collectionIds.Except(existingCollectionIds).Any())
2022-08-29 14:53:16 -04:00
{
throw new BadRequestException("Specified CollectionId does not exist on the specified Organization.");
}
await _cipherRepository.CreateAsync(cipher, collectionIds);
}
2022-08-29 14:53:16 -04:00
else
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
// Make sure the user can save new ciphers to their personal vault
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership);
if (anyPersonalOwnershipPolicies)
2022-08-29 16:06:55 -04:00
{
2017-04-27 14:50:22 -04:00
throw new BadRequestException("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.");
2022-08-29 16:06:55 -04:00
}
2017-07-10 16:21:18 -04:00
await _cipherRepository.CreateAsync(cipher);
2022-08-29 16:06:55 -04:00
}
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Created);
2022-08-29 14:53:16 -04:00
if (cipher.OrganizationId.HasValue)
2022-08-29 16:06:55 -04:00
{
var org = await _organizationRepository.GetByIdAsync(cipher.OrganizationId.Value);
cipher.OrganizationUseTotp = org.UseTotp;
}
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherCreateAsync(cipher, null);
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
else
{
2017-04-27 14:50:22 -04:00
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
cipher.RevisionDate = DateTime.UtcNow;
await _cipherRepository.ReplaceAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Updated);
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, collectionIds);
2022-08-29 16:06:55 -04:00
}
}
public async Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachment)
2022-08-29 16:06:55 -04:00
{
if (attachment == null)
{
throw new BadRequestException("Cipher attachment does not exist");
}
await _attachmentStorageService.UploadNewAttachmentAsync(stream, cipher, attachment);
if (!await ValidateCipherAttachmentFile(cipher, attachment))
{
throw new BadRequestException("File received does not match expected file length.");
}
2022-08-29 16:06:55 -04:00
}
public async Task<(string attachmentId, string uploadUrl)> CreateAttachmentForDelayedUploadAsync(Cipher cipher,
2021-12-14 15:05:07 +00:00
string key, string fileName, long fileSize, bool adminRequest, Guid savingUserId)
2022-08-29 16:06:55 -04:00
{
await ValidateCipherEditForAttachmentAsync(cipher, savingUserId, adminRequest, fileSize);
2022-08-29 16:06:55 -04:00
var attachmentId = Utilities.CoreHelpers.SecureRandomString(32, upper: false, special: false);
var data = new CipherAttachment.MetaData
{
AttachmentId = attachmentId,
FileName = fileName,
Key = key,
Size = fileSize,
Validated = false,
};
2022-08-29 14:53:16 -04:00
var uploadUrl = await _attachmentStorageService.GetAttachmentUploadUrlAsync(cipher, data);
2022-08-29 16:06:55 -04:00
await _cipherRepository.UpdateAttachmentAsync(new CipherAttachment
2022-08-29 14:53:16 -04:00
{
Id = cipher.Id,
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
2021-12-14 15:05:07 +00:00
AttachmentData = JsonSerializer.Serialize(data)
2022-08-29 14:53:16 -04:00
});
cipher.AddAttachment(attachmentId, data);
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
2022-08-29 16:06:55 -04:00
return (attachmentId, uploadUrl);
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
public async Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, string key,
long requestLength, Guid savingUserId, bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
await ValidateCipherEditForAttachmentAsync(cipher, savingUserId, orgAdmin, requestLength);
2022-08-29 16:06:55 -04:00
var attachmentId = Utilities.CoreHelpers.SecureRandomString(32, upper: false, special: false);
var data = new CipherAttachment.MetaData
2022-08-29 16:06:55 -04:00
{
AttachmentId = attachmentId,
2021-12-14 15:05:07 +00:00
FileName = fileName,
Key = key,
2022-08-29 16:06:55 -04:00
};
await _attachmentStorageService.UploadNewAttachmentAsync(stream, cipher, data);
// Must read stream length after it has been saved, otherwise it's 0
2017-07-10 14:30:12 -04:00
data.Size = stream.Length;
try
2022-08-29 16:06:55 -04:00
{
var attachment = new CipherAttachment
{
Id = cipher.Id,
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
AttachmentData = JsonSerializer.Serialize(data)
};
2022-08-29 16:06:55 -04:00
await _cipherRepository.UpdateAttachmentAsync(attachment);
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_AttachmentCreated);
cipher.AddAttachment(attachmentId, data);
if (!await ValidateCipherAttachmentFile(cipher, data))
2022-08-29 16:06:55 -04:00
{
throw new Exception("Content-Length does not match uploaded file size");
2022-08-29 16:06:55 -04:00
}
}
catch
{
// Clean up since this is not transactional
await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, data);
throw;
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
2022-08-29 16:06:55 -04:00
}
public async Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, string key,
long requestLength, string attachmentId, Guid organizationId)
2022-08-29 16:06:55 -04:00
{
try
2022-08-29 16:06:55 -04:00
{
2017-07-10 16:21:18 -04:00
if (requestLength < 1)
{
throw new BadRequestException("No data to attach.");
}
2022-08-29 16:06:55 -04:00
if (cipher.Id == default(Guid))
{
throw new BadRequestException(nameof(cipher.Id));
}
2017-07-10 14:30:12 -04:00
if (cipher.OrganizationId.HasValue)
{
throw new BadRequestException("Cipher belongs to an organization already.");
}
2022-08-29 16:06:55 -04:00
var org = await _organizationRepository.GetByIdAsync(organizationId);
if (org == null || !org.MaxStorageGb.HasValue)
2017-07-10 16:21:18 -04:00
{
2017-07-10 20:48:06 -04:00
throw new BadRequestException("This organization cannot use attachments.");
2017-07-10 16:21:18 -04:00
}
var storageBytesRemaining = org.StorageBytesRemaining();
if (storageBytesRemaining < requestLength)
{
throw new BadRequestException("Not enough storage available for this organization.");
}
2022-08-29 16:06:55 -04:00
var attachments = cipher.GetAttachments();
if (!attachments.ContainsKey(attachmentId))
{
throw new BadRequestException($"Cipher does not own specified attachment");
}
var originalAttachmentMetadata = attachments[attachmentId];
if (originalAttachmentMetadata.TempMetadata != null)
{
throw new BadRequestException("Another process is trying to migrate this attachment");
}
// Clone metadata to be modified and saved into the TempMetadata,
// we cannot change the metadata here directly because if the subsequent endpoint fails
// to be called, then the metadata would stay corrupted.
var attachmentMetadata = CoreHelpers.CloneObject(originalAttachmentMetadata);
attachmentMetadata.AttachmentId = originalAttachmentMetadata.AttachmentId;
originalAttachmentMetadata.TempMetadata = attachmentMetadata;
if (key != null)
{
attachmentMetadata.Key = key;
attachmentMetadata.FileName = fileName;
}
await _attachmentStorageService.UploadShareAttachmentAsync(stream, cipher.Id, organizationId,
attachmentMetadata);
2022-08-29 16:06:55 -04:00
// Previous call may alter metadata
var updatedAttachment = new CipherAttachment
{
Id = cipher.Id,
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
AttachmentData = JsonSerializer.Serialize(originalAttachmentMetadata)
};
await _cipherRepository.UpdateAttachmentAsync(updatedAttachment);
2022-08-29 16:06:55 -04:00
}
catch
{
2018-11-15 12:52:31 -05:00
await _attachmentStorageService.CleanupAsync(cipher.Id);
2022-08-29 16:06:55 -04:00
throw;
}
}
public async Task<bool> ValidateCipherAttachmentFile(Cipher cipher, CipherAttachment.MetaData attachmentData)
2022-08-29 16:06:55 -04:00
{
var (valid, realSize) = await _attachmentStorageService.ValidateFileAsync(cipher, attachmentData, _fileSizeLeeway);
if (!valid || realSize > MAX_FILE_SIZE)
2022-08-29 16:06:55 -04:00
{
// File reported differs in size from that promised. Must be a rogue client. Delete Send
await DeleteAttachmentAsync(cipher, attachmentData);
return false;
2022-08-29 16:06:55 -04:00
}
// Update Send data if necessary
if (realSize != attachmentData.Size)
2022-08-29 16:06:55 -04:00
{
attachmentData.Size = realSize.Value;
}
attachmentData.Validated = true;
2021-12-14 15:05:07 +00:00
var updatedAttachment = new CipherAttachment
{
Id = cipher.Id,
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentData.AttachmentId,
AttachmentData = JsonSerializer.Serialize(attachmentData)
};
await _cipherRepository.UpdateAttachmentAsync(updatedAttachment);
2022-08-29 16:06:55 -04:00
return valid;
}
2022-08-29 14:53:16 -04:00
public async Task<AttachmentResponseData> GetAttachmentDownloadDataAsync(Cipher cipher, string attachmentId)
2022-08-29 16:06:55 -04:00
{
var attachments = cipher?.GetAttachments() ?? new Dictionary<string, CipherAttachment.MetaData>();
2022-08-29 16:06:55 -04:00
if (!attachments.ContainsKey(attachmentId))
2022-08-29 16:06:55 -04:00
{
throw new NotFoundException();
}
2017-04-19 16:00:47 -04:00
var data = attachments[attachmentId];
var response = new AttachmentResponseData
{
Cipher = cipher,
2021-12-14 15:05:07 +00:00
Data = data,
Id = attachmentId,
Url = await _attachmentStorageService.GetAttachmentDownloadUrlAsync(cipher, data),
};
return response;
2022-08-29 16:06:55 -04:00
}
public async Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
if (!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("You do not have permissions to delete this.");
}
await _cipherRepository.DeleteAsync(cipher);
await _attachmentStorageService.DeleteAttachmentsForCipherAsync(cipher.Id);
await _eventService.LogCipherEventAsync(cipher, EventType.Cipher_Deleted);
2019-01-10 22:24:08 -05:00
// push
await _pushService.PushSyncCipherDeleteAsync(cipher);
}
2019-01-10 22:24:08 -05:00
public async Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
var cipherIdsSet = new HashSet<Guid>(cipherIds);
var deletingCiphers = new List<Cipher>();
2019-01-10 22:24:08 -05:00
if (orgAdmin && organizationId.HasValue)
2022-08-29 16:06:55 -04:00
{
2017-06-09 00:30:59 -04:00
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(organizationId.Value);
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
await _cipherRepository.DeleteByIdsOrganizationIdAsync(deletingCiphers.Select(c => c.Id), organizationId.Value);
2022-08-29 16:06:55 -04:00
}
else
{
2017-06-09 00:30:59 -04:00
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
2017-06-09 00:30:59 -04:00
await _cipherRepository.DeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
}
var events = deletingCiphers.Select(c =>
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Deleted, null));
2022-09-14 14:57:05 -04:00
foreach (var eventsBatch in events.Chunk(100))
2017-07-07 11:07:22 -04:00
{
await _eventService.LogCipherEventsAsync(eventsBatch);
2017-07-07 11:07:22 -04:00
}
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCiphersAsync(deletingUserId);
2017-07-07 11:07:22 -04:00
}
public async Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId,
bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
if (!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("You do not have permissions to delete this.");
2017-07-07 11:07:22 -04:00
}
2018-09-25 09:12:50 -04:00
if (!cipher.ContainsAttachment(attachmentId))
{
throw new NotFoundException();
}
2017-06-09 00:30:59 -04:00
await DeleteAttachmentAsync(cipher, cipher.GetAttachments()[attachmentId]);
}
public async Task PurgeAsync(Guid organizationId)
2022-08-29 16:06:55 -04:00
{
var org = await _organizationRepository.GetByIdAsync(organizationId);
if (org == null)
2022-08-29 16:06:55 -04:00
{
2017-06-09 00:30:59 -04:00
throw new NotFoundException();
}
2018-09-25 09:12:50 -04:00
await _cipherRepository.DeleteByOrganizationIdAsync(organizationId);
await _eventService.LogOrganizationEventAsync(org, Bit.Core.Enums.EventType.Organization_PurgedVault);
2022-08-29 16:06:55 -04:00
}
2017-06-09 00:30:59 -04:00
public async Task MoveManyAsync(IEnumerable<Guid> cipherIds, Guid? destinationFolderId, Guid movingUserId)
2022-08-29 16:06:55 -04:00
{
if (destinationFolderId.HasValue)
{
var folder = await _folderRepository.GetByIdAsync(destinationFolderId.Value);
if (folder == null || folder.UserId != movingUserId)
{
2018-11-15 12:52:31 -05:00
throw new BadRequestException("Invalid folder.");
}
2022-08-29 14:53:16 -04:00
}
await _cipherRepository.MoveAsync(cipherIds, destinationFolderId, movingUserId);
2022-08-29 16:06:55 -04:00
// push
2017-06-09 00:30:59 -04:00
await _pushService.PushSyncCiphersAsync(movingUserId);
2022-08-29 16:06:55 -04:00
}
public async Task SaveFolderAsync(Folder folder)
2022-08-29 16:06:55 -04:00
{
if (folder.Id == default(Guid))
{
await _folderRepository.CreateAsync(folder);
// push
await _pushService.PushSyncFolderCreateAsync(folder);
}
else
{
folder.RevisionDate = DateTime.UtcNow;
2018-11-15 12:52:31 -05:00
await _folderRepository.UpsertAsync(folder);
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncFolderUpdateAsync(folder);
2022-08-29 16:06:55 -04:00
}
}
2017-08-30 18:18:39 -04:00
2022-08-29 14:53:16 -04:00
public async Task DeleteFolderAsync(Folder folder)
2022-08-29 16:06:55 -04:00
{
2017-12-01 14:06:16 -05:00
await _folderRepository.DeleteAsync(folder);
2017-07-10 14:30:12 -04:00
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncFolderDeleteAsync(folder);
2022-08-29 16:06:55 -04:00
}
2018-11-15 12:52:31 -05:00
public async Task ShareAsync(Cipher originalCipher, Cipher cipher, Guid organizationId,
IEnumerable<Guid> collectionIds, Guid sharingUserId, DateTime? lastKnownRevisionDate)
2022-08-29 16:06:55 -04:00
{
2018-11-15 12:52:31 -05:00
var attachments = cipher.GetAttachments();
var hasOldAttachments = attachments?.Values?.Any(a => a.Key == null) ?? false;
var updatedCipher = false;
2018-11-15 12:52:31 -05:00
var migratedAttachments = false;
var originalAttachments = CoreHelpers.CloneObject(originalCipher.GetAttachments());
2017-07-10 16:21:18 -04:00
2022-08-29 16:06:55 -04:00
try
{
await ValidateCipherCanBeShared(cipher, sharingUserId, organizationId, lastKnownRevisionDate);
2017-07-10 14:30:12 -04:00
// Sproc will not save this UserId on the cipher. It is used limit scope of the collectionIds.
cipher.UserId = sharingUserId;
cipher.OrganizationId = organizationId;
cipher.RevisionDate = DateTime.UtcNow;
if (hasOldAttachments)
{
var attachmentsWithUpdatedMetadata = originalCipher.GetAttachments();
var attachmentsToUpdateMetadata = CoreHelpers.CloneObject(attachments);
foreach (var updatedMetadata in attachmentsWithUpdatedMetadata.Where(a => a.Value?.TempMetadata != null))
{
if (attachmentsToUpdateMetadata.ContainsKey(updatedMetadata.Key))
{
attachmentsToUpdateMetadata[updatedMetadata.Key] = updatedMetadata.Value.TempMetadata;
}
}
cipher.SetAttachments(attachmentsToUpdateMetadata);
}
if (!await _cipherRepository.ReplaceAsync(cipher, collectionIds))
{
throw new BadRequestException("Unable to save.");
}
updatedCipher = true;
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_Shared);
2017-07-10 16:21:18 -04:00
if (hasOldAttachments)
2022-08-29 16:06:55 -04:00
{
2018-11-15 12:52:31 -05:00
// migrate old attachments
foreach (var attachment in attachments.Values.Where(a => a.TempMetadata != null).Select(a => a.TempMetadata))
2017-07-10 14:30:12 -04:00
{
await _attachmentStorageService.StartShareAttachmentAsync(cipher.Id, organizationId,
attachment);
migratedAttachments = true;
2017-07-10 14:30:12 -04:00
}
2018-11-15 12:52:31 -05:00
// commit attachment migration
2017-07-10 20:48:06 -04:00
await _attachmentStorageService.CleanupAsync(cipher.Id);
2017-07-10 14:30:12 -04:00
}
2022-08-29 16:06:55 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
catch
2018-06-13 14:03:44 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
// roll everything back
if (updatedCipher)
2018-06-13 14:03:44 -04:00
{
if (hasOldAttachments)
{
foreach (var item in originalAttachments)
{
item.Value.TempMetadata = null;
}
originalCipher.SetAttachments(originalAttachments);
}
var currentCollectionsForCipher = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(sharingUserId, originalCipher.Id);
var currentCollectionIdsForCipher = currentCollectionsForCipher.Select(c => c.CollectionId).ToList();
currentCollectionIdsForCipher.RemoveAll(id => collectionIds.Contains(id));
await _collectionCipherRepository.UpdateCollectionsAsync(originalCipher.Id, sharingUserId, currentCollectionIdsForCipher);
await _cipherRepository.ReplaceAsync(originalCipher);
2019-07-25 15:50:13 -04:00
}
2018-06-13 14:03:44 -04:00
if (!hasOldAttachments || !migratedAttachments)
2017-04-12 12:42:00 -04:00
{
throw;
}
if (updatedCipher)
{
await _userRepository.UpdateStorageAsync(sharingUserId);
await _organizationRepository.UpdateStorageAsync(organizationId);
2022-08-29 14:53:16 -04:00
}
foreach (var attachment in attachments.Where(a => a.Value.Key == null))
2017-04-17 23:12:48 -04:00
{
await _attachmentStorageService.RollbackShareAttachmentAsync(cipher.Id, organizationId,
attachment.Value, originalAttachments[attachment.Key].ContainerName);
2017-04-12 12:42:00 -04:00
}
2017-07-10 20:48:06 -04:00
await _attachmentStorageService.CleanupAsync(cipher.Id);
2022-08-29 16:06:55 -04:00
throw;
}
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, collectionIds);
2022-08-29 16:06:55 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
public async Task ShareManyAsync(IEnumerable<(Cipher cipher, DateTime? lastKnownRevisionDate)> cipherInfos,
Guid organizationId, IEnumerable<Guid> collectionIds, Guid sharingUserId)
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
var cipherIds = new List<Guid>();
foreach (var (cipher, lastKnownRevisionDate) in cipherInfos)
2022-08-29 16:06:55 -04:00
{
await ValidateCipherCanBeShared(cipher, sharingUserId, organizationId, lastKnownRevisionDate);
2022-08-29 16:06:55 -04:00
2018-06-13 14:03:44 -04:00
cipher.UserId = null;
cipher.OrganizationId = organizationId;
2017-12-01 14:06:16 -05:00
cipher.RevisionDate = DateTime.UtcNow;
2019-07-25 15:50:13 -04:00
cipherIds.Add(cipher.Id);
2022-08-29 16:06:55 -04:00
}
2017-12-01 14:06:16 -05:00
2017-04-12 12:42:00 -04:00
await _cipherRepository.UpdateCiphersAsync(sharingUserId, cipherInfos.Select(c => c.cipher));
await _collectionCipherRepository.UpdateCollectionsForCiphersAsync(cipherIds, sharingUserId,
organizationId, collectionIds);
2022-08-29 16:06:55 -04:00
var events = cipherInfos.Select(c =>
new Tuple<Cipher, EventType, DateTime?>(c.cipher, EventType.Cipher_Shared, null));
2022-09-14 14:57:05 -04:00
foreach (var eventsBatch in events.Chunk(100))
2022-08-29 16:06:55 -04:00
{
await _eventService.LogCipherEventsAsync(eventsBatch);
2022-08-29 16:06:55 -04:00
}
// push
2018-06-13 14:03:44 -04:00
await _pushService.PushSyncCiphersAsync(sharingUserId);
2022-08-29 16:06:55 -04:00
}
public async Task SaveCollectionsAsync(Cipher cipher, IEnumerable<Guid> collectionIds, Guid savingUserId,
bool orgAdmin)
2022-08-29 16:06:55 -04:00
{
if (cipher.Id == default(Guid))
2022-08-29 16:06:55 -04:00
{
2017-07-10 16:21:18 -04:00
throw new BadRequestException(nameof(cipher.Id));
2022-08-29 16:06:55 -04:00
}
if (!cipher.OrganizationId.HasValue)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("Cipher must belong to an organization.");
2022-08-29 16:06:55 -04:00
}
2017-12-01 14:06:16 -05:00
cipher.RevisionDate = DateTime.UtcNow;
2022-08-29 16:06:55 -04:00
2017-04-12 12:42:00 -04:00
// The sprocs will validate that all collections belong to this org/user and that they have
// proper write permissions.
if (orgAdmin)
{
await _collectionCipherRepository.UpdateCollectionsForAdminAsync(cipher.Id,
cipher.OrganizationId.Value, collectionIds);
}
else
2022-08-29 16:06:55 -04:00
{
if (!(await UserCanEditAsync(cipher, savingUserId)))
{
throw new BadRequestException("You do not have permissions to edit this.");
2022-08-29 14:53:16 -04:00
}
await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds);
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_UpdatedCollections);
2022-08-29 14:53:16 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, collectionIds);
2022-08-29 16:06:55 -04:00
}
public async Task ImportCiphersAsync(
List<Folder> folders,
List<CipherDetails> ciphers,
IEnumerable<KeyValuePair<int, int>> folderRelationships)
2022-08-29 16:06:55 -04:00
{
var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;
2022-08-29 16:06:55 -04:00
// Make sure the user can save new ciphers to their personal vault
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value, PolicyType.PersonalOwnership);
if (anyPersonalOwnershipPolicies)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("You cannot import items into your personal vault because you are " +
"a member of an organization which forbids it.");
}
foreach (var cipher in ciphers)
{
cipher.SetNewId();
if (cipher.UserId.HasValue && cipher.Favorite)
{
cipher.Favorites = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":\"true\"}}";
}
2022-08-29 16:06:55 -04:00
}
var userfoldersIds = (await _folderRepository.GetManyByUserIdAsync(userId ?? Guid.Empty)).Select(f => f.Id).ToList();
//Assign id to the ones that don't exist in DB
//Need to keep the list order to create the relationships
List<Folder> newFolders = new List<Folder>();
foreach (var folder in folders)
{
if (!userfoldersIds.Contains(folder.Id))
{
folder.SetNewId();
newFolders.Add(folder);
}
2022-08-29 16:06:55 -04:00
}
// Create the folder associations based on the newly created folder ids
foreach (var relationship in folderRelationships)
{
var cipher = ciphers.ElementAtOrDefault(relationship.Key);
var folder = folders.ElementAtOrDefault(relationship.Value);
if (cipher == null || folder == null)
{
continue;
}
cipher.Folders = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":" +
$"\"{folder.Id.ToString().ToUpperInvariant()}\"}}";
2022-08-29 16:06:55 -04:00
}
// Create it all
await _cipherRepository.CreateAsync(ciphers, newFolders);
2022-08-29 16:06:55 -04:00
// push
if (userId.HasValue)
2022-08-29 16:06:55 -04:00
{
await _pushService.PushSyncVaultAsync(userId.Value);
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
public async Task ImportCiphersAsync(
List<Collection> collections,
List<CipherDetails> ciphers,
IEnumerable<KeyValuePair<int, int>> collectionRelationships,
Guid importingUserId)
2022-08-29 16:06:55 -04:00
{
var org = collections.Count > 0 ?
await _organizationRepository.GetByIdAsync(collections[0].OrganizationId) :
await _organizationRepository.GetByIdAsync(ciphers.FirstOrDefault(c => c.OrganizationId.HasValue).OrganizationId.Value);
if (collections.Count > 0 && org != null && org.MaxCollections.HasValue)
2022-08-29 16:06:55 -04:00
{
var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id);
if (org.MaxCollections.Value < (collectionCount + collections.Count))
{
throw new BadRequestException("This organization can only have a maximum of " +
$"{org.MaxCollections.Value} collections.");
}
}
// Init. ids for ciphers
foreach (var cipher in ciphers)
{
cipher.SetNewId();
2022-08-29 16:06:55 -04:00
}
var userCollectionsIds = (await _collectionRepository.GetManyByOrganizationIdAsync(org.Id)).Select(c => c.Id).ToList();
//Assign id to the ones that don't exist in DB
//Need to keep the list order to create the relationships
List<Collection> newCollections = new List<Collection>();
foreach (var collection in collections)
{
if (!userCollectionsIds.Contains(collection.Id))
{
collection.SetNewId();
newCollections.Add(collection);
}
}
2018-07-17 13:34:12 -04:00
// Create associations based on the newly assigned ids
var collectionCiphers = new List<CollectionCipher>();
foreach (var relationship in collectionRelationships)
{
var cipher = ciphers.ElementAtOrDefault(relationship.Key);
var collection = collections.ElementAtOrDefault(relationship.Value);
if (cipher == null || collection == null)
{
continue;
}
collectionCiphers.Add(new CollectionCipher
{
CipherId = cipher.Id,
CollectionId = collection.Id
});
}
// Create it all
await _cipherRepository.CreateAsync(ciphers, newCollections, collectionCiphers);
// push
2018-06-13 14:03:44 -04:00
await _pushService.PushSyncVaultAsync(importingUserId);
if (org != null)
2022-08-29 16:06:55 -04:00
{
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.VaultImported, org, _currentContext));
2022-08-29 16:06:55 -04:00
}
}
public async Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false)
{
if (!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId)))
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("You do not have permissions to soft delete this.");
}
if (cipher.DeletedDate.HasValue)
2022-08-29 14:53:16 -04:00
{
// Already soft-deleted, we can safely ignore this
return;
}
cipher.DeletedDate = cipher.RevisionDate = DateTime.UtcNow;
if (cipher is CipherDetails details)
2022-08-29 16:06:55 -04:00
{
await _cipherRepository.UpsertAsync(details);
2022-08-29 16:06:55 -04:00
}
else
2022-08-29 16:06:55 -04:00
{
await _cipherRepository.UpsertAsync(cipher);
2022-08-29 16:06:55 -04:00
}
await _eventService.LogCipherEventAsync(cipher, EventType.Cipher_SoftDeleted);
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
2022-08-29 16:06:55 -04:00
}
public async Task SoftDeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId, Guid? organizationId, bool orgAdmin)
2022-08-29 16:06:55 -04:00
{
var cipherIdsSet = new HashSet<Guid>(cipherIds);
var deletingCiphers = new List<Cipher>();
if (orgAdmin && organizationId.HasValue)
2022-08-29 16:06:55 -04:00
{
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(organizationId.Value);
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
await _cipherRepository.SoftDeleteByIdsOrganizationIdAsync(deletingCiphers.Select(c => c.Id), organizationId.Value);
2022-08-29 16:06:55 -04:00
}
else
2022-08-29 14:53:16 -04:00
{
var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId);
deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList();
await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId);
2022-08-29 16:06:55 -04:00
}
var events = deletingCiphers.Select(c =>
new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_SoftDeleted, null));
2022-09-14 14:57:05 -04:00
foreach (var eventsBatch in events.Chunk(100))
2022-08-29 16:06:55 -04:00
{
await _eventService.LogCipherEventsAsync(eventsBatch);
}
// push
await _pushService.PushSyncCiphersAsync(deletingUserId);
}
public async Task RestoreAsync(Cipher cipher, Guid restoringUserId, bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
if (!orgAdmin && !(await UserCanEditAsync(cipher, restoringUserId)))
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("You do not have permissions to delete this.");
}
if (!cipher.DeletedDate.HasValue)
{
// Already restored, we can safely ignore this
return;
}
cipher.DeletedDate = null;
cipher.RevisionDate = DateTime.UtcNow;
if (cipher is CipherDetails details)
2022-08-29 16:06:55 -04:00
{
await _cipherRepository.UpsertAsync(details);
2022-08-29 16:06:55 -04:00
}
else
2022-08-29 16:06:55 -04:00
{
await _cipherRepository.UpsertAsync(cipher);
2022-08-29 16:06:55 -04:00
}
await _eventService.LogCipherEventAsync(cipher, EventType.Cipher_Restored);
2022-08-29 16:06:55 -04:00
// push
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
2022-08-29 16:06:55 -04:00
}
public async Task<ICollection<CipherOrganizationDetails>> RestoreManyAsync(IEnumerable<Guid> cipherIds, Guid restoringUserId, Guid? organizationId = null, bool orgAdmin = false)
2022-08-29 16:06:55 -04:00
{
if (cipherIds == null || !cipherIds.Any())
{
return new List<CipherOrganizationDetails>();
}
var cipherIdsSet = new HashSet<Guid>(cipherIds);
var restoringCiphers = new List<CipherOrganizationDetails>();
DateTime? revisionDate;
if (orgAdmin && organizationId.HasValue)
{
var ciphers = await _cipherRepository.GetManyOrganizationDetailsByOrganizationIdAsync(organizationId.Value);
restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id)).ToList();
revisionDate = await _cipherRepository.RestoreByIdsOrganizationIdAsync(restoringCiphers.Select(c => c.Id), organizationId.Value);
}
else
{
var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId);
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);
}
var events = restoringCiphers.Select(c =>
{
c.RevisionDate = revisionDate.Value;
c.DeletedDate = null;
return new Tuple<Cipher, EventType, DateTime?>(c, EventType.Cipher_Restored, null);
2022-08-29 16:06:55 -04:00
});
2022-09-14 14:57:05 -04:00
foreach (var eventsBatch in events.Chunk(100))
2022-08-29 16:06:55 -04:00
{
await _eventService.LogCipherEventsAsync(eventsBatch);
2022-08-29 16:06:55 -04:00
}
// push
await _pushService.PushSyncCiphersAsync(restoringUserId);
return restoringCiphers;
}
public async Task<(IEnumerable<CipherOrganizationDetails>, Dictionary<Guid, IGrouping<Guid, CollectionCipher>>)> GetOrganizationCiphers(Guid userId, Guid organizationId)
2022-08-29 16:06:55 -04:00
{
if (!await _currentContext.ViewAllCollections(organizationId) && !await _currentContext.AccessReports(organizationId) && !await _currentContext.AccessImportExport(organizationId))
2022-08-29 16:06:55 -04:00
{
throw new NotFoundException();
}
IEnumerable<CipherOrganizationDetails> orgCiphers;
if (await _currentContext.AccessImportExport(organizationId))
{
// Admins, Owners, Providers and Custom (with import/export permission) can access all items even if not assigned to them
orgCiphers = await _cipherRepository.GetManyOrganizationDetailsByOrganizationIdAsync(organizationId);
2022-08-29 16:06:55 -04:00
}
else
{
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, true);
orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId);
}
var orgCipherIds = orgCiphers.Select(c => c.Id);
var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(organizationId);
var collectionCiphersGroupDict = collectionCiphers
.Where(c => orgCipherIds.Contains(c.CipherId))
.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
return (orgCiphers, collectionCiphersGroupDict);
}
private async Task<bool> UserCanEditAsync(Cipher cipher, Guid userId)
2022-08-29 16:06:55 -04:00
{
if (!cipher.OrganizationId.HasValue && cipher.UserId.HasValue && cipher.UserId.Value == userId)
2022-08-29 16:06:55 -04:00
{
return true;
}
2017-03-25 16:25:10 -04:00
return await _cipherRepository.GetCanEditByIdAsync(userId, cipher.Id);
2022-08-29 16:06:55 -04:00
}
2017-03-25 16:25:10 -04:00
private void ValidateCipherLastKnownRevisionDateAsync(Cipher cipher, DateTime? lastKnownRevisionDate)
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
if (cipher.Id == default || !lastKnownRevisionDate.HasValue)
{
return;
}
2017-05-06 23:23:01 -04:00
if ((cipher.RevisionDate - lastKnownRevisionDate.Value).Duration() > TimeSpan.FromSeconds(1))
2022-08-29 16:06:55 -04:00
{
2017-05-06 23:23:01 -04:00
throw new BadRequestException(
"The cipher you are updating is out of date. Please save your work, sync your vault, and try again."
);
}
2022-08-29 16:06:55 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
private async Task DeleteAttachmentAsync(Cipher cipher, CipherAttachment.MetaData attachmentData)
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
if (attachmentData == null || string.IsNullOrWhiteSpace(attachmentData.AttachmentId))
{
return;
2022-08-29 14:53:16 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentData.AttachmentId);
cipher.DeleteAttachment(attachmentData.AttachmentId);
await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentData);
await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_AttachmentDeleted);
2022-08-29 16:06:55 -04:00
// push
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
2022-08-29 16:06:55 -04:00
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
private async Task ValidateCipherEditForAttachmentAsync(Cipher cipher, Guid savingUserId, bool orgAdmin,
long requestLength)
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
if (!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId)))
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
throw new BadRequestException("You do not have permissions to edit this.");
}
if (requestLength < 1)
{
throw new BadRequestException("No data to attach.");
}
var storageBytesRemaining = await StorageBytesRemainingForCipherAsync(cipher);
if (storageBytesRemaining < requestLength)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("Not enough storage available.");
}
2022-08-29 16:06:55 -04:00
}
private async Task<long> StorageBytesRemainingForCipherAsync(Cipher cipher)
2022-08-29 16:06:55 -04:00
{
var storageBytesRemaining = 0L;
if (cipher.UserId.HasValue)
{
var user = await _userRepository.GetByIdAsync(cipher.UserId.Value);
if (!(await _userService.CanAccessPremium(user)))
{
throw new BadRequestException("You must have premium status to use attachments.");
}
if (user.Premium)
{
storageBytesRemaining = user.StorageBytesRemaining();
}
2022-08-29 16:06:55 -04:00
else
{
// Users that get access to file storage/premium from their organization get the default
// 1 GB max storage.
storageBytesRemaining = user.StorageBytesRemaining(
_globalSettings.SelfHosted ? (short)10240 : (short)1);
}
}
else if (cipher.OrganizationId.HasValue)
{
var org = await _organizationRepository.GetByIdAsync(cipher.OrganizationId.Value);
if (!org.MaxStorageGb.HasValue)
{
throw new BadRequestException("This organization cannot use attachments.");
}
storageBytesRemaining = org.StorageBytesRemaining();
}
return storageBytesRemaining;
}
private async Task ValidateCipherCanBeShared(
Cipher cipher,
Guid sharingUserId,
Guid organizationId,
DateTime? lastKnownRevisionDate)
2022-08-29 16:06:55 -04:00
{
if (cipher.Id == default(Guid))
2022-08-29 14:53:16 -04:00
{
throw new BadRequestException("Cipher must already exist.");
2022-08-29 14:53:16 -04:00
}
2017-07-10 14:30:12 -04:00
if (cipher.OrganizationId.HasValue)
{
throw new BadRequestException("One or more ciphers already belong to an organization.");
}
if (!cipher.UserId.HasValue || cipher.UserId.Value != sharingUserId)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("One or more ciphers do not belong to you.");
2022-08-29 16:06:55 -04:00
}
var attachments = cipher.GetAttachments();
var hasAttachments = attachments?.Any() ?? false;
var org = await _organizationRepository.GetByIdAsync(organizationId);
if (org == null)
2022-08-29 14:53:16 -04:00
{
throw new BadRequestException("Could not find organization.");
}
2022-08-29 14:53:16 -04:00
if (hasAttachments && !org.MaxStorageGb.HasValue)
{
throw new BadRequestException("This organization cannot use attachments.");
}
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
var storageAdjustment = attachments?.Sum(a => a.Value.Size) ?? 0;
if (org.StorageBytesRemaining() < storageAdjustment)
2022-08-29 16:06:55 -04:00
{
Validate cipher updates with revision date (#994) * Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
2020-11-23 08:48:05 -06:00
throw new BadRequestException("Not enough storage available for this organization.");
}
2022-08-29 16:06:55 -04:00
2017-04-27 14:50:22 -04:00
ValidateCipherLastKnownRevisionDateAsync(cipher, lastKnownRevisionDate);
}
}