Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream all file downloads #878

Merged
merged 6 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions Backend.Tests/Controllers/LiftControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Backend.Tests.Mocks;
using BackendFramework.Controllers;
using BackendFramework.Helper;
Expand Down Expand Up @@ -205,8 +206,16 @@ public RoundTripObj(
}
}

/// <summary> Read all of the bytes from a Stream into byte array. </summary>
private static byte[] ReadAllBytes(Stream stream)
{
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}

[Test]
public void TestExportDeleted()
public async Task TestExportDeleted()
{
var word = RandomWord(_proj.Id);
var secondWord = RandomWord(_proj.Id);
Expand All @@ -219,16 +228,23 @@ public void TestExportDeleted()
word.Id = "";
word.Vernacular = "updated";

_wordService.Update(_proj.Id, wordToUpdate.Id, word);
_wordService.DeleteFrontierWord(_proj.Id, wordToDelete.Id);
await _wordService.Update(_proj.Id, wordToUpdate.Id, word);
await _wordService.DeleteFrontierWord(_proj.Id, wordToDelete.Id);

const string userId = "testId";
_liftController.ExportLiftFile(_proj.Id, userId).Wait();
var result = (FileContentResult)_liftController.DownloadLiftFile(_proj.Id, userId).Result;
var result = (FileStreamResult)_liftController.DownloadLiftFile(_proj.Id, userId).Result;
Assert.NotNull(result);

// Read contents.
byte[] contents;
using (var fileStream = result.FileStream)
{
contents = ReadAllBytes(fileStream);
}

// Write LiftFile contents to a temporary directory.
var extractedExportDir = ExtractZipFileContents(result.FileContents);
var extractedExportDir = ExtractZipFileContents(contents);
var exportPath = Path.Combine(extractedExportDir,
Path.Combine("Lift", "NewLiftFile.lift"));
var text = File.ReadAllText(exportPath, Encoding.UTF8);
Expand All @@ -239,7 +255,7 @@ public void TestExportDeleted()
Assert.That(text.IndexOf("dateDeleted"), Is.EqualTo(text.LastIndexOf("dateDeleted")));

// Delete the export
_liftController.DeleteLiftFile(userId);
await _liftController.DeleteLiftFile(userId);
var notFoundResult = _liftController.DownloadLiftFile(_proj.Id, userId).Result as NotFoundObjectResult;
Assert.NotNull(notFoundResult);
}
Expand Down
7 changes: 4 additions & 3 deletions Backend.Tests/Mocks/PermissionServiceMock.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BackendFramework.Interfaces;
using System.Threading.Tasks;
using BackendFramework.Interfaces;
using BackendFramework.Models;
using Microsoft.AspNetCore.Http;

Expand Down Expand Up @@ -26,9 +27,9 @@ public bool IsUserIdAuthorized(HttpContext request, string userId)
/// <summary>
/// By default this will return true, unless the test passes in an <see cref="UnauthorizedHttpContext"/>.
/// </summary>
public bool HasProjectPermission(HttpContext request, Permission permission)
public Task<bool> HasProjectPermission(HttpContext request, Permission permission)
{
return request is null || request.Request.Headers["Authorization"] != UnauthorizedHeader;
return Task.FromResult(request is null || request.Request.Headers["Authorization"] != UnauthorizedHeader);
}

public bool IsViolationEdit(HttpContext request, string userEditId, string projectId)
Expand Down
16 changes: 8 additions & 8 deletions Backend/Controllers/AudioController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public AudioController(IWordRepository repo, IWordService wordService, IPermissi

/// <summary> Returns the audio file in the form of a stream from disk</summary>
/// <remarks> GET: v1/projects/{projectId}/words/{wordId}/download/audio </remarks>
/// <returns> Audio file stream </returns>
/// <returns> Audio file stream. </returns>
[AllowAnonymous]
[HttpGet("{wordId}/download/audio/{fileName}")]
public async Task<IActionResult> DownloadAudioFile(string projectId, string wordId, string fileName)
public IActionResult DownloadAudioFile(string projectId, string wordId, string fileName)
{
// if we require authorization and authentication for audio files, the frontend cannot just use the api
// endpoint as the src
Expand All @@ -42,7 +42,7 @@ public async Task<IActionResult> DownloadAudioFile(string projectId, string word
// return new ForbidResult();
//}

// sanitize user input
// Sanitize user input
if (!Sanitization.SanitizeId(projectId) || !Sanitization.SanitizeId(wordId))
{
return new UnsupportedMediaTypeResult();
Expand All @@ -54,13 +54,13 @@ public async Task<IActionResult> DownloadAudioFile(string projectId, string word
return new BadRequestObjectResult("There was more than one subDir of the extracted zip");
}

var fileContents = await System.IO.File.ReadAllBytesAsync(filePath);
if (fileContents is null)
var file = System.IO.File.OpenRead(filePath);
if (file is null)
{
return new BadRequestObjectResult("The file does not exist");
}

return File(fileContents, "video/webm");
return File(file, "application/octet-stream");
}

/// <summary>
Expand All @@ -73,7 +73,7 @@ public async Task<IActionResult> DownloadAudioFile(string projectId, string word
public async Task<IActionResult> UploadAudioFile(string projectId, string wordId,
[FromForm] FileUpload fileUpload)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -121,7 +121,7 @@ public async Task<IActionResult> UploadAudioFile(string projectId, string wordId
[HttpDelete("{wordId}/audio/delete/{fileName}")]
public async Task<IActionResult> Delete(string projectId, string wordId, string fileName)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand Down
4 changes: 2 additions & 2 deletions Backend/Controllers/AvatarController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public async Task<IActionResult> DownloadAvatar(string userId)
return new NotFoundObjectResult(userId);
}

var image = await System.IO.File.ReadAllBytesAsync(avatar);
return File(image, "image/jpeg");
var imageFile = System.IO.File.OpenRead(avatar);
return File(imageFile, "application/octet-stream");
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Backend/Controllers/FrontierController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public FrontierController(IWordRepository repo, IWordService wordService,
[HttpGet]
public async Task<IActionResult> GetFrontier(string projectId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -86,7 +86,7 @@ public async Task<IActionResult> PostFrontier(string projectId, [FromBody] Word
#pragma warning restore 1998
{
#if DEBUG
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand All @@ -111,7 +111,7 @@ public async Task<IActionResult> PostFrontier(string projectId, [FromBody] Word
[HttpDelete("{wordId}")]
public async Task<IActionResult> DeleteFrontierWord(string projectId, string wordId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand Down
18 changes: 9 additions & 9 deletions Backend/Controllers/LiftController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public LiftController(IWordRepository repo, IProjectService projServ, IPermissio
[RequestSizeLimit(250_000_000)] // 250MB.
public async Task<IActionResult> UploadLiftFile(string projectId, [FromForm] FileUpload fileUpload)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -202,7 +202,7 @@ public async Task<IActionResult> ExportLiftFile(string projectId)
// These internal methods are extracted for unit testing
internal async Task<IActionResult> ExportLiftFile(string projectId, string userId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -272,7 +272,7 @@ public async Task<IActionResult> DownloadLiftFile(string projectId)

internal async Task<IActionResult> DownloadLiftFile(string projectId, string userId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
{
return new ForbidResult();
}
Expand All @@ -284,26 +284,26 @@ internal async Task<IActionResult> DownloadLiftFile(string projectId, string use
return new NotFoundObjectResult(userId);
}

var file = await System.IO.File.ReadAllBytesAsync(filePath);
var file = System.IO.File.OpenRead(filePath);
return File(
file,
"application/zip",
"application/octet-stream",
$"LiftExport-{projectId}-{DateTime.Now:yyyy-MM-dd_hh-mm-ss-fff}.zip");
}

/// <summary> Delete prepared export </summary>
/// <remarks> GET: v1/projects/{projectId}/words/deleteexport </remarks>
/// <returns> UserId, if successful </returns>
[HttpGet("deleteexport")]
public IActionResult DeleteLiftFile()
public async Task<IActionResult> DeleteLiftFile()
{
var userId = _permissionService.GetUserId(HttpContext);
return DeleteLiftFile(userId);
return await DeleteLiftFile(userId);
}

internal IActionResult DeleteLiftFile(string userId)
internal async Task<IActionResult> DeleteLiftFile(string userId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
{
return new ForbidResult();
}
Expand Down
18 changes: 9 additions & 9 deletions Backend/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ProjectController(IProjectService projectService, ISemDomParser semDomPar
[HttpGet]
public async Task<IActionResult> GetAllProjects()
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
{
return new ForbidResult();
}
Expand All @@ -50,7 +50,7 @@ public async Task<IActionResult> GetAllProjects()
[HttpGet("{projectId}/users")]
public async Task<IActionResult> GetAllUsers(string projectId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
{
return new ForbidResult();
}
Expand All @@ -67,7 +67,7 @@ public async Task<IActionResult> GetAllUsers(string projectId)
[HttpDelete]
public async Task<IActionResult> Delete()
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
{
return new ForbidResult();
}
Expand All @@ -79,7 +79,7 @@ public async Task<IActionResult> Delete()
[HttpGet("{projectId}")]
public async Task<IActionResult> Get(string projectId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -151,7 +151,7 @@ public async Task<IActionResult> Post([FromBody] Project project)
[HttpPut("{projectId}")]
public async Task<IActionResult> Put(string projectId, [FromBody] Project project)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
{
return new ForbidResult();
}
Expand All @@ -176,7 +176,7 @@ public async Task<IActionResult> Put(string projectId, [FromBody] Project projec
[HttpPut("{projectId}/characters")]
public async Task<IActionResult> PutChars(string projectId, [FromBody] Project project)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.MergeAndCharSet))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.MergeAndCharSet))
{
return new ForbidResult();
}
Expand All @@ -199,7 +199,7 @@ public async Task<IActionResult> PutChars(string projectId, [FromBody] Project p
[HttpDelete("{projectId}")]
public async Task<IActionResult> Delete(string projectId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -240,7 +240,7 @@ public async Task<IActionResult> GetSemDoms(string projectId)
[HttpPut("{projectId}/users/{userId}")]
public async Task<IActionResult> UpdateUserRole(string projectId, string userId, [FromBody] int[] permissions)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -291,7 +291,7 @@ public async Task<IActionResult> UpdateUserRole(string projectId, string userId,
[HttpGet("{projectId}/liftcheck")]
public async Task<IActionResult> CanUploadLift(string projectId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.ImportExport))
{
return new ForbidResult();
}
Expand Down
6 changes: 3 additions & 3 deletions Backend/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public async Task<IActionResult> ResetPassword([FromBody] PasswordResetData data
[HttpGet("projects/{projectId}/allusers")]
public async Task<IActionResult> GetAllUsers()
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DeleteEditSettingsAndUsers))
{
return new ForbidResult();
}
Expand All @@ -103,7 +103,7 @@ public async Task<IActionResult> GetAllUsers()
[HttpDelete]
public async Task<IActionResult> Delete()
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
{
return new ForbidResult();
}
Expand Down Expand Up @@ -239,7 +239,7 @@ public async Task<IActionResult> Put(string userId, [FromBody] User user)
[HttpDelete("{userId}")]
public async Task<IActionResult> Delete(string userId)
{
if (!_permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.DatabaseAdmin))
{
return new ForbidResult();
}
Expand Down
Loading