реализация взаимодейстия LMS и external tool по LTI 1.3 с отправкой
This commit is contained in:
parent
8f96e5b3bb
commit
30bc91ee53
|
|
@ -0,0 +1,25 @@
|
|||
package ru.oa2.lti.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Configuration
|
||||
public class RestClientConfig {
|
||||
|
||||
@Bean
|
||||
public RestClient getRestClient(RestClient.Builder restClientBuilder) {
|
||||
return restClientBuilder
|
||||
.baseUrl("http://openolat.local")
|
||||
.defaultHeader(HttpHeaders.USER_AGENT, "SpringBootApp")
|
||||
.defaultStatusHandler(HttpStatusCode::isError, (req, res) -> {
|
||||
throw new RuntimeException("API error: " + res.getStatusCode() + " body: " +
|
||||
new String(res.getBody().readAllBytes(), StandardCharsets.UTF_8)); //TODO business exception
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import jakarta.servlet.ServletResponse;
|
|||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import ru.oa2.lti.ApplicationService;
|
||||
import ru.oa2.lti.model.LtiLogin;
|
||||
import ru.oa2.lti.service.jwt.Payload;
|
||||
import ru.oa2.lti.service.jwt.TokenService;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -26,16 +28,17 @@ public class LoginController {
|
|||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@PostMapping("/login")
|
||||
public String login(@RequestParam("client_id") String clientId,
|
||||
@RequestParam("iss") String iss,
|
||||
@RequestParam("login_hint") String loginHint,
|
||||
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
||||
@RequestParam("lti_message_hint") String ltiMessageHint,
|
||||
@RequestParam("target_link_uri") String targetLinkUri,
|
||||
HttpServletRequest request,
|
||||
RedirectAttributes redirectAttributes,
|
||||
ServletResponse servletResponse) {
|
||||
@RequestParam("iss") String iss,
|
||||
@RequestParam("login_hint") String loginHint,
|
||||
@RequestParam("lti_deployment_id") String ltiDeploymentId,
|
||||
@RequestParam("lti_message_hint") String ltiMessageHint,
|
||||
@RequestParam("target_link_uri") String targetLinkUri,
|
||||
HttpServletRequest request,
|
||||
RedirectAttributes redirectAttributes,
|
||||
ServletResponse servletResponse) {
|
||||
|
||||
var ltiLogin = LtiLogin.builder()
|
||||
.clientId(clientId)
|
||||
|
|
@ -49,62 +52,64 @@ public class LoginController {
|
|||
log.info("BODY: {}", ltiLogin);
|
||||
|
||||
var payload = service.getPayload(ltiLogin);
|
||||
var accessToken = tokenService.exchangeForAccessToken(ltiLogin.getClientId(), "http://openolat.local/lti/token");
|
||||
|
||||
// 4. Сохранить access token в сессии
|
||||
HttpSession session = request.getSession();
|
||||
session.setAttribute("lti_access_token", accessToken);
|
||||
session.setAttribute("lti_context_id", payload.getContextId());
|
||||
session.setAttribute("lti_user_id", ltiLogin.getClientId());
|
||||
// 4. Сохранить access token в сессии
|
||||
HttpSession session = request.getSession();
|
||||
session.setAttribute("lti_login", ltiLogin);
|
||||
session.setAttribute("lti_message_hint", Long.valueOf(ltiMessageHint));
|
||||
session.setAttribute("lti_context_id", payload.getContextId());
|
||||
session.setAttribute("lti_user_id", ltiLogin.getClientId());
|
||||
session.setAttribute("payload", payload);
|
||||
|
||||
var data = service.getTask(payload.getContextId());
|
||||
if (payload.getContextId() != null) {
|
||||
var body = tokenService.ltiAuth(ltiMessageHint, iss, loginHint);
|
||||
return body.getBody();
|
||||
}
|
||||
|
||||
redirectAttributes.addFlashAttribute("description", data);
|
||||
|
||||
//TODO заполнять из data
|
||||
redirectAttributes.addFlashAttribute("codeTitle", "Dockerfile:");
|
||||
redirectAttributes.addFlashAttribute("inputCode", "FROM maven:3.9.9-eclipse-temurin-21-jammy AS build\n" +
|
||||
"\n" +
|
||||
"COPY . /build\n" +
|
||||
"WORKDIR /build\n" +
|
||||
"\n" +
|
||||
"RUN --mount=type=cache,target=/root/.m2/repository,rw \\\n" +
|
||||
"\tmvn clean package -DskipTests -B\n" +
|
||||
"\n" +
|
||||
"FROM eclipse-temurin:21-jdk AS extract\n" +
|
||||
"\n" +
|
||||
"COPY --from=build /build/target/lti-provider-*.jar app.jar\n" +
|
||||
"RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted\n" +
|
||||
"\n" +
|
||||
"FROM eclipse-temurin:21-jre\n" +
|
||||
"\n" +
|
||||
"LABEL org.opencontainers.image.title=\"LTI Provider\"\n" +
|
||||
"LABEL org.opencontainers.image.description=\"LTI провайдер для лабораторных по Docker и Kubernetes\"\n" +
|
||||
"LABEL org.opencontainers.image.url=\"TODO\"\n" +
|
||||
"LABEL org.opencontainers.image.source=\"TODO\"\n" +
|
||||
"LABEL org.opencontainers.image.documentation=\"#TODO\"\n" +
|
||||
"\n" +
|
||||
"WORKDIR /opt\n" +
|
||||
"\n" +
|
||||
"COPY --from=extract extracted/dependencies/ ./\n" +
|
||||
"COPY --from=extract extracted/spring-boot-loader/ ./\n" +
|
||||
"COPY --from=extract extracted/snapshot-dependencies/ ./\n" +
|
||||
"COPY --from=extract extracted/application/ ./\n" +
|
||||
"\n" +
|
||||
"ENV TZ=\"Europe/Moscow\"\n" +
|
||||
"ENV JAVA_TOOL_OPTIONS=\"-Xmx1g -Xms1g\"\n" +
|
||||
"\n" +
|
||||
"ENTRYPOINT [\"java\", \"-jar\", \"/opt/app.jar\"]");
|
||||
|
||||
return "redirect:/tool/lti/docker-task";
|
||||
return "redirect:/tool/lti/select";
|
||||
}
|
||||
|
||||
@GetMapping("/docker-task")
|
||||
public String showDockerTas(Model model) {
|
||||
|
||||
if (!model.containsAttribute("description")) {
|
||||
return "redirect:/error";
|
||||
@GetMapping("/task")
|
||||
public String showDockerTas(Model model,
|
||||
HttpServletRequest request) {
|
||||
|
||||
var session = request.getSession();
|
||||
var payload = (Payload) session.getAttribute("payload");
|
||||
|
||||
if (payload != null) {
|
||||
var data = service.getTask(payload.getContextId());
|
||||
|
||||
if (data == null) {
|
||||
return "redirect:/error";
|
||||
}
|
||||
|
||||
model.addAttribute("description", data);
|
||||
model.addAttribute("codeTitle", "Dockerfile:");
|
||||
model.addAttribute("inputCode", "FROM ...");
|
||||
}
|
||||
return "docker-task";
|
||||
|
||||
return "task";
|
||||
}
|
||||
|
||||
@GetMapping("/select")
|
||||
public String select(Model model) {
|
||||
return "select";
|
||||
}
|
||||
|
||||
@PostMapping("/redirect")
|
||||
public String redirect(@RequestParam("id_token") String idToken,
|
||||
@RequestParam("state") String state,
|
||||
HttpServletRequest request) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("/redirect idToken: {}, state: {}", idToken, state);
|
||||
}
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
session.setAttribute("id_token", idToken);
|
||||
session.setAttribute("state", state);
|
||||
|
||||
return "redirect:/tool/lti/task";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
package ru.oa2.lti.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import ru.oa2.lti.ApplicationService;
|
||||
import ru.oa2.lti.model.LtiLogin;
|
||||
import ru.oa2.lti.service.jwt.JwtService;
|
||||
import ru.oa2.lti.service.jwt.TokenService;
|
||||
import ru.oa2.lti.service.results.ResultService;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -13,17 +24,44 @@ import ru.oa2.lti.ApplicationService;
|
|||
public class ResultController {
|
||||
|
||||
final ApplicationService service;
|
||||
final ResultService resultService;
|
||||
final TokenService tokenService;
|
||||
final JwtService jwtService;
|
||||
|
||||
public ResultController(ApplicationService applicationService) {
|
||||
public ResultController(ApplicationService applicationService,
|
||||
ResultService resultService,
|
||||
TokenService tokenService,
|
||||
JwtService jwtService) {
|
||||
this.service = applicationService;
|
||||
this.resultService = resultService;
|
||||
this.tokenService = tokenService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@PostMapping("/docker")
|
||||
public String result(@RequestBody String body) {
|
||||
public ResponseEntity result(@RequestBody String body,
|
||||
HttpServletRequest req) {
|
||||
|
||||
log.info("RESULT: {}", body);
|
||||
|
||||
HttpSession session = req.getSession(false);
|
||||
if (session == null) return ResponseEntity.status(401).build();
|
||||
|
||||
var ltiLogin = (LtiLogin) session.getAttribute("lti_login");
|
||||
var assessToken = tokenService.exchangeForAccessToken(ltiLogin.getClientId(), "http://openolat.local/lti/token");
|
||||
|
||||
String userId = (String) session.getAttribute("lti_user_id");
|
||||
UUID ltiContextId = (UUID) session.getAttribute("lti_context_id");
|
||||
String idToken = (String) session.getAttribute("id_token");
|
||||
|
||||
resultService.setResult(
|
||||
assessToken,
|
||||
jwtService.getTokenPayload(idToken)
|
||||
);
|
||||
|
||||
//TODO возвращать json
|
||||
return service.saveResult(body);
|
||||
return ResponseEntity
|
||||
.accepted()
|
||||
.body(service.saveResult(body));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class IdTokenPayload {
|
||||
|
||||
LocalDateTime iat;
|
||||
LocalDateTime exp;
|
||||
String iss;
|
||||
UUID aud;
|
||||
UUID sub;
|
||||
String messageType;
|
||||
ResourceLink resourceLink;
|
||||
List<String> roles;
|
||||
TokenContext context;
|
||||
NamesRoleService namesRoleService;
|
||||
LaunchPresentation launchPresentation;
|
||||
TokenEndpoint tokenEndpoint;
|
||||
String version;
|
||||
UUID deploymentId;
|
||||
String targetLinkUri;
|
||||
}
|
||||
|
|
@ -3,4 +3,6 @@ package ru.oa2.lti.service.jwt;
|
|||
public interface JwtService {
|
||||
|
||||
Payload getPayload(String jwt);
|
||||
|
||||
IdTokenPayload getTokenPayload(String jwt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
|
|
@ -18,14 +24,89 @@ public class JwtServiceImpl implements JwtService {
|
|||
public Payload getPayload(String token) {
|
||||
var jwt = jwtDecoder.decode(token);
|
||||
|
||||
return Payload.builder()
|
||||
.deploymentKey(jwt.getClaim("deploymentKey"))
|
||||
.deploymentId(UUID.fromString(jwt.getClaim("deploymentId")))
|
||||
.contextKey(jwt.getClaim("contextKey"))
|
||||
.contextId(UUID.fromString(jwt.getClaim("contextId")))
|
||||
.courseadmin(jwt.getClaim("courseadmin"))
|
||||
.coach(jwt.getClaim("coach"))
|
||||
.participant(jwt.getClaim("participant"))
|
||||
.build();
|
||||
var payload = new Payload();
|
||||
for (var key : jwt.getClaims().entrySet()) {
|
||||
switch (key.getKey()) {
|
||||
case "deploymentKey" -> payload.setDeploymentKey((Long) key.getValue());
|
||||
case "deploymentId" -> payload.setDeploymentId(UUID.fromString((String) key.getValue()));
|
||||
case "contextKey" -> payload.setContextKey((Long) key.getValue());
|
||||
case "contextId" -> payload.setContextId(UUID.fromString((String) key.getValue()));
|
||||
case "courseadmin" -> payload.setCourseadmin((Boolean) key.getValue());
|
||||
case "coach" -> payload.setCoach((Boolean) key.getValue());
|
||||
case "participant" -> payload.setParticipant((Boolean) key.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdTokenPayload getTokenPayload(String idToken) {
|
||||
var jwt = jwtDecoder.decode(idToken);
|
||||
|
||||
var payload = new IdTokenPayload();
|
||||
for (var el : jwt.getClaims().entrySet()) {
|
||||
switch (el.getKey()) {
|
||||
case "iat" -> payload.setIat(LocalDateTime.ofInstant((Instant) el.getValue(), ZoneId.systemDefault()));
|
||||
case "exp" -> payload.setExp(LocalDateTime.ofInstant((Instant) el.getValue(), ZoneId.systemDefault()));
|
||||
case "iss" -> payload.setIss((String) el.getValue());
|
||||
case "aud" -> payload.setAud(UUID.fromString((String) ((ArrayList) el.getValue()).get(0)));
|
||||
case "sub" -> payload.setSub(UUID.fromString((String) el.getValue()));
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/message_type"
|
||||
-> payload.setMessageType((String) el.getValue());
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/resource_link"
|
||||
-> {
|
||||
var m = (LinkedTreeMap) el.getValue();
|
||||
payload.setResourceLink(
|
||||
new ResourceLink(UUID.fromString((String) m.get("id")), (String) m.get("title"))
|
||||
);
|
||||
}
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/roles" ->
|
||||
payload.setRoles((List<String>) el.getValue());
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/context" -> {
|
||||
var tc = (LinkedTreeMap) el.getValue();
|
||||
payload.setContext(
|
||||
new TokenContext(
|
||||
UUID.fromString((String) tc.get("id")),
|
||||
(String) tc.get("label"),
|
||||
(String) tc.get("title"),
|
||||
(List<String>) tc.get("type")
|
||||
)
|
||||
);
|
||||
}
|
||||
case "https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" -> {
|
||||
var nrs = (LinkedTreeMap) el.getValue();
|
||||
payload.setNamesRoleService(
|
||||
new NamesRoleService(
|
||||
(String) nrs.get("context_memberships_url"),
|
||||
(List<String>) nrs.get("service_versions")
|
||||
)
|
||||
);
|
||||
}
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/launch_presentation" -> {
|
||||
payload.setLaunchPresentation(new LaunchPresentation(
|
||||
(String) ((LinkedTreeMap) el.getValue()).get("document_target")
|
||||
));
|
||||
}
|
||||
case "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" -> {
|
||||
var endpoint = (LinkedTreeMap) el.getValue();
|
||||
payload.setTokenEndpoint(
|
||||
new TokenEndpoint(
|
||||
(List<String>) endpoint.get("scope"),
|
||||
(String) endpoint.get("lineitems"),
|
||||
(String) endpoint.get("lineitem")
|
||||
)
|
||||
);
|
||||
}
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/version" ->
|
||||
payload.setVersion((String) el.getValue());
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/deployment_id" ->
|
||||
payload.setDeploymentId(UUID.fromString((String) el.getValue()));
|
||||
case "https://purl.imsglobal.org/spec/lti/claim/target_link_uri" ->
|
||||
payload.setTargetLinkUri((String) el.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
/*
|
||||
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation" : {
|
||||
"document_target" : "iframe"
|
||||
},
|
||||
*/
|
||||
public record LaunchPresentation(
|
||||
String documentTarget
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
"https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice" : {
|
||||
"context_memberships_url" : "http://openolat.local/lti/nrps/b0d12061-2564-47a0-9291-b0d226f1eefe/memberships/",
|
||||
"service_versions" : [ "2.0" ]
|
||||
},
|
||||
*/
|
||||
public record NamesRoleService(
|
||||
String contextMembershipsUrl,
|
||||
List<String>serviceVersions
|
||||
) {
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
public class Payload {
|
||||
private Long deploymentKey;
|
||||
private UUID deploymentId;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record ResourceLink(
|
||||
UUID id,
|
||||
String title
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
public record TokenContext(
|
||||
UUID id,
|
||||
String label,
|
||||
String title,
|
||||
List<String> type
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
"https://purl.imsglobal.org/spec/lti-ags/claim/endpoint" : {
|
||||
"scope" : [ "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/score" ],
|
||||
"lineitems" : "http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/",
|
||||
"lineitem" : "http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem"
|
||||
},
|
||||
*/
|
||||
public record TokenEndpoint(
|
||||
List<String> scope,
|
||||
String lineitems,
|
||||
String lineitem
|
||||
) {
|
||||
}
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
package ru.oa2.lti.service.jwt;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TokenService {
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final RestClient restClient;
|
||||
|
||||
public TokenService(RestClient restClient) {
|
||||
this.restClient = restClient;
|
||||
}
|
||||
|
||||
// Вызывается после LTI-запуска
|
||||
public String exchangeForAccessToken(
|
||||
|
|
@ -31,7 +39,7 @@ public class TokenService {
|
|||
body.add("grant_type", "client_credentials");
|
||||
body.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
|
||||
body.add("client_assertion", clientAssertion);
|
||||
body.add("scope", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/result");
|
||||
body.add("scope", "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem https://purl.imsglobal.org/spec/lti-ags/scope/scope https://purl.imsglobal.org/spec/lti-ags/claim/endpoint");
|
||||
|
||||
// Заголовки
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
|
@ -53,4 +61,24 @@ public class TokenService {
|
|||
throw new RuntimeException("Не удалось получить access token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ResponseEntity<String> ltiAuth(String ltiLoginHint, String iss, String loginHint) {
|
||||
var result = restClient
|
||||
.get()
|
||||
.uri("/lti/auth?" +
|
||||
"state=start" +
|
||||
"&code=code1" +
|
||||
"&iss=" + iss +
|
||||
"&login_hint=" + loginHint +
|
||||
"&redirect_uri=http://openolat.local/tool/lti/redirect" +
|
||||
"<i_message_hint=" + ltiLoginHint)
|
||||
.retrieve();
|
||||
|
||||
ResponseEntity<String> body = result.toEntity(String.class);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("lti/auth RESPONSE: {}", body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.oa2.lti.service.results;
|
||||
|
||||
public record Lineitems(
|
||||
String id,
|
||||
String label,
|
||||
String resourceId
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.lti.service.results;
|
||||
|
||||
import ru.oa2.lti.service.jwt.IdTokenPayload;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ResultService {
|
||||
|
||||
void setResult(String accessToken, IdTokenPayload idToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package ru.oa2.lti.service.results;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import ru.oa2.lti.service.jwt.IdTokenPayload;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ResultServiceImpl implements ResultService {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public ResultServiceImpl(RestClient client) {
|
||||
this.restClient = client;
|
||||
this.mapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(String accessToken, IdTokenPayload idToken) {
|
||||
|
||||
// http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem
|
||||
|
||||
|
||||
var resp = restClient.get()
|
||||
.uri("http://openolat.local/lti/ags/b0d12061-2564-47a0-9291-b0d226f1eefe/context/112999233990970/lineitems/112999233990979/lineitem")
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
|
||||
.header("Accept", "application/vnd.ims.lis.v1.score+json")
|
||||
.retrieve();
|
||||
|
||||
var respBody = resp.toEntity(String.class).getBody();
|
||||
try {
|
||||
Lineitems lineitems = mapper.readValue(respBody, Lineitems.class);
|
||||
|
||||
String resourceId = "112999233990970"; //TODO
|
||||
String userId = "efc0b988-cfe0-4d00-9466-cf86fcf8f885"; //TODO
|
||||
|
||||
var result = restClient
|
||||
.post()
|
||||
.uri(String.format("/lti/ags/%s/context/%s/lineitems/laba/lineitem/scores/x",
|
||||
idToken.getContext().id(), resourceId))
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.header("Content-Type", "application/vnd.ims.lis.v1.score+json")
|
||||
.header("Accept", "application/vnd.ims.lis.v1.score+json")
|
||||
.body(
|
||||
String.format(
|
||||
"{\"id\":\"%s\", \"userId\": \"%s\", \"scoreMaximum\": 1, \"label\":\"LTI page 1\", \"resourceId\": \"%s\"}",
|
||||
lineitems.id(), userId, lineitems.resourceId()
|
||||
)
|
||||
)
|
||||
.retrieve();
|
||||
log.info("RESULT RESP: {}", result.toEntity(String.class));
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<h1>(.)_(.)</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -125,9 +125,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Подключаем FontAwesome для иконок -->
|
||||
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
document.getElementById('runButton').addEventListener('click', function() {
|
||||
const codeInput = document.getElementById('codeInput').value;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.oa2.lti.jwt;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import ru.oa2.lti.config.JwtConfig;
|
||||
import ru.oa2.lti.service.jwt.JwtService;
|
||||
import ru.oa2.lti.service.jwt.JwtServiceImpl;
|
||||
|
||||
@SpringBootTest(classes = {JwtConfig.class, JwtServiceImpl.class})
|
||||
public class IdTokenTest {
|
||||
|
||||
@Autowired
|
||||
JwtService jwtService;
|
||||
|
||||
@Test
|
||||
public void idTokenTest() {
|
||||
//TODO как сделать вечный токен?
|
||||
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjMyODYzM2M0LWQ0MmMtNDhjZi1hYmJkLTZhYzE4NGNiNDVkOSJ9.eyJpYXQiOjE3NjYwNjk2NDYsImV4cCI6MTc2NjA3MzI0NiwiaXNzIjoiaHR0cHM6Ly9vcGVub2xhdC5sb2NhbCIsImF1ZCI6IjFjYjhiZDg5LTMyYTAtNDM1MC1iZDlmLTExMDQ3NzRjOThiOCIsInN1YiI6ImVmYzBiOTg4LWNmZTAtNGQwMC05NDY2LWNmODZmY2Y4Zjg4NSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL21lc3NhZ2VfdHlwZSI6Ikx0aVJlc291cmNlTGlua1JlcXVlc3QiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS9jbGFpbS9yZXNvdXJjZV9saW5rIjp7ImlkIjoiMzMyMWVjZTItMTQzYS00YzYzLTk2NGQtODQwNmI4MTlkZTk3IiwidGl0bGUiOiJEb2NrZXIifSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGkvY2xhaW0vcm9sZXMiOlsiaHR0cDovL3B1cmwuaW1zZ2xvYmFsLm9yZy92b2NhYi9saXMvdjIvbWVtYmVyc2hpcCNMZWFybmVyIl0sImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL2NvbnRleHQiOnsiaWQiOiJiMGQxMjA2MS0yNTY0LTQ3YTAtOTI5MS1iMGQyMjZmMWVlZmUiLCJsYWJlbCI6IkRvY2tlciIsInRpdGxlIjoiRG9ja2VyIiwidHlwZSI6WyJodHRwOi8vcHVybC5pbXNnbG9iYWwub3JnL3ZvY2FiL2xpcy92Mi9jb3Vyc2UjQ291cnNlU2VjdGlvbiJdfSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGktbnJwcy9jbGFpbS9uYW1lc3JvbGVzZXJ2aWNlIjp7ImNvbnRleHRfbWVtYmVyc2hpcHNfdXJsIjoiaHR0cDovL29wZW5vbGF0LmxvY2FsL2x0aS9ucnBzL2IwZDEyMDYxLTI1NjQtNDdhMC05MjkxLWIwZDIyNmYxZWVmZS9tZW1iZXJzaGlwcy8iLCJzZXJ2aWNlX3ZlcnNpb25zIjpbIjIuMCJdfSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGkvY2xhaW0vbGF1bmNoX3ByZXNlbnRhdGlvbiI6eyJkb2N1bWVudF90YXJnZXQiOiJpZnJhbWUifSwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9sdGktYWdzL2NsYWltL2VuZHBvaW50Ijp7InNjb3BlIjpbImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9saW5laXRlbSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9saW5laXRlbS5yZWFkb25seSIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpLWFncy9zY29wZS9yZXN1bHQucmVhZG9ubHkiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS1hZ3Mvc2NvcGUvc2NvcmUiXSwibGluZWl0ZW1zIjoiaHR0cDovL29wZW5vbGF0LmxvY2FsL2x0aS9hZ3MvYjBkMTIwNjEtMjU2NC00N2EwLTkyOTEtYjBkMjI2ZjFlZWZlL2NvbnRleHQvMTEyOTk5MjMzOTkwOTcwL2xpbmVpdGVtcy8iLCJsaW5laXRlbSI6Imh0dHA6Ly9vcGVub2xhdC5sb2NhbC9sdGkvYWdzL2IwZDEyMDYxLTI1NjQtNDdhMC05MjkxLWIwZDIyNmYxZWVmZS9jb250ZXh0LzExMjk5OTIzMzk5MDk3MC9saW5laXRlbXMvMTEyOTk5MjMzOTkwOTc5L2xpbmVpdGVtIn0sImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL3ZlcnNpb24iOiIxLjMuMCIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvbHRpL2NsYWltL2RlcGxveW1lbnRfaWQiOiJhOWViZGM3My04MDBmLTRmNmYtOGQ0MS0xYzYyMTViYmUyN2IiLCJodHRwczovL3B1cmwuaW1zZ2xvYmFsLm9yZy9zcGVjL2x0aS9jbGFpbS90YXJnZXRfbGlua191cmkiOiJodHRwczovL29wZW5vbGF0LmxvY2FsIn0.BsB3_7_T53Uoj1YJCOeFP11Twm10wOEVNEadyR_jWp1sM5u9Xnkag_0bBkmQigHpXvXtK2wIwi2VguujYz6t5SoBREa48rCB-qilcs1glVG9vrMPHhwsd5qGbD1y8vtfDwn_LZsi9IfW6vmDsYNUj3zGKupRBCdz46C9cRAXvgWz8Wv-dE2QYqYJmN0S7OBhiSFAXYAIwYLdq07ia4b5ufgD6AQx_sptWHpzmKatlfG8K3bqgCLa6CWH2SgrAmoXxFwimrbel4D94feA9qxnwvN9oa1T2Hs_shQXpn9KpWN0xUEmcpXsaEOG-_xpR26kalVidsqDIGr3wW8XdF8UVA";
|
||||
|
||||
var idTokenPayload = jwtService.getTokenPayload(token);
|
||||
//TODO добавить проверки
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue