LTI: поддержка протокола LTI 1.3 для интеграции с внешними инструментами.#655
LTI: поддержка протокола LTI 1.3 для интеграции с внешними инструментами.#655KirillBorisovich wants to merge 23 commits intoInteIIigeNET:masterfrom
Conversation
…askId and ltiTaskId
…porting from an external tool, and also made validating jwt tokens
…pLinkingReturnController, added a check for matching toolId and course.LtiToolId
| { | ||
| public class HomeworkTaskLtiUrl | ||
| { | ||
| [Key] |
There was a problem hiding this comment.
| [Key] | |
| [ForeignKey] |
| [Required] | ||
| public string LtiLaunchUrl { get; set; } | ||
|
|
||
| public string? CustomParams { get; set; } |
| { | ||
| public interface ITasksRepository : ICrudRepository<HomeworkTask, long> | ||
| { | ||
| Task AddLtiUrlAsync(long taskId, LtiLaunchData ltiLaunchData); |
There was a problem hiding this comment.
| Task AddLtiUrlAsync(long taskId, LtiLaunchData ltiLaunchData); | |
| Task AddOrUpdateLtiLaunchDataAsync(long taskId, LtiLaunchData ltiLaunchData); |
| return true; | ||
| } | ||
|
|
||
| private async Task FillLtiDataForCourseDtos(IEnumerable<CourseDTO> courses) |
There was a problem hiding this comment.
| private async Task FillLtiDataForCourseDtos(IEnumerable<CourseDTO> courses) | |
| private async Task FillNecessaryLtiDataForCourseDtos(params IEnumerable<CourseDTO> courses) |
| var groups = await _groupsRepository.GetGroupsWithGroupMatesByCourse(course.Id).ToArrayAsync(); | ||
| var courseDto = course.ToCourseDto(); | ||
|
|
||
| await FillLtiDataForCourseDtos(new[] { courseDto }); |
There was a problem hiding this comment.
| await FillLtiDataForCourseDtos(new[] { courseDto }); | |
| if (courseDto.Lti.... != null) await FillLtiDataForCourseDtos(new[] { courseDto }); |
| var tasksToSave = taskPairs.Select(x => x.NewEntity); | ||
| await _tasksRepository.AddRangeAsync(tasksToSave); | ||
|
|
||
| foreach (var pair in taskPairs) |
| var result = await _courseFilterService.ApplyFiltersToCourses( | ||
| userId, coursesWithValues.Select(c => c.ToCourseDto()).ToArray()); | ||
|
|
||
| await FillLtiDataForCourseDtos(result); |
| return true; | ||
| } | ||
|
|
||
| private async Task FillLtiDataForCourseDtos(IEnumerable<CourseDTO> courses) |
| Task<HomeworkTask> AddTaskAsync(long homeworkId, PostTaskViewModel taskViewModel, LtiLaunchData? ltiLaunchData = null); | ||
| Task DeleteTaskAsync(long taskId); | ||
| Task<HomeworkTask> UpdateTaskAsync(long taskId, PostTaskViewModel taskViewModel, ActionOptions options); | ||
| Task<HomeworkTask> UpdateTaskAsync(long taskId, PostTaskViewModel taskViewModel, ActionOptions options, LtiLaunchData? ltiLaunchData = null); |
There was a problem hiding this comment.
Давай LtiLaunchData будет частью PostTaskViewModel, кушать ведь не просит
| public async Task<LtiLaunchData?> GetTaskLtiDataAsync(long taskId) | ||
| { | ||
| return await _tasksRepository.GetLtiDataAsync(taskId); | ||
| } | ||
|
|
||
| public async Task<Dictionary<long, LtiLaunchData>> GetLtiDataForTasksAsync(long[] taskIds) | ||
| { | ||
| return await _tasksRepository.GetLtiDataForTasksAsync(taskIds); | ||
| } |
| var ltiLaunchData = await _tasksService.GetTaskLtiDataAsync(taskId); | ||
| var taskViewModel = task.ToHomeworkTaskViewModel(); | ||
| taskViewModel.LtiLaunchData = ltiLaunchData.ToLtiLaunchData(); |
There was a problem hiding this comment.
Должно быть спрятано в сервисе
| var task = await _tasksService.AddTaskAsync( | ||
| homeworkId, | ||
| taskViewModel, | ||
| taskViewModel.LtiLaunchData.ToLtiLaunchData()); |
There was a problem hiding this comment.
этот параметр не нужен, давай доставать его изнутри сервиса
| await _tasksService.UpdateTaskAsync(taskId, | ||
| taskViewModel, | ||
| taskViewModel.ActionOptions ?? ActionOptions.Default, | ||
| taskViewModel.LtiLaunchData.ToLtiLaunchData()); |
|
|
||
| const setCurrentState = async () => { | ||
| const course = await ApiSingleton.coursesApi.coursesGetCourseData(+courseId!) | ||
| console.log(course) |
| courseIsLoading: boolean; | ||
|
|
||
| ltiTools: LtiToolDto[]; | ||
| ltiToolId: number | undefined; |
There was a problem hiding this comment.
давай, быть может, оперировать названиями?
| {task.ltiLaunchData ? ( | ||
| <LtiLaunchButton | ||
| courseId={courseId} | ||
| toolId={ltiToolId} | ||
| taskId={task.id || 0} | ||
| ltiLaunchData={task.ltiLaunchData} | ||
| /> | ||
| ) : ( | ||
| task.canSendSolution && ( | ||
| <Button | ||
| fullWidth | ||
| size="large" | ||
| variant="contained" | ||
| color={lastSolution ? "secondary" : "primary"} | ||
| onClick={(e) => { | ||
| e.persist() | ||
| setTaskSolutionPage((prevState) => ({ | ||
| ...prevState, | ||
| addSolution: true, | ||
| })) | ||
| }} | ||
| > | ||
| {lastSolution?.state === SolutionState.NUMBER_0 | ||
| ? "Изменить решение" | ||
| : "Добавить решение"} | ||
| </Button> | ||
| ) | ||
| )} | ||
| </Grid> |
| "EventBusUserName": "guest", | ||
| "EventBusPassword": "guest", | ||
| "EventBusUserName": "user", | ||
| "EventBusPassword": "password", |
| await _coursesServiceClient.GetCourseByTask(solution.TaskId); | ||
| var student = await _authServiceClient.GetAccountData(solutionModel.StudentId); | ||
| var studentModel = _mapper.Map<AccountDataDto>(student); | ||
| // _eventBus.Publish(new StudentPassTaskEvent(course, solutionModel, studentModel, task)); |
| public class LtiPlatformConfig | ||
| { | ||
| public string Issuer { get; set; } | ||
| public string OidcAuthorizationEndpoint { get; set; } | ||
| public string DeepLinkReturnUrl { get; set; } | ||
| public string ResourceLinkReturnUrl { get; set; } | ||
| public string AssignmentsGradesEndpoint { get; set; } | ||
| public string AccessTokenUrl { get; set; } | ||
| public string JwksEndpoint { get; set; } | ||
| public LtiSigningKeyConfig SigningKey { get; set; } | ||
| } | ||
|
|
||
| public class LtiSigningKeyConfig | ||
| { | ||
| public string KeyId { get; set; } | ||
| public string PrivateKeyPem { get; set; } | ||
| } No newline at end of file |
There was a problem hiding this comment.
в Config, это не модели
| @@ -0,0 +1,19 @@ | |||
| namespace HwProj.APIGateway.API.Lti.Models; | |||
|
|
|||
| public class LtiPlatformConfig | |||
There was a problem hiding this comment.
| public class LtiPlatformConfig | |
| internal class LtiPlatformConfig |
| public LtiSigningKeyConfig SigningKey { get; set; } | ||
| } | ||
|
|
||
| public class LtiSigningKeyConfig |
There was a problem hiding this comment.
| public class LtiSigningKeyConfig | |
| internal class LtiSigningKeyConfig |
| @@ -0,0 +1,14 @@ | |||
| namespace HwProj.APIGateway.API.Lti.Models | |||
| { | |||
| public class LtiToolConfig | |||
There was a problem hiding this comment.
| public class LtiToolConfig | |
| internal class LtiToolConfig |
| public class LtiToolDto( | ||
| long id, | ||
| string name, | ||
| string clientId, | ||
| string jwksEndpoint, | ||
| string initiateLoginUri, | ||
| string launchUrl, | ||
| string deepLink) | ||
| { | ||
| public long Id { get; init; } = id; | ||
| public string Name { get; init; } = name; | ||
| public string ClientId { get; init; } = clientId; | ||
| public string JwksEndpoint { get; init; } = jwksEndpoint; | ||
| public string InitiateLoginUri { get; init; } = initiateLoginUri; | ||
| public string LaunchUrl { get; init; } = launchUrl; | ||
| public string DeepLink { get; init; } = deepLink; | ||
| } | ||
| } No newline at end of file |
|
|
||
| public class LtiDeepLinkingContentItem | ||
| { | ||
| public string Type { get; set; } // "ltiResourceLink" |
| public Task<LtiToolDto?> GetByClientIdAsync(string clientId) | ||
| { | ||
| var cfg = _tools.FirstOrDefault(t => t.ClientId == clientId); | ||
| return Task.FromResult(cfg == null ? null : MapToDto(cfg)); |
There was a problem hiding this comment.
| return Task.FromResult(cfg == null ? null : MapToDto(cfg)); | |
| return Task.FromResult(cfg.MapToDto()); |
| public class UserTaskSolutionsPageData | ||
| { | ||
| public long CourseId { get; set; } | ||
| public long? LtiToolId { get; set; } |
| return Task.FromResult<IReadOnlyList<LtiToolDto>>(result); | ||
| } | ||
|
|
||
| public Task<LtiToolDto?> GetByIdAsync(long id) |
There was a problem hiding this comment.
почему асинхронное, если синхронное
| } | ||
|
|
||
| var client = httpClientFactory.CreateClient(); | ||
| var json = await client.GetStringAsync(jwksUrl); |
There was a problem hiding this comment.
что будет, если здесь упадет запрос?
| // В продакшене здесь стоит добавить Expire Policy (например, MemoryCache), | ||
| // чтобы обновлять ключи раз в сутки. |
| string clientId, | ||
| string toolId, | ||
| string courseId, | ||
| string targetLinkUri, | ||
| string userId, | ||
| string nonce); |
There was a problem hiding this comment.
я бы сделал класс с get/init пропертями
| } | ||
|
|
||
| string tokenString = form["JWT"]!; | ||
| var handler = new JwtSecurityTokenHandler(); |
There was a problem hiding this comment.
точно мы должны его пересоздавать? Можно ли инжектить?
|
|
||
| var resultList = new List<object>(); | ||
|
|
||
| if (unverifiedToken.Payload.TryGetValue(itemsClaimName, out var itemsObject)) |
There was a problem hiding this comment.
Действительно ли структура такая жидкая?
| <body> | ||
| <p>Задача выбрана. Возвращаемся в HwProj...</p> | ||
| <script> | ||
| var payload = {responsePayloadJson}; |
DedSec256
left a comment
There was a problem hiding this comment.
Крутая работа!
Вот основные замечания сейчас
|
|
||
| var responsePayloadJson = JsonSerializer.Serialize(resultList); | ||
|
|
||
| var htmlResponse = $@" |
There was a problem hiding this comment.
| var htmlResponse = $@" | |
| // language=html | |
| var htmlResponse = $@" |
Реализация поддержки протокола LTI 1.3 для интеграции с внешними инструментами.