Skip to content

Commit

Permalink
Add password support for file downloads
Browse files Browse the repository at this point in the history
Updated the file download operation to include a password parameter, allowing for secure file access when required. Modified related methods and data structures to handle password input and pass it through the file conversion and download process. Enhanced error handling to manage password-related exceptions.
  • Loading branch information
pavelbannov committed Nov 29, 2024
1 parent 8a87ddb commit 78e0074
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 30 deletions.
8 changes: 5 additions & 3 deletions products/ASC.Files/Core/ApiModels/Binders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,25 @@ internal static List<JsonElement> ParseQuery(this ModelBindingContext bindingCon
return ParseQuery(bindingContext, $"{modelName}[]");
}

internal static IEnumerable<ItemKeyValuePair<JsonElement, string>> ParseDictionary(this ModelBindingContext bindingContext, string modelName)
internal static IEnumerable<DownloadRequestItemDto> ParseDictionary(this ModelBindingContext bindingContext, string modelName)
{
var result = new List<ItemKeyValuePair<JsonElement, string>>();
var result = new List<DownloadRequestItemDto>();

for (var i = 0; ; i++)
{
var keyProviderResult = bindingContext.ValueProvider.GetValue($"{modelName}[{i}][key]");
var valueProviderResult = bindingContext.ValueProvider.GetValue($"{modelName}[{i}][value]");
var passwordProviderResult = bindingContext.ValueProvider.GetValue($"{modelName}[{i}][password]");

if (keyProviderResult != ValueProviderResult.None && valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(modelName, keyProviderResult);
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
bindingContext.ModelState.SetModelValue(modelName, passwordProviderResult);

if (!string.IsNullOrEmpty(keyProviderResult.FirstValue) && !string.IsNullOrEmpty(valueProviderResult.FirstValue))
{
result.Add(new ItemKeyValuePair<JsonElement, string> { Key = ParseQueryParam(keyProviderResult.FirstValue), Value = valueProviderResult.FirstValue });
result.Add(new DownloadRequestItemDto { Key = ParseQueryParam(keyProviderResult.FirstValue), Value = valueProviderResult.FirstValue, Password = passwordProviderResult.FirstValue });
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ public class DownloadRequestDto : BaseBatchRequestDto
/// <summary>
/// List of file IDs which will be converted
/// </summary>
public IEnumerable<ItemKeyValuePair<JsonElement, string>> FileConvertIds { get; set; } = new List<ItemKeyValuePair<JsonElement, string>>();
public IEnumerable<DownloadRequestItemDto> FileConvertIds { get; set; } = new List<DownloadRequestItemDto>();
}

public class DownloadRequestItemDto
{
public JsonElement Key { get; init; }
public string Value { get; init; }
public string Password { get; init; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public FileDownloadOperationData(IEnumerable<T> folders,
public string BaseUri { get; init; }
}

public record FilesDownloadOperationItem<T>(T Id, string Ext);
public record FilesDownloadOperationItem<T>(T Id, string Ext, string Password);

[Transient]
public class FileDownloadOperation(IServiceProvider serviceProvider) : ComposeFileOperation<FileDownloadOperationData<string>, FileDownloadOperationData<int>>(serviceProvider)
Expand Down Expand Up @@ -215,14 +215,14 @@ protected override async Task PublishChanges(DistributedTask task)

class FileDownloadOperation<T> : FileOperation<FileDownloadOperationData<T>, T>
{
private readonly Dictionary<T, string> _files;
private readonly Dictionary<T, (string, string)> _files;
private readonly IDictionary<string, StringValues> _headers;
private ItemNameValueCollection<T> _entriesPathId;

public FileDownloadOperation(IServiceProvider serviceProvider, FileDownloadOperationData<T> fileDownloadOperationData)
: base(serviceProvider, fileDownloadOperationData)
{
_files = fileDownloadOperationData.FilesDownload?.ToDictionary(r => r.Id, r => r.Ext) ?? new Dictionary<T, string>();
_files = fileDownloadOperationData.FilesDownload?.ToDictionary(r => r.Id, r => (r.Ext, r.Password)) ?? new Dictionary<T, (string, string)>();
_headers = fileDownloadOperationData.Headers.ToDictionary(x => x.Key, x => new StringValues(x.Value));
this[OpType] = (int)FileOperationType.Download;
}
Expand Down Expand Up @@ -258,9 +258,9 @@ protected override async Task DoJob(IServiceScope serviceScope)
foreach (var file in filesForSend)
{
var key = file.Id;
if (_files.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value))
if (_files.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value.Item1))
{
await filesMessageService.SendAsync(MessageAction.FileDownloadedAs, file, _headers, file.Title, value);
await filesMessageService.SendAsync(MessageAction.FileDownloadedAs, file, _headers, file.Title, value.Item1);
}
else
{
Expand All @@ -285,16 +285,16 @@ private async Task<ItemNameValueCollection<T>> ExecPathFromFileAsync(IServiceSco

var fileExt = FileUtility.GetFileExtension(title);
var extsConvertible = await fileUtility.GetExtsConvertibleAsync();
var convertible = extsConvertible.TryGetValue(fileExt, out var convertibleToExt);
var convertible = extsConvertible.TryGetValue(fileExt, out _);

if (convertible && await DocSpaceHelper.IsWatermarkEnabled(file, folderDao))
{
_files[file.Id] = FileUtility.WatermarkedDocumentExt;
_files[file.Id] = (FileUtility.WatermarkedDocumentExt, _files[file.Id].Item2);
}

if (_files.TryGetValue(file.Id, out var convertToExt) && !string.IsNullOrEmpty(convertToExt))
if (_files.TryGetValue(file.Id, out var convertToExt) && !string.IsNullOrEmpty(convertToExt.Item1))
{
title = FileUtility.ReplaceFileExtension(title, convertToExt);
title = FileUtility.ReplaceFileExtension(title, convertToExt.Item1);
}

var entriesPathId = new ItemNameValueCollection<T>();
Expand Down Expand Up @@ -398,6 +398,7 @@ internal async Task CompressToZipAsync(Stream stream, IServiceScope scope)

using ICompress compressTo = scope.ServiceProvider.GetService<CompressToArchive>();
await compressTo.SetStream(stream);
string error = null;

foreach (var path in _entriesPathId.AllKeys)
{
Expand All @@ -419,6 +420,7 @@ internal async Task CompressToZipAsync(Stream stream, IServiceScope scope)

File<T> file = null;
var convertToExt = string.Empty;
var password = string.Empty;

if (!Equals(entryId, default(T)))
{
Expand All @@ -431,8 +433,9 @@ internal async Task CompressToZipAsync(Stream stream, IServiceScope scope)
continue;
}

if (_files.TryGetValue(file.Id, out convertToExt) && !string.IsNullOrEmpty(convertToExt))
if (_files.TryGetValue(file.Id, out var convertData) && !string.IsNullOrEmpty(convertData.Item1))
{
(convertToExt, password) = convertData;
var sourceFileName = Path.GetFileName(path);
var targetFileName = FileUtility.ReplaceFileExtension(sourceFileName, convertToExt);
newTitle = path.Replace(sourceFileName, targetFileName);
Expand All @@ -458,23 +461,29 @@ internal async Task CompressToZipAsync(Stream stream, IServiceScope scope)
await compressTo.CreateEntry(newTitle, file.ModifiedOn);
try
{
await using var readStream = await fileConverter.EnableConvertAsync(file, convertToExt, true) ?
await fileConverter.ExecAsync(file, convertToExt) :
await using var readStream = await fileConverter.EnableConvertAsync(file, convertToExt, true) ?
await fileConverter.ExecAsync(file, convertToExt, password) :
await fileDao.GetFileStreamAsync(file);

var t = Task.Run(async () => await compressTo.PutStream(readStream));

while (!t.IsCompleted)
{
await PublishChanges();
await Task.Delay(100);
}

await compressTo.CloseEntry();
}
catch (Exception ex) when(ex.InnerException is DocumentServiceException { Code: DocumentServiceException.ErrorCode.ConvertPassword })
{
error += $"{entryId}_password:";

Logger.ErrorWithException(ex);
}
catch (Exception ex)
{
this[Err] = ex.Message;
error += ex.Message;

Logger.ErrorWithException(ex);
}
Expand All @@ -500,6 +509,13 @@ await fileConverter.ExecAsync(file, convertToExt) :

await ProgressStep();
}

if (!string.IsNullOrEmpty(error))
{
this[Err] = error;
await PublishChanges();
}

}

internal async Task<string> GetFileAsync(Stream stream, IServiceScope scope)
Expand Down Expand Up @@ -529,6 +545,7 @@ internal async Task<string> GetFileAsync(Stream stream, IServiceScope scope)

File<T> file = null;
var convertToExt = string.Empty;
var password = string.Empty;

if (!Equals(entryId, default(T)))
{
Expand All @@ -541,8 +558,9 @@ internal async Task<string> GetFileAsync(Stream stream, IServiceScope scope)
return null;
}

if (_files.TryGetValue(file.Id, out convertToExt) && !string.IsNullOrEmpty(convertToExt))
if (_files.TryGetValue(file.Id, out var convertData) && !string.IsNullOrEmpty(convertData.Item1))
{
(convertToExt, password) = convertData;
var sourceFileName = Path.GetFileName(path);
var targetFileName = FileUtility.ReplaceFileExtension(sourceFileName, convertToExt);
newTitle = path.Replace(sourceFileName, targetFileName);
Expand All @@ -554,7 +572,7 @@ internal async Task<string> GetFileAsync(Stream stream, IServiceScope scope)
try
{
await using var readStream = await fileConverter.EnableConvertAsync(file, convertToExt, true) ?
await fileConverter.ExecAsync(file, convertToExt) :
await fileConverter.ExecAsync(file, convertToExt, password) :
await fileDao.GetFileStreamAsync(file);

await readStream.CopyToAsync(stream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,33 +430,37 @@ private static (List<FilesDownloadOperationItem<int>>, List<FilesDownloadOperati
{
if (item.Id.ValueKind == JsonValueKind.Number)
{
resultInt.Add(new FilesDownloadOperationItem<int>(item.Id.GetInt32(), item.Ext));
resultInt.Add(new FilesDownloadOperationItem<int>(item.Id.GetInt32(), item.Ext, item.Password));
}
else if (item.Id.ValueKind == JsonValueKind.String)
{
var val = item.Id.GetString();
if (int.TryParse(val, out var i))
{
resultInt.Add(new FilesDownloadOperationItem<int>(i, item.Ext));
resultInt.Add(new FilesDownloadOperationItem<int>(i, item.Ext, item.Password));
}
else
{
resultString.Add(new FilesDownloadOperationItem<string>(val, item.Ext));
resultString.Add(new FilesDownloadOperationItem<string>(val, item.Ext, item.Password));
}
}
else if (item.Id.ValueKind == JsonValueKind.Object)
{
var key = item.Id.GetProperty("key");

var val = item.Id.GetProperty("value").GetString();
var password = "";
if (item.Id.TryGetProperty("password", out var p))
{
password = p.GetString();
};

if (key.ValueKind == JsonValueKind.Number)
{
resultInt.Add(new FilesDownloadOperationItem<int>(key.GetInt32(), val));
resultInt.Add(new FilesDownloadOperationItem<int>(key.GetInt32(), val, password));
}
else
{
resultString.Add(new FilesDownloadOperationItem<string>(key.GetString(), val));
resultString.Add(new FilesDownloadOperationItem<string>(key.GetString(), val, password));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions products/ASC.Files/Server/Api/OperationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public class OperationController(
[HttpPut("bulkdownload")]
public async IAsyncEnumerable<FileOperationDto> BulkDownload(DownloadRequestDto inDto)
{
var files = inDto.FileConvertIds.Select(fileId => new FilesDownloadOperationItem<JsonElement>(fileId.Key, fileId.Value)).ToList();
files.AddRange(inDto.FileIds.Select(fileId => new FilesDownloadOperationItem<JsonElement>(fileId, string.Empty)));
var files = inDto.FileConvertIds.Select(fileId => new FilesDownloadOperationItem<JsonElement>(fileId.Key, fileId.Value, fileId.Password)).ToList();
files.AddRange(inDto.FileIds.Select(fileId => new FilesDownloadOperationItem<JsonElement>(fileId, string.Empty, string.Empty)));

await fileOperationsManager.PublishDownload(inDto.FolderIds, files, commonLinkUtility.ServerRootPath);

Expand Down

0 comments on commit 78e0074

Please sign in to comment.