mirror of
https://github.com/bitwarden/server.git
synced 2024-11-26 12:55:17 +01:00
support for attachments keys
load existing items and set attachments on key update
This commit is contained in:
parent
73cc221deb
commit
7cda459127
@ -12,6 +12,8 @@ using Bit.Core.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -21,6 +23,8 @@ namespace Bit.Api.Controllers
|
|||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly ICipherRepository _cipherRepository;
|
||||||
|
private readonly IFolderRepository _folderRepository;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly ILicensingService _licenseService;
|
private readonly ILicensingService _licenseService;
|
||||||
@ -29,6 +33,8 @@ namespace Bit.Api.Controllers
|
|||||||
public AccountsController(
|
public AccountsController(
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
|
ICipherRepository cipherRepository,
|
||||||
|
IFolderRepository folderRepository,
|
||||||
ICipherService cipherService,
|
ICipherService cipherService,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
ILicensingService licenseService,
|
ILicensingService licenseService,
|
||||||
@ -36,6 +42,8 @@ namespace Bit.Api.Controllers
|
|||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
|
_cipherRepository = cipherRepository;
|
||||||
|
_folderRepository = folderRepository;
|
||||||
_cipherService = cipherService;
|
_cipherService = cipherService;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_licenseService = licenseService;
|
_licenseService = licenseService;
|
||||||
@ -219,11 +227,27 @@ namespace Bit.Api.Controllers
|
|||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: It is assumed that the eventual repository call will make sure the updated
|
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
|
||||||
// ciphers belong to user making this call. Therefore, no check is done here.
|
var ciphersDict = model.Ciphers?.ToDictionary(c => c.Id.Value);
|
||||||
|
var ciphers = new List<Cipher>();
|
||||||
|
if(existingCiphers.Any() && ciphersDict != null)
|
||||||
|
{
|
||||||
|
foreach(var cipher in existingCiphers.Where(c => ciphersDict.ContainsKey(c.Id)))
|
||||||
|
{
|
||||||
|
ciphers.Add(ciphersDict[cipher.Id].ToCipher(cipher));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ciphers = model.Ciphers.Select(c => c.ToCipher(user.Id));
|
var existingFolders = await _folderRepository.GetManyByUserIdAsync(user.Id);
|
||||||
var folders = model.Folders.Select(c => c.ToFolder(user.Id));
|
var foldersDict = model.Folders?.ToDictionary(f => f.Id);
|
||||||
|
var folders = new List<Folder>();
|
||||||
|
if(existingFolders.Any() && foldersDict != null)
|
||||||
|
{
|
||||||
|
foreach(var folder in existingFolders.Where(f => foldersDict.ContainsKey(f.Id)))
|
||||||
|
{
|
||||||
|
folders.Add(foldersDict[folder.Id].ToFolder(folder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _userService.UpdateKeyAsync(
|
var result = await _userService.UpdateKeyAsync(
|
||||||
user,
|
user,
|
||||||
|
@ -391,13 +391,12 @@ namespace Bit.Api.Controllers
|
|||||||
var shareCiphers = new List<Cipher>();
|
var shareCiphers = new List<Cipher>();
|
||||||
foreach(var cipher in model.Ciphers)
|
foreach(var cipher in model.Ciphers)
|
||||||
{
|
{
|
||||||
var cipherGuid = new Guid(cipher.Id);
|
if(!ciphersDict.ContainsKey(cipher.Id.Value))
|
||||||
if(!ciphersDict.ContainsKey(cipherGuid))
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Trying to share ciphers that you do not own.");
|
throw new BadRequestException("Trying to share ciphers that you do not own.");
|
||||||
}
|
}
|
||||||
|
|
||||||
shareCiphers.Add(cipher.ToCipher(ciphersDict[cipherGuid]));
|
shareCiphers.Add(cipher.ToCipher(ciphersDict[cipher.Id.Value]));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cipherService.ShareManyAsync(shareCiphers, organizationId,
|
await _cipherService.ShareManyAsync(shareCiphers, organizationId,
|
||||||
@ -450,9 +449,9 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Request.GetFileAsync(async (stream, fileName) =>
|
await Request.GetFileAsync(async (stream, fileName, key) =>
|
||||||
{
|
{
|
||||||
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName,
|
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName, key,
|
||||||
Request.ContentLength.GetValueOrDefault(0), userId);
|
Request.ContentLength.GetValueOrDefault(0), userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -475,9 +474,9 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Request.GetFileAsync(async (stream, fileName) =>
|
await Request.GetFileAsync(async (stream, fileName, key) =>
|
||||||
{
|
{
|
||||||
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName,
|
await _cipherService.CreateAttachmentAsync(cipher, stream, fileName, key,
|
||||||
Request.ContentLength.GetValueOrDefault(0), userId);
|
Request.ContentLength.GetValueOrDefault(0), userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -498,9 +497,9 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Request.GetFileAsync(async (stream, fileName) =>
|
await Request.GetFileAsync(async (stream, fileName, key) =>
|
||||||
{
|
{
|
||||||
await _cipherService.CreateAttachmentShareAsync(cipher, stream, fileName,
|
await _cipherService.CreateAttachmentShareAsync(cipher, stream,
|
||||||
Request.ContentLength.GetValueOrDefault(0), attachmentId, organizationId);
|
Request.ContentLength.GetValueOrDefault(0), attachmentId, organizationId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,40 +13,54 @@ namespace Bit.Api.Utilities
|
|||||||
{
|
{
|
||||||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||||||
|
|
||||||
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, Task> callback)
|
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, string, Task> callback)
|
||||||
{
|
|
||||||
await request.GetFilesAsync(1, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task GetFilesAsync(this HttpRequest request, int? fileCount, Func<Stream, string, Task> callback)
|
|
||||||
{
|
{
|
||||||
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
||||||
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||||
var reader = new MultipartReader(boundary, request.Body);
|
var reader = new MultipartReader(boundary, request.Body);
|
||||||
|
|
||||||
var section = await reader.ReadNextSectionAsync();
|
var firstSection = await reader.ReadNextSectionAsync();
|
||||||
var fileNumber = 1;
|
if(firstSection != null)
|
||||||
while(section != null && fileNumber <= fileCount)
|
|
||||||
{
|
{
|
||||||
if(ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var content) &&
|
if(ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out var firstContent))
|
||||||
HasFileContentDisposition(content))
|
|
||||||
{
|
{
|
||||||
var fileName = HeaderUtilities.RemoveQuotes(content.FileName).ToString();
|
if(HasFileContentDisposition(firstContent))
|
||||||
using(section.Body)
|
|
||||||
{
|
{
|
||||||
await callback(section.Body, fileName);
|
// Old style with just data
|
||||||
|
var fileName = HeaderUtilities.RemoveQuotes(firstContent.FileName).ToString();
|
||||||
|
using(firstSection.Body)
|
||||||
|
{
|
||||||
|
await callback(firstSection.Body, fileName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(HasKeyDisposition(firstContent))
|
||||||
|
{
|
||||||
|
// New style with key, then data
|
||||||
|
string key = null;
|
||||||
|
using(var sr = new StreamReader(firstSection.Body))
|
||||||
|
{
|
||||||
|
key = await sr.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondSection = await reader.ReadNextSectionAsync();
|
||||||
|
if(secondSection != null)
|
||||||
|
{
|
||||||
|
if(ContentDispositionHeaderValue.TryParse(secondSection.ContentDisposition,
|
||||||
|
out var secondContent) && HasFileContentDisposition(secondContent))
|
||||||
|
{
|
||||||
|
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
|
||||||
|
using(secondSection.Body)
|
||||||
|
{
|
||||||
|
await callback(secondSection.Body, fileName, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secondSection = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fileNumber >= fileCount)
|
firstSection = null;
|
||||||
{
|
|
||||||
section = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
fileNumber++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,9 +82,15 @@ namespace Bit.Api.Utilities
|
|||||||
|
|
||||||
private static bool HasFileContentDisposition(ContentDispositionHeaderValue content)
|
private static bool HasFileContentDisposition(ContentDispositionHeaderValue content)
|
||||||
{
|
{
|
||||||
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
|
// Content-Disposition: form-data; name="data"; filename="Misc 002.jpg"
|
||||||
return content != null && content.DispositionType.Equals("form-data") &&
|
return content != null && content.DispositionType.Equals("form-data") &&
|
||||||
(!StringSegment.IsNullOrEmpty(content.FileName) || !StringSegment.IsNullOrEmpty(content.FileNameStar));
|
(!StringSegment.IsNullOrEmpty(content.FileName) || !StringSegment.IsNullOrEmpty(content.FileNameStar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasKeyDisposition(ContentDispositionHeaderValue content)
|
||||||
|
{
|
||||||
|
// Content-Disposition: form-data; name="key";
|
||||||
|
return content != null && content.DispositionType.Equals("form-data") && content.Name == "key";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/Core/Models/Api/CipherAttachmentModel.cs
Normal file
21
src/Core/Models/Api/CipherAttachmentModel.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Api
|
||||||
|
{
|
||||||
|
public class CipherAttachmentModel
|
||||||
|
{
|
||||||
|
public CipherAttachmentModel() { }
|
||||||
|
|
||||||
|
public CipherAttachmentModel(CipherAttachment.MetaData data)
|
||||||
|
{
|
||||||
|
FileName = data.FileName;
|
||||||
|
Key = data.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
[EncryptedStringLength(1000)]
|
||||||
|
public string FileName { get; set; }
|
||||||
|
[EncryptedStringLength(1000)]
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,10 @@ namespace Bit.Core.Models.Api
|
|||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public IEnumerable<CipherFieldModel> Fields { get; set; }
|
public IEnumerable<CipherFieldModel> Fields { get; set; }
|
||||||
public IEnumerable<CipherPasswordHistoryModel> PasswordHistory { get; set; }
|
public IEnumerable<CipherPasswordHistoryModel> PasswordHistory { get; set; }
|
||||||
|
[Obsolete]
|
||||||
public Dictionary<string, string> Attachments { get; set; }
|
public Dictionary<string, string> Attachments { get; set; }
|
||||||
|
// TODO: Rename to Attachments whenever the above is finally removed.
|
||||||
|
public Dictionary<string, CipherAttachmentModel> Attachments2 { get; set; }
|
||||||
|
|
||||||
public CipherLoginModel Login { get; set; }
|
public CipherLoginModel Login { get; set; }
|
||||||
public CipherCardModel Card { get; set; }
|
public CipherCardModel Card { get; set; }
|
||||||
@ -84,7 +87,10 @@ namespace Bit.Core.Models.Api
|
|||||||
throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");
|
throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
if((Attachments?.Count ?? 0) == 0)
|
var hasAttachments2 = (Attachments2?.Count ?? 0) > 0;
|
||||||
|
var hasAttachments = (Attachments?.Count ?? 0) > 0;
|
||||||
|
|
||||||
|
if(!hasAttachments2 && !hasAttachments)
|
||||||
{
|
{
|
||||||
return existingCipher;
|
return existingCipher;
|
||||||
}
|
}
|
||||||
@ -95,9 +101,22 @@ namespace Bit.Core.Models.Api
|
|||||||
return existingCipher;
|
return existingCipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(var attachment in attachments.Where(a => Attachments.ContainsKey(a.Key)))
|
if(hasAttachments2)
|
||||||
{
|
{
|
||||||
attachment.Value.FileName = Attachments[attachment.Key];
|
foreach(var attachment in attachments.Where(a => Attachments2.ContainsKey(a.Key)))
|
||||||
|
{
|
||||||
|
var attachment2 = Attachments2[attachment.Key];
|
||||||
|
attachment.Value.FileName = attachment2.FileName;
|
||||||
|
attachment.Value.Key = attachment2.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(hasAttachments)
|
||||||
|
{
|
||||||
|
foreach(var attachment in attachments.Where(a => Attachments.ContainsKey(a.Key)))
|
||||||
|
{
|
||||||
|
attachment.Value.FileName = Attachments[attachment.Key];
|
||||||
|
attachment.Value.Key = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
existingCipher.SetAttachments(attachments);
|
existingCipher.SetAttachments(attachments);
|
||||||
@ -132,15 +151,7 @@ namespace Bit.Core.Models.Api
|
|||||||
public class CipherWithIdRequestModel : CipherRequestModel
|
public class CipherWithIdRequestModel : CipherRequestModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[StringLength(36)]
|
public Guid? Id { get; set; }
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public Cipher ToCipher(Guid userId)
|
|
||||||
{
|
|
||||||
var cipher = ToCipherDetails(userId);
|
|
||||||
cipher.Id = new Guid(Id);
|
|
||||||
return cipher;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CipherCreateRequestModel : IValidatableObject
|
public class CipherCreateRequestModel : IValidatableObject
|
||||||
@ -224,7 +235,7 @@ namespace Bit.Core.Models.Api
|
|||||||
organizationIds.Add(c.OrganizationId);
|
organizationIds.Add(c.OrganizationId);
|
||||||
if(allHaveIds)
|
if(allHaveIds)
|
||||||
{
|
{
|
||||||
allHaveIds = !(string.IsNullOrWhiteSpace(c.Id) || string.IsNullOrWhiteSpace(c.OrganizationId));
|
allHaveIds = !(!c.Id.HasValue || string.IsNullOrWhiteSpace(c.OrganizationId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +31,5 @@ namespace Bit.Core.Models.Api
|
|||||||
public class FolderWithIdRequestModel : FolderRequestModel
|
public class FolderWithIdRequestModel : FolderRequestModel
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public new Folder ToFolder(Guid userId)
|
|
||||||
{
|
|
||||||
return ToFolder(new Folder
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Id = Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -8,12 +7,14 @@ namespace Bit.Core.Models.Api
|
|||||||
{
|
{
|
||||||
public class AttachmentResponseModel : ResponseModel
|
public class AttachmentResponseModel : ResponseModel
|
||||||
{
|
{
|
||||||
public AttachmentResponseModel(string id, CipherAttachment.MetaData data, Cipher cipher, GlobalSettings globalSettings)
|
public AttachmentResponseModel(string id, CipherAttachment.MetaData data, Cipher cipher,
|
||||||
|
GlobalSettings globalSettings)
|
||||||
: base("attachment")
|
: base("attachment")
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}";
|
Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}";
|
||||||
FileName = data.FileName;
|
FileName = data.FileName;
|
||||||
|
Key = data.Key;
|
||||||
Size = data.SizeString;
|
Size = data.SizeString;
|
||||||
SizeName = Utilities.CoreHelpers.ReadableBytesSize(data.Size);
|
SizeName = Utilities.CoreHelpers.ReadableBytesSize(data.Size);
|
||||||
}
|
}
|
||||||
@ -21,6 +22,7 @@ namespace Bit.Core.Models.Api
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
public string Size { get; set; }
|
public string Size { get; set; }
|
||||||
public string SizeName { get; set; }
|
public string SizeName { get; set; }
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ namespace Bit.Core.Models.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,6 +340,7 @@ namespace Bit.Core.Repositories.SqlServer
|
|||||||
[dbo].[Cipher]
|
[dbo].[Cipher]
|
||||||
SET
|
SET
|
||||||
[Data] = TC.[Data],
|
[Data] = TC.[Data],
|
||||||
|
[Attachments] = TC.[Attachments],
|
||||||
[RevisionDate] = TC.[RevisionDate]
|
[RevisionDate] = TC.[RevisionDate]
|
||||||
FROM
|
FROM
|
||||||
[dbo].[Cipher] C
|
[dbo].[Cipher] C
|
||||||
|
@ -13,10 +13,9 @@ namespace Bit.Core.Services
|
|||||||
bool skipPermissionCheck = false, bool limitCollectionScope = true);
|
bool skipPermissionCheck = false, bool limitCollectionScope = true);
|
||||||
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId, IEnumerable<Guid> collectionIds = null,
|
Task SaveDetailsAsync(CipherDetails cipher, Guid savingUserId, IEnumerable<Guid> collectionIds = null,
|
||||||
bool skipPermissionCheck = false);
|
bool skipPermissionCheck = false);
|
||||||
Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, long requestLength, Guid savingUserId,
|
Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, string key,
|
||||||
bool orgAdmin = false);
|
long requestLength, Guid savingUserId, bool orgAdmin = false);
|
||||||
Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, long requestLength,
|
Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, long requestLength, string attachmentId,
|
||||||
string attachmentId,
|
|
||||||
Guid organizationShareId);
|
Guid organizationShareId);
|
||||||
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false);
|
||||||
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
Task DeleteManyAsync(IEnumerable<Guid> cipherIds, Guid deletingUserId);
|
||||||
|
@ -143,8 +143,8 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, long requestLength,
|
public async Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, string key,
|
||||||
Guid savingUserId, bool orgAdmin = false)
|
long requestLength, Guid savingUserId, bool orgAdmin = false)
|
||||||
{
|
{
|
||||||
if(!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId)))
|
if(!orgAdmin && !(await UserCanEditAsync(cipher, savingUserId)))
|
||||||
{
|
{
|
||||||
@ -201,6 +201,7 @@ namespace Bit.Core.Services
|
|||||||
var data = new CipherAttachment.MetaData
|
var data = new CipherAttachment.MetaData
|
||||||
{
|
{
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
|
Key = key,
|
||||||
Size = stream.Length
|
Size = stream.Length
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -228,7 +229,7 @@ namespace Bit.Core.Services
|
|||||||
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, long requestLength,
|
public async Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, long requestLength,
|
||||||
string attachmentId, Guid organizationId)
|
string attachmentId, Guid organizationId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
Loading…
Reference in New Issue
Block a user