mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
support for attachments keys
load existing items and set attachments on key update
This commit is contained in:
@ -12,6 +12,8 @@ using Bit.Core.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Api.Utilities;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -21,6 +23,8 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IFolderRepository _folderRepository;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly ILicensingService _licenseService;
|
||||
@ -29,6 +33,8 @@ namespace Bit.Api.Controllers
|
||||
public AccountsController(
|
||||
IUserService userService,
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IFolderRepository folderRepository,
|
||||
ICipherService cipherService,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ILicensingService licenseService,
|
||||
@ -36,6 +42,8 @@ namespace Bit.Api.Controllers
|
||||
{
|
||||
_userService = userService;
|
||||
_userRepository = userRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
_folderRepository = folderRepository;
|
||||
_cipherService = cipherService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_licenseService = licenseService;
|
||||
@ -219,11 +227,27 @@ namespace Bit.Api.Controllers
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
// NOTE: It is assumed that the eventual repository call will make sure the updated
|
||||
// ciphers belong to user making this call. Therefore, no check is done here.
|
||||
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
|
||||
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 folders = model.Folders.Select(c => c.ToFolder(user.Id));
|
||||
var existingFolders = await _folderRepository.GetManyByUserIdAsync(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(
|
||||
user,
|
||||
|
@ -391,13 +391,12 @@ namespace Bit.Api.Controllers
|
||||
var shareCiphers = new List<Cipher>();
|
||||
foreach(var cipher in model.Ciphers)
|
||||
{
|
||||
var cipherGuid = new Guid(cipher.Id);
|
||||
if(!ciphersDict.ContainsKey(cipherGuid))
|
||||
if(!ciphersDict.ContainsKey(cipher.Id.Value))
|
||||
{
|
||||
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,
|
||||
@ -450,9 +449,9 @@ namespace Bit.Api.Controllers
|
||||
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);
|
||||
});
|
||||
|
||||
@ -475,9 +474,9 @@ namespace Bit.Api.Controllers
|
||||
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);
|
||||
});
|
||||
|
||||
@ -498,9 +497,9 @@ namespace Bit.Api.Controllers
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -13,40 +13,54 @@ namespace Bit.Api.Utilities
|
||||
{
|
||||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||||
|
||||
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, Task> callback)
|
||||
{
|
||||
await request.GetFilesAsync(1, callback);
|
||||
}
|
||||
|
||||
private static async Task GetFilesAsync(this HttpRequest request, int? fileCount, Func<Stream, string, Task> callback)
|
||||
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, string, Task> callback)
|
||||
{
|
||||
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
|
||||
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||
var reader = new MultipartReader(boundary, request.Body);
|
||||
|
||||
var section = await reader.ReadNextSectionAsync();
|
||||
var fileNumber = 1;
|
||||
while(section != null && fileNumber <= fileCount)
|
||||
var firstSection = await reader.ReadNextSectionAsync();
|
||||
if(firstSection != null)
|
||||
{
|
||||
if(ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var content) &&
|
||||
HasFileContentDisposition(content))
|
||||
if(ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out var firstContent))
|
||||
{
|
||||
var fileName = HeaderUtilities.RemoveQuotes(content.FileName).ToString();
|
||||
using(section.Body)
|
||||
if(HasFileContentDisposition(firstContent))
|
||||
{
|
||||
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)
|
||||
{
|
||||
section = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
section = await reader.ReadNextSectionAsync();
|
||||
fileNumber++;
|
||||
}
|
||||
firstSection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +82,15 @@ namespace Bit.Api.Utilities
|
||||
|
||||
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") &&
|
||||
(!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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user