diff --git a/src/ClientServer/Controllers/Core/Exercises/DownloadAssetController.cs b/src/ClientServer/Controllers/Core/Exercises/DownloadAssetController.cs index 10c201388188deab8628172ef059c010ddc0b5e6..640deae10e239dcfa4c0c2bf8a10367a4b322eab 100644 --- a/src/ClientServer/Controllers/Core/Exercises/DownloadAssetController.cs +++ b/src/ClientServer/Controllers/Core/Exercises/DownloadAssetController.cs @@ -15,14 +15,26 @@ using Microsoft.Extensions.Primitives; namespace ClientServer.Controllers.Core.Exercises { [Route(Constants.ApiPrefix + "assets")] - public class TestAssetController : ControllerWithDb + public class DownloadAssetController : ControllerWithDb { - public TestAssetController(SyndromDbContext context) : base(context) + /* + * no participation --> not allowed to download asset + * if we set this the browser will not download the json response as a file?? (TODO check that) + * Response.StatusCode = 406; + * + * + * we don't need methods to download exercise description or custom project description assets + * because the frontend already loads the content and thus can locally "download" the file (via blobs) + */ + + + + public DownloadAssetController(SyndromDbContext context) : base(context) { } /// <summary> - /// gets (downloads) the test asset + /// gets (downloads) the normal/submit test asset /// </summary> /// <param name="generatedCode">the release code</param> /// <param name="testId">the test id</param> @@ -37,17 +49,27 @@ namespace ClientServer.Controllers.Core.Exercises int userId = GetUserId(); + int exerciseId; if (asTutor) { //check if the user is really a tutor for this exercise (group) - var targetUserGroupId = + var exerciseDataTuple = await _context.ExerciseReleases.Where(p => p.GeneratedCode == generatedCode) - .Select(p => p.Exercise.UserGroupId) + .Select(p => new {p.Exercise.UserGroupId, p.ExerciseId}) .FirstOrDefaultAsync(); - if (!await base.HasGroupPermission(targetUserGroupId, + if (exerciseDataTuple == null) + { + Response.StatusCode = 406; + await + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "participation not found"))); + return; + } + + if (!await base.HasGroupPermission(exerciseDataTuple.UserGroupId, permission => permission != null && permission.CanAssessExercises)) { Response.StatusCode = 406; @@ -56,6 +78,8 @@ namespace ClientServer.Controllers.Core.Exercises Jc.Serialize(new BasicResponse(ResponseCode.NoPermission, "no permission"))); return; } + + exerciseId = exerciseDataTuple.ExerciseId; } else { @@ -76,6 +100,15 @@ namespace ClientServer.Controllers.Core.Exercises return; } + if (participation.ExerciseRelease == null) + { + Response.StatusCode = 406; + await + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "exercise release not found"))); + return; + } + if (isSubmitTest) { //submit tests are only displayed when the release is finished... @@ -93,14 +126,17 @@ namespace ClientServer.Controllers.Core.Exercises return; } } + + exerciseId = participation.ExerciseRelease.ExerciseId; } - var asset = await this.GetTestNormalOrSubmitTestAsset(testId, assetId, isSubmitTest); + var asset = await this._GetTestNormalOrSubmitTestAsset(testId, assetId, exerciseId, isSubmitTest); if (asset == null) { //already handled + return; } else if (asset.Content == null) { @@ -129,24 +165,28 @@ namespace ClientServer.Controllers.Core.Exercises /// </summary> /// <param name="testId">the test id</param> /// <param name="assetId">the asset id</param> + /// <param name="exerciseId"></param> /// <param name="isSubmitTest">true: submit test, false: not</param> /// <returns>the asset or null if not found or error</returns> - private async Task<TempTestAsset> GetTestNormalOrSubmitTestAsset(int testId, int assetId, bool isSubmitTest) + private async Task<TempTestAsset> _GetTestNormalOrSubmitTestAsset(int testId, int assetId, int exerciseId, + bool isSubmitTest) { - //ensure that this is the right asset (check testId too) + //calling method ensured that the user has access to the test var connection = await _context.TestWithFileAsAssetReferences .Include(p => p.FileReferenceTestAsset) - .FirstOrDefaultAsync(p => p.TestId == testId && p.FileReferenceTestAssetId == assetId); + .FirstOrDefaultAsync(p => p.TestId == testId && + p.Test.ExerciseId == exerciseId && + p.FileReferenceTestAssetId == assetId); - if (connection== null) + if (connection == null) { await Response.WriteAsync( Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference connection not found"))); return null; } - + if (connection.FileReferenceTestAsset == null) { await @@ -154,12 +194,12 @@ namespace ClientServer.Controllers.Core.Exercises Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); return null; } - + var testAssetsBasePath = Files.GetUploadFilePath(UploadDirType.TestAssets); - var testFilesDict = await Files.ReadUploadedFilesAsArray(testAssetsBasePath, connection.FileReferenceTestAsset.Id); - - + var testFilesDict = + await Files.ReadUploadedFilesAsArray(testAssetsBasePath, connection.FileReferenceTestAsset.Id); + var fileName = "asset"; if (Files.ValidFileName(connection.FileReferenceTestAsset.OriginalName)) @@ -170,7 +210,8 @@ namespace ClientServer.Controllers.Core.Exercises { //just try to get a file extension... - var lastIndex = connection.FileReferenceTestAsset.OriginalName.LastIndexOf(".", StringComparison.Ordinal); + var lastIndex = + connection.FileReferenceTestAsset.OriginalName.LastIndexOf(".", StringComparison.Ordinal); if (lastIndex == -1) { //no file extension... @@ -224,30 +265,33 @@ namespace ClientServer.Controllers.Core.Exercises var connection = await _context.CustomTestWithFileAsAssetReferences .Include(p => p.FileReferenceUserFileAsset) .FirstOrDefaultAsync(p => - p.CustomTestId == customTestId && p.CustomTest.UserId == userId && + p.CustomTestId == customTestId && + p.CustomTest.ExerciseReleaseId == participation.ExerciseReleaseId && + p.CustomTest.UserId == userId && p.FileReferenceUserFileAssetId == assetId) ; - + if (connection == null) { Response.StatusCode = 406; await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset connection not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset connection not found"))); return; } - + var testAsset = connection.FileReferenceUserFileAsset; if (testAsset == null) { Response.StatusCode = 406; await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); return; } - var fileName = "asset"; if (Files.ValidFileName(testAsset.OriginalName)) @@ -271,8 +315,8 @@ namespace ClientServer.Controllers.Core.Exercises } } - var filesIdsToRead = new int[] { connection.FileReferenceUserFileAssetId}; - + var filesIdsToRead = new int[] {connection.FileReferenceUserFileAssetId}; + var basePath = Files.GetUploadFilePath(UploadDirType.UserAssets); var filesDict = await Files.ReadUploadedFilesAsArray(basePath, filesIdsToRead); @@ -282,22 +326,22 @@ namespace ClientServer.Controllers.Core.Exercises if (content == null) { await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset content not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset content not found"))); return; } - + //see https://stackoverflow.com/questions/20508788/do-i-need-content-type-application-octet-stream-for-file-download Response.ContentType = "application/octet-stream"; //overwrite global json response Response.Headers.Add(new KeyValuePair<string, StringValues>("Content-Disposition", "attachment; filename=\"" + fileName + "\"")); - await Response.Body.WriteAsync(content, 0, content.Length); await Response.Body.FlushAsync(); } - + /// <summary> /// gets (downloads) the custom project custom test asset /// </summary> @@ -311,7 +355,7 @@ namespace ClientServer.Controllers.Core.Exercises if (!await base.IsLoggedIn(null, false, false)) return; int userId = GetUserId(); - + var customProject = await _context.CustomProjects .FirstOrDefaultAsync(p => p.UserId == userId && //just ensure the project is from the current user p.Id == customProjectId) @@ -324,9 +368,8 @@ namespace ClientServer.Controllers.Core.Exercises Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "custom project not found"))); return; } - - + var oldTest = await _context.CustomProjectTests .Where(p => p.Id == customTestId && p.CustomProjectId == customProjectId) @@ -340,35 +383,37 @@ namespace ClientServer.Controllers.Core.Exercises Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "test not found"))); return; } - - - var connection = await _context.CustomProjectTestWithFileAsAssetReferences + + + var connection = await _context.CustomProjectTestWithFileAsAssetReferences .Include(p => p.FileReferenceUserFileAsset) .FirstOrDefaultAsync(p => - p.CustomProjectTestId == customTestId && p.CustomProjectTest.CustomProjectId == customProject.Id && + p.CustomProjectTestId == customTestId && + p.CustomProjectTest.CustomProjectId == customProject.Id && p.FileReferenceUserFileAssetId == assetId) ; - + if (connection == null) { Response.StatusCode = 406; await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset connection not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset connection not found"))); return; } - + var testAsset = connection.FileReferenceUserFileAsset; if (testAsset == null) { Response.StatusCode = 406; await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); return; } - var fileName = "asset"; if (Files.ValidFileName(testAsset.OriginalName)) @@ -392,8 +437,8 @@ namespace ClientServer.Controllers.Core.Exercises } } - var filesIdsToRead = new int[] { connection.FileReferenceUserFileAssetId}; - + var filesIdsToRead = new int[] {connection.FileReferenceUserFileAssetId}; + var basePath = Files.GetUploadFilePath(UploadDirType.UserAssets); var filesDict = await Files.ReadUploadedFilesAsArray(basePath, filesIdsToRead); @@ -403,20 +448,19 @@ namespace ClientServer.Controllers.Core.Exercises if (content == null) { await - Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset content not found"))); + Response.WriteAsync( + Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset content not found"))); return; } - + //see https://stackoverflow.com/questions/20508788/do-i-need-content-type-application-octet-stream-for-file-download Response.ContentType = "application/octet-stream"; //overwrite global json response Response.Headers.Add(new KeyValuePair<string, StringValues>("Content-Disposition", "attachment; filename=\"" + fileName + "\"")); - await Response.Body.WriteAsync(content, 0, content.Length); await Response.Body.FlushAsync(); - } /// <summary> @@ -426,14 +470,15 @@ namespace ClientServer.Controllers.Core.Exercises /// <param name="testId">the test id</param> /// <param name="assetId">the asset reference id</param> [HttpGet("editor/{exerciseId}/{testId}/{assetId}")] - public async Task GetTestAssetWithoutReleaseCode(int exerciseId, int testId, int assetId) + public async Task GetExerciseEditorTestAssetWithoutReleaseCode(int exerciseId, int testId, int assetId) { if (!await base.IsLoggedIn(null, false, false)) return; int userId = GetUserId(); int managingGroupId = await _context.Exercises - .Where(p => p.Id == exerciseId).Select(p => p.UserGroupId) + .Where(p => p.Id == exerciseId) + .Select(p => p.UserGroupId) .FirstOrDefaultAsync(); if (managingGroupId == default(int)) @@ -447,6 +492,7 @@ namespace ClientServer.Controllers.Core.Exercises //the user group that WILL manages the exercise (after the change) var targetUserGroup = await _context.UserGroups.FirstOrDefaultAsync(p => p.Id == managingGroupId); + if (targetUserGroup == null) { Response.StatusCode = 406; @@ -475,17 +521,19 @@ namespace ClientServer.Controllers.Core.Exercises //ensure that this is the right asset (check testId too) var connection = await _context.TestWithFileAsAssetReferences .Include(p => p.FileReferenceTestAsset) - .FirstOrDefaultAsync(p => p.TestId == testId && p.FileReferenceTestAssetId == assetId); + .FirstOrDefaultAsync(p => p.TestId == testId && + p.Test.ExerciseId == exerciseId && + p.FileReferenceTestAssetId == assetId); - if (connection== null) + if (connection == null) { await Response.WriteAsync( Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference connection not found"))); return; } - + if (connection.FileReferenceTestAsset == null) { await @@ -493,10 +541,11 @@ namespace ClientServer.Controllers.Core.Exercises Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "asset reference not found"))); return; } - + var testAssetsBasePath = Files.GetUploadFilePath(UploadDirType.TestAssets); - var testFilesDict = await Files.ReadUploadedFilesAsArray(testAssetsBasePath, connection.FileReferenceTestAsset.Id); + var testFilesDict = + await Files.ReadUploadedFilesAsArray(testAssetsBasePath, connection.FileReferenceTestAsset.Id); var fileName = "asset"; @@ -508,7 +557,8 @@ namespace ClientServer.Controllers.Core.Exercises { //just try to get a file extension... - var lastIndex = connection.FileReferenceTestAsset.OriginalName.LastIndexOf(".", StringComparison.Ordinal); + var lastIndex = + connection.FileReferenceTestAsset.OriginalName.LastIndexOf(".", StringComparison.Ordinal); if (lastIndex == -1) { //no file extension... @@ -539,14 +589,17 @@ namespace ClientServer.Controllers.Core.Exercises internal class TempTestAsset { public int Id { get; set; } + /// <summary> /// the name of the file/asset /// </summary> public string DisplayName { get; set; } + /// <summary> /// the content of the asset (binary) /// </summary> public byte[] Content { get; set; } + /// <summary> /// the mime type (maybe we need to know what content is stored) /// </summary>