Skip to content

Commit d13ff7b

Browse files
Merge branch master
2 parents 4075fde + 6d1c5c3 commit d13ff7b

86 files changed

Lines changed: 3962 additions & 1324 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task<CoursePreviewView[]> GetAllCourses()
4949
[ProducesResponseType(typeof(CourseAllData), (int)HttpStatusCode.OK)]
5050
public async Task<IActionResult> GetAllCourseData(long courseId)
5151
{
52-
var courseResult = await _coursesClient.GetAllCourseData(courseId);
52+
var courseResult = await _coursesClient.GetCourseDataRaw(courseId);
5353
if (!courseResult.Succeeded)
5454
return BadRequest(courseResult.Errors[0]);
5555

@@ -66,7 +66,7 @@ public async Task<IActionResult> GetAllCourseData(long courseId)
6666
[ProducesResponseType(typeof(CourseViewModel), (int)HttpStatusCode.OK)]
6767
public async Task<IActionResult> GetCourseData(long courseId)
6868
{
69-
var course = await _coursesClient.GetCourseById(courseId);
69+
var course = await _coursesClient.GetCourseView(courseId);
7070
if (course == null) return NotFound();
7171

7272
var result = await ToCourseViewModel(course);

HwProj.APIGateway/HwProj.APIGateway.API/Controllers/FilesController.cs

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using HwProj.AuthService.Client;
55
using HwProj.ContentService.Client;
66
using HwProj.Models.ContentService.DTO;
7-
using HwProj.Models.Roles;
7+
using HwProj.Models.CourseUnitType;
88
using Microsoft.AspNetCore.Authorization;
99
using Microsoft.AspNetCore.Mvc;
1010

@@ -13,72 +13,79 @@ namespace HwProj.APIGateway.API.Controllers;
1313
[Route("api/[controller]")]
1414
[Authorize]
1515
[ApiController]
16-
public class FilesController : AggregationController
16+
public class FilesController(
17+
IAuthServiceClient authServiceClient,
18+
IContentServiceClient contentServiceClient,
19+
FilesPrivacyFilter privacyFilter,
20+
FilesCountLimiter filesCountLimiter)
21+
: AggregationController(authServiceClient)
1722
{
18-
private readonly IContentServiceClient _contentServiceClient;
19-
20-
public FilesController(IAuthServiceClient authServiceClient,
21-
IContentServiceClient contentServiceClient) : base(authServiceClient)
22-
{
23-
_contentServiceClient = contentServiceClient;
24-
}
25-
2623
[HttpPost("process")]
27-
[Authorize(Roles = Roles.LecturerRole)]
28-
[ServiceFilter(typeof(CourseMentorOnlyAttribute))]
2924
[ProducesResponseType((int)HttpStatusCode.OK)]
30-
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.ServiceUnavailable)]
25+
[ProducesResponseType((int)HttpStatusCode.Forbidden)]
26+
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.BadRequest)]
3127
public async Task<IActionResult> Process([FromForm] ProcessFilesDTO processFilesDto)
3228
{
33-
var result = await _contentServiceClient.ProcessFilesAsync(processFilesDto);
29+
var checkRights = await privacyFilter.CheckUploadRights(UserId, processFilesDto.FilesScope);
30+
if (!checkRights) return Forbid("Недостаточно прав для загрузки файлов");
31+
32+
var checkCountLimit = await filesCountLimiter.CheckCountLimit(processFilesDto);
33+
if (!checkCountLimit)
34+
return Forbid("Слишком много файлов в решении." +
35+
$"Максимальное количество файлов - ${FilesCountLimiter.MaxSolutionFiles}");
36+
37+
var result = await contentServiceClient.ProcessFilesAsync(processFilesDto);
3438
return result.Succeeded
3539
? Ok()
36-
: StatusCode((int)HttpStatusCode.ServiceUnavailable, result.Errors);
40+
: BadRequest(result.Errors);
3741
}
3842

3943
[HttpPost("statuses")]
40-
[Authorize(Roles = Roles.LecturerRole)]
41-
[ServiceFilter(typeof(CourseMentorOnlyAttribute))]
44+
[ProducesResponseType((int)HttpStatusCode.Forbidden)]
4245
[ProducesResponseType(typeof(FileInfoDTO[]), (int)HttpStatusCode.OK)]
43-
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.ServiceUnavailable)]
46+
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.BadRequest)]
4447
public async Task<IActionResult> GetStatuses(ScopeDTO filesScope)
4548
{
46-
var filesStatusesResult = await _contentServiceClient.GetFilesStatuses(filesScope);
47-
return filesStatusesResult.Succeeded
48-
? Ok(filesStatusesResult.Value) as IActionResult
49-
: StatusCode((int)HttpStatusCode.ServiceUnavailable, filesStatusesResult.Errors);
49+
var checkRights = await privacyFilter.CheckUploadRights(UserId, filesScope);
50+
if (!checkRights) return Forbid("Недостаточно прав для получения информации о файлах");
51+
52+
var result = await contentServiceClient.GetFilesStatuses(filesScope);
53+
return result.Succeeded
54+
? Ok(result.Value)
55+
: BadRequest(result.Errors);
5056
}
5157

5258
[HttpGet("downloadLink")]
59+
[ProducesResponseType((int)HttpStatusCode.Forbidden)]
5360
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
5461
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.NotFound)]
5562
public async Task<IActionResult> GetDownloadLink([FromQuery] long fileId)
5663
{
57-
var result = await _contentServiceClient.GetDownloadLinkAsync(fileId);
58-
return result.Succeeded
59-
? Ok(result.Value)
60-
: NotFound(result.Errors);
61-
}
64+
var linkDto = await contentServiceClient.GetDownloadLinkAsync(fileId);
65+
if (!linkDto.Succeeded) return BadRequest(linkDto.Errors);
6266

63-
[HttpGet("info/course/{courseId}")]
64-
[ProducesResponseType(typeof(FileInfoDTO[]), (int)HttpStatusCode.OK)]
65-
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.ServiceUnavailable)]
66-
public async Task<IActionResult> GetFilesInfo(long courseId)
67-
{
68-
var filesInfoResult = await _contentServiceClient.GetFilesInfo(courseId);
69-
return filesInfoResult.Succeeded
70-
? Ok(filesInfoResult.Value) as IActionResult
71-
: StatusCode((int)HttpStatusCode.ServiceUnavailable, filesInfoResult.Errors);
67+
var result = linkDto.Value;
68+
var userId = UserId;
69+
70+
foreach (var scope in result.FileScopes)
71+
{
72+
if (await privacyFilter.CheckDownloadRights(userId, scope))
73+
return Ok(result.DownloadUrl);
74+
}
75+
76+
return Forbid("Недостаточно прав для получения ссылки на файл");
7277
}
7378

74-
[HttpGet("info/course/{courseId}/uploaded")]
79+
[HttpGet("info/course/{courseId}")]
7580
[ProducesResponseType(typeof(FileInfoDTO[]), (int)HttpStatusCode.OK)]
76-
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.ServiceUnavailable)]
77-
public async Task<IActionResult> GetUploadedFilesInfo(long courseId)
81+
[ProducesResponseType(typeof(string[]), (int)HttpStatusCode.BadRequest)]
82+
public async Task<IActionResult> GetFilesInfo(long courseId,
83+
[FromQuery] bool uploadedOnly = true,
84+
[FromQuery] string courseUnitType = CourseUnitType.Homework)
7885
{
79-
var filesInfoResult = await _contentServiceClient.GetUploadedFilesInfo(courseId);
86+
var filesInfoResult = await contentServiceClient.GetFilesInfo(courseId, uploadedOnly, courseUnitType);
8087
return filesInfoResult.Succeeded
81-
? Ok(filesInfoResult.Value) as IActionResult
82-
: StatusCode((int)HttpStatusCode.ServiceUnavailable, filesInfoResult.Errors);
88+
? Ok(filesInfoResult.Value)
89+
: BadRequest(filesInfoResult.Errors);
8390
}
8491
}

HwProj.APIGateway/HwProj.APIGateway.API/Controllers/TasksController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public class TasksController(ICoursesServiceClient coursesClient) : ControllerBa
1616
[HttpGet("get/{taskId}")]
1717
[Authorize]
1818
[ProducesResponseType(typeof(HomeworkTaskViewModel), (int)HttpStatusCode.OK)]
19-
public async Task<IActionResult> GetTask(long taskId)
19+
public async Task<IActionResult> GetTask(long taskId, [FromQuery] bool? withCriteria)
2020
{
21-
var result = await coursesClient.GetTask(taskId);
21+
var result = await coursesClient.GetTask(taskId, withCriteria ?? false);
2222
return result == null
2323
? NotFound()
2424
: Ok(result);
@@ -38,7 +38,7 @@ public async Task<IActionResult> GetForEditingTask(long taskId)
3838
[HttpPost("add/{homeworkId}")]
3939
[Authorize(Roles = Roles.LecturerRole)]
4040
[ProducesResponseType(typeof(Result<HomeworkTaskViewModel>), (int)HttpStatusCode.OK)]
41-
public async Task<IActionResult> AddTask(long homeworkId, CreateTaskViewModel taskViewModel)
41+
public async Task<IActionResult> AddTask(long homeworkId, PostTaskViewModel taskViewModel)
4242
{
4343
var result = await coursesClient.AddTask(homeworkId, taskViewModel);
4444
return result.Succeeded
@@ -57,7 +57,7 @@ public async Task<IActionResult> DeleteTask(long taskId)
5757
[HttpPut("update/{taskId}")]
5858
[Authorize(Roles = Roles.LecturerRole)]
5959
[ProducesResponseType(typeof(Result<HomeworkTaskViewModel>), (int)HttpStatusCode.OK)]
60-
public async Task<IActionResult> UpdateTask(long taskId, CreateTaskViewModel taskViewModel)
60+
public async Task<IActionResult> UpdateTask(long taskId, PostTaskViewModel taskViewModel)
6161
{
6262
var result = await coursesClient.UpdateTask(taskId, taskViewModel);
6363
return Ok(result);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using HwProj.ContentService.Client;
4+
using HwProj.Models.ContentService.DTO;
5+
using HwProj.Models.CourseUnitType;
6+
7+
namespace HwProj.APIGateway.API.Filters;
8+
9+
public class FilesCountLimiter(IContentServiceClient contentServiceClient)
10+
{
11+
public const long MaxSolutionFiles = 5;
12+
13+
public async Task<bool> CheckCountLimit(ProcessFilesDTO processFilesDto)
14+
{
15+
if (processFilesDto.FilesScope.CourseUnitType == CourseUnitType.Homework) return true;
16+
17+
var existingStatuses = await contentServiceClient.GetFilesStatuses(processFilesDto.FilesScope);
18+
if (!existingStatuses.Succeeded) return false;
19+
20+
var existingIds = existingStatuses.Value.Select(f => f.Id).ToList();
21+
if (processFilesDto.DeletingFileIds.Any(id => !existingIds.Contains(id)))
22+
return false;
23+
24+
return existingIds.Count + processFilesDto.NewFiles.Count - processFilesDto.DeletingFileIds.Count <=
25+
MaxSolutionFiles;
26+
}
27+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using HwProj.CoursesService.Client;
5+
using HwProj.Models.ContentService.DTO;
6+
using HwProj.Models.CourseUnitType;
7+
using HwProj.SolutionsService.Client;
8+
9+
namespace HwProj.APIGateway.API.Filters;
10+
11+
public class FilesPrivacyFilter(
12+
ICoursesServiceClient coursesServiceClient,
13+
ISolutionsServiceClient solutionsServiceClient)
14+
{
15+
private async Task<HashSet<string>> GetSolutionStudentIds(long solutionId)
16+
{
17+
var studentIds = new HashSet<string>();
18+
var solution = await solutionsServiceClient.GetSolutionById(solutionId);
19+
studentIds.Add(solution.StudentId);
20+
21+
if (solution.GroupId is { } groupId)
22+
{
23+
var groups = await coursesServiceClient.GetGroupsById(groupId);
24+
if (groups is [var group]) studentIds.UnionWith(group.StudentsIds.ToHashSet());
25+
}
26+
27+
return studentIds;
28+
}
29+
30+
public async Task<bool> CheckDownloadRights(string? userId, ScopeDTO fileScope)
31+
{
32+
if (userId == null) return false;
33+
34+
switch (fileScope.CourseUnitType)
35+
{
36+
case CourseUnitType.Homework:
37+
return true;
38+
case CourseUnitType.Solution:
39+
{
40+
var studentIds = await GetSolutionStudentIds(fileScope.CourseUnitId);
41+
if (studentIds.Contains(userId)) return true;
42+
43+
var mentorIds = await coursesServiceClient.GetCourseLecturersIds(fileScope.CourseId);
44+
return mentorIds.Contains(userId);
45+
}
46+
default:
47+
return false;
48+
}
49+
}
50+
51+
public async Task<bool> CheckUploadRights(string? userId, ScopeDTO fileScope)
52+
{
53+
if (userId == null) return false;
54+
55+
switch (fileScope.CourseUnitType)
56+
{
57+
case CourseUnitType.Homework:
58+
{
59+
var mentorIds = await coursesServiceClient.GetCourseLecturersIds(fileScope.CourseId);
60+
return mentorIds.Contains(userId);
61+
}
62+
case CourseUnitType.Solution:
63+
{
64+
var studentIds = await GetSolutionStudentIds(fileScope.CourseUnitId);
65+
return studentIds.Contains(userId);
66+
}
67+
default:
68+
return false;
69+
}
70+
}
71+
}

HwProj.APIGateway/HwProj.APIGateway.API/Lti/Controllers/LtiAssignmentsGradesControllers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public class LtiAssignmentsGradesControllers(
2121
ILtiToolService toolService)
2222
: ControllerBase
2323
{
24-
[HttpPost("lineItem/{taskId}")]
25-
[Consumes("application/vnd.ims.lti-ags.v1.score+json")]
24+
[HttpPost("lineItem/{taskId}/scores")]
25+
[Consumes("application/json", "application/vnd.ims.lis.v1.score+json")]
2626
public async Task<IActionResult> UpdateTaskScore(long taskId, [FromBody] Score score)
2727
{
2828
var scopeClaim = User.FindFirst("scope")?.Value;

HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public void ConfigureServices(IServiceCollection services)
111111
services.AddSingleton<ILtiKeyService, LtiKeyService>();
112112

113113
services.AddScoped<CourseMentorOnlyAttribute>();
114+
services.AddScoped<FilesPrivacyFilter>();
115+
services.AddScoped<FilesCountLimiter>();
114116
}
115117

116118
public void Configure(IApplicationBuilder app, IHostEnvironment env)

HwProj.Common/HwProj.Exceptions/HwProj.Exceptions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.2</TargetFramework>
4+
<TargetFramework>netstandard2.0</TargetFramework>
55
<LangVersion>$(CSharpLanguageVersion)</LangVersion>
66
<Nullable>$(NullableReferenceTypes)</Nullable>
77
</PropertyGroup>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.DataAnnotations;
4+
using System.Linq;
5+
using Microsoft.AspNetCore.Http;
6+
using FileTypeChecker.Abstracts;
7+
using FileTypeChecker.Types;
8+
9+
namespace HwProj.Models.ContentService.Attributes
10+
{
11+
[AttributeUsage(AttributeTargets.Property)]
12+
public class CorrectFileTypeAttribute : FileValidationAttribute
13+
{
14+
private static readonly HashSet<FileType> ForbiddenFileTypes = new HashSet<FileType>
15+
{
16+
new MachO(), new Executable(), new ExecutableAndLinkableFormat()
17+
};
18+
19+
protected override ValidationResult Validate(IFormFile file)
20+
{
21+
try
22+
{
23+
using var fileContent = file.OpenReadStream();
24+
//FileTypeValidator.RegisterCustomTypes(typeof(MachO).Assembly);
25+
if ( //!FileTypeValidator.IsTypeRecognizable(fileContent) ||
26+
ForbiddenFileTypes.Any(type => type.DoesMatchWith(fileContent)))
27+
{
28+
return new ValidationResult(
29+
$"Файл `{file.FileName}` имеет недопустимый тип ${file.ContentType}");
30+
}
31+
}
32+
catch
33+
{
34+
return new ValidationResult(
35+
$"Невозможно прочитать файл `{file.FileName}`");
36+
}
37+
38+
return ValidationResult.Success;
39+
}
40+
41+
private class MachO : FileType
42+
{
43+
private const string TypeName = "MacOS executable";
44+
private const string TypeExtension = "macho";
45+
46+
private static readonly byte[][] MagicBytes =
47+
{
48+
new byte[] { 0xfe, 0xed, 0xfa, 0xce }, // Mach-O BE 32-bit
49+
new byte[] { 0xfe, 0xed, 0xfa, 0xcf }, // Mach-O BE 64-bit
50+
new byte[] { 0xce, 0xfa, 0xed, 0xfe }, // Mach-O LE 32-bit
51+
new byte[] { 0xcf, 0xfa, 0xed, 0xfe }, // Mach-O LE 64-bit
52+
};
53+
54+
public MachO() : base(TypeName, TypeExtension, MagicBytes)
55+
{
56+
}
57+
}
58+
}
59+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Linq;
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace HwProj.Models.ContentService.Attributes
7+
{
8+
public abstract class FileValidationAttribute : ValidationAttribute
9+
{
10+
protected abstract ValidationResult Validate(IFormFile file);
11+
12+
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) =>
13+
value switch
14+
{
15+
IFormFile singleFile => Validate(singleFile),
16+
IEnumerable<IFormFile> files => files
17+
.Select(Validate)
18+
.FirstOrDefault(x => x != ValidationResult.Success) ?? ValidationResult.Success,
19+
_ => null
20+
};
21+
}
22+
}

0 commit comments

Comments
 (0)