Skip to content

LTI: поддержка протокола LTI 1.3 для интеграции с внешними инструментами.#655

Open
KirillBorisovich wants to merge 23 commits intoInteIIigeNET:masterfrom
KirillBorisovich:feat/lti-development
Open

LTI: поддержка протокола LTI 1.3 для интеграции с внешними инструментами.#655
KirillBorisovich wants to merge 23 commits intoInteIIigeNET:masterfrom
KirillBorisovich:feat/lti-development

Conversation

@KirillBorisovich
Copy link

Реализация поддержки протокола LTI 1.3 для интеграции с внешними инструментами.

KirillBorisovich and others added 23 commits November 30, 2025 16:01
…porting from an external tool, and also made validating jwt tokens
…pLinkingReturnController, added a check for matching toolId and course.LtiToolId
@DedSec256 DedSec256 changed the title LTI LTI: поддержки протокола LTI 1.3 для интеграции с внешними инструментами. Mar 7, 2026
@DedSec256 DedSec256 changed the title LTI: поддержки протокола LTI 1.3 для интеграции с внешними инструментами. LTI: поддержка протокола LTI 1.3 для интеграции с внешними инструментами. Mar 7, 2026
{
public class HomeworkTaskLtiUrl
{
[Key]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Key]
[ForeignKey]

[Required]
public string LtiLaunchUrl { get; set; }

public string? CustomParams { get; set; }
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///

JSON

{
public interface ITasksRepository : ICrudRepository<HomeworkTask, long>
{
Task AddLtiUrlAsync(long taskId, LtiLaunchData ltiLaunchData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Task AddLtiUrlAsync(long taskId, LtiLaunchData ltiLaunchData);
Task AddOrUpdateLtiLaunchDataAsync(long taskId, LtiLaunchData ltiLaunchData);

return true;
}

private async Task FillLtiDataForCourseDtos(IEnumerable<CourseDTO> courses)
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

аналогично

var result = await _courseFilterService.ApplyFiltersToCourses(
userId, coursesWithValues.Select(c => c.ToCourseDto()).ToArray());

await FillLtiDataForCourseDtos(result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

проверка на Lti

return true;
}

private async Task FillLtiDataForCourseDtos(IEnumerable<CourseDTO> courses)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

проверка на Lti

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай LtiLaunchData будет частью PostTaskViewModel, кушать ведь не просит

Comment on lines +118 to +126
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем нужны этим методы?

Comment on lines +48 to +50
var ltiLaunchData = await _tasksService.GetTaskLtiDataAsync(taskId);
var taskViewModel = task.ToHomeworkTaskViewModel();
taskViewModel.LtiLaunchData = ltiLaunchData.ToLtiLaunchData();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Должно быть спрятано в сервисе

var task = await _tasksService.AddTaskAsync(
homeworkId,
taskViewModel,
taskViewModel.LtiLaunchData.ToLtiLaunchData());
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

этот параметр не нужен, давай доставать его изнутри сервиса

Comment on lines +103 to +106
await _tasksService.UpdateTaskAsync(taskId,
taskViewModel,
taskViewModel.ActionOptions ?? ActionOptions.Default,
taskViewModel.LtiLaunchData.ToLtiLaunchData());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

аналогично


const setCurrentState = async () => {
const course = await ApiSingleton.coursesApi.coursesGetCourseData(+courseId!)
console.log(course)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не забыть удалить

courseIsLoading: boolean;

ltiTools: LtiToolDto[];
ltiToolId: number | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай, быть может, оперировать названиями?

Comment on lines +236 to +264
{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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай вынесем в функцию

Comment on lines -14 to +15
"EventBusUserName": "guest",
"EventBusPassword": "guest",
"EventBusUserName": "user",
"EventBusPassword": "password",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

отменить эти изменения

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines +3 to +19
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
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в Config, это не модели

@@ -0,0 +1,19 @@
namespace HwProj.APIGateway.API.Lti.Models;

public class LtiPlatformConfig
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class LtiPlatformConfig
internal class LtiPlatformConfig

public LtiSigningKeyConfig SigningKey { get; set; }
}

public class LtiSigningKeyConfig
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class LtiSigningKeyConfig
internal class LtiSigningKeyConfig

@@ -0,0 +1,14 @@
namespace HwProj.APIGateway.API.Lti.Models
{
public class LtiToolConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class LtiToolConfig
internal class LtiToolConfig

Comment on lines +3 to +20
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
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

record LtiToolDto


public class LtiDeepLinkingContentItem
{
public string Type { get; set; } // "ltiResourceLink"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

удалить

public Task<LtiToolDto?> GetByClientIdAsync(string clientId)
{
var cfg = _tools.FirstOrDefault(t => t.ClientId == clientId);
return Task.FromResult(cfg == null ? null : MapToDto(cfg));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай сделаем строкой

return Task.FromResult<IReadOnlyList<LtiToolDto>>(result);
}

public Task<LtiToolDto?> GetByIdAsync(long id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему асинхронное, если синхронное

}

var client = httpClientFactory.CreateClient();
var json = await client.GetStringAsync(jwksUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

что будет, если здесь упадет запрос?

Comment on lines +25 to +26
// В продакшене здесь стоит добавить Expire Policy (например, MemoryCache),
// чтобы обновлять ключи раз в сутки.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай сделаем

Comment on lines +9 to +14
string clientId,
string toolId,
string courseId,
string targetLinkUri,
string userId,
string nonce);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я бы сделал класс с get/init пропертями

}

string tokenString = form["JWT"]!;
var handler = new JwtSecurityTokenHandler();
Copy link
Contributor

@DedSec256 DedSec256 Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

точно мы должны его пересоздавать? Можно ли инжектить?


var resultList = new List<object>();

if (unverifiedToken.Payload.TryGetValue(itemsClaimName, out var itemsObject))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Действительно ли структура такая жидкая?

<body>
<p>Задача выбрана. Возвращаемся в HwProj...</p>
<script>
var payload = {responsePayloadJson};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потенциальная инжекция

Copy link
Contributor

@DedSec256 DedSec256 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Крутая работа!
Вот основные замечания сейчас


var responsePayloadJson = JsonSerializer.Serialize(resultList);

var htmlResponse = $@"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var htmlResponse = $@"
// language=html
var htmlResponse = $@"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants