SEBSERV-417 adapt Moodle API calls, error handling and fixes

This commit is contained in:
anhefti 2024-06-06 10:19:01 +02:00
parent 012b0e2f99
commit f9de99d9bc
17 changed files with 110 additions and 34 deletions

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gbl.model.user;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import com.fasterxml.jackson.annotation.JsonProperty;
public final class LoginForward {
@JsonProperty("entityKey")
public final EntityKey entityKey;
@JsonProperty("actionName")
public final String actionName;
public LoginForward(
@JsonProperty("entityKey") final EntityKey entityKey,
@JsonProperty("actionName") final String actionName) {
this.entityKey = entityKey;
this.actionName = actionName;
}
}

View file

@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.gbl.model.user;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
@ -19,7 +18,7 @@ public class TokenLoginInfo {
@JsonProperty("userUUID")
public final String userUUID;
@JsonProperty("redirect")
public final EntityKey redirect;
public final LoginForward login_forward;
@JsonProperty("login")
public final OAuth2AccessToken login;
@ -27,12 +26,13 @@ public class TokenLoginInfo {
public TokenLoginInfo(
@JsonProperty("username") final String username,
@JsonProperty("userUUID") final String userUUID,
@JsonProperty("redirect") final EntityKey redirect,
@JsonProperty("redirect") final LoginForward login_forward,
@JsonProperty("login") final OAuth2AccessToken login) {
this.username = username;
this.userUUID = userUUID;
this.redirect = redirect;
this.login_forward = login_forward;
this.login = login;
}
}

View file

@ -21,6 +21,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
import org.apache.commons.codec.binary.Base64InputStream;

View file

@ -8,8 +8,16 @@
package ch.ethz.seb.sebserver.gui.content;
import javax.servlet.http.HttpServletRequest;
import java.util.function.Consumer;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.LoginForward;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
@ -54,13 +62,19 @@ public class MainPage implements TemplateComposer {
private final WidgetFactory widgetFactory;
private final PolyglotPageService polyglotPageService;
private final AuthorizationContextHolder authorizationContextHolder;
private final PageService pageService;
public MainPage(
final WidgetFactory widgetFactory,
final PolyglotPageService polyglotPageService) {
final PolyglotPageService polyglotPageService,
final AuthorizationContextHolder authorizationContextHolder,
final PageService pageService) {
this.widgetFactory = widgetFactory;
this.polyglotPageService = polyglotPageService;
this.authorizationContextHolder = authorizationContextHolder;
this.pageService = pageService;
}
@Override
@ -158,6 +172,19 @@ public class MainPage implements TemplateComposer {
pageContext.copyOf(nav));
mainSash.setWeights(DEFAULT_SASH_WEIGHTS);
final LoginForward loginForward = authorizationContextHolder
.getAuthorizationContext()
.getLoginForward();
if (loginForward != null) {
final PageAction pageAction = pageService.pageActionBuilder(pageContext)
.newAction( ActionDefinition.valueOf(loginForward.actionName))
.withEntityKey(loginForward.entityKey)
.create();
pageService.executePageAction(pageAction);
}
}
private static final class ContentActionEventListener implements ActionEventListener {

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.content.action;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gui.content.activity.PageStateDefinitionImpl;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition;

View file

@ -456,8 +456,9 @@ public class LmsSetupForm implements TemplateComposer {
} else if (formHandle != null && formHandle.handleError(result.getError())) {
action.pageContext().notifyActivationError(EntityType.LMS_SETUP, error);
} else {
result.getOrThrow();
action.pageContext().notifyUnexpectedError(result.getError());
}
return action;
}
return handleTestResult(

View file

@ -15,6 +15,8 @@ import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpSession;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.LoginForward;
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -152,6 +154,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
private final String jwtTokenVerificationURI;
private Result<UserInfo> loggedInUser = null;
private LoginForward loginForward = null;
OAuth2AuthorizationContext(
final String guiClientId,
@ -215,6 +218,11 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
return null;
}
@Override
public LoginForward getLoginForward() {
return loginForward;
}
@Override
public boolean login(final String username, final CharSequence password) {
if (!this.valid || this.isLoggedIn()) {
@ -282,6 +290,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
final TokenLoginInfo loginInfo = response.getBody();
this.restTemplate.getOAuth2ClientContext().setAccessToken(loginInfo.login);
loginForward = loginInfo.login_forward;
return this.isLoggedIn();
} catch (final Exception e) {
log.warn("Autologin failed due to unexpected error: {}", e.getMessage());

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.LoginForward;
import org.springframework.web.client.RestTemplate;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
@ -67,4 +69,5 @@ public interface SEBServerAuthorizationContext {
CharSequence getUserPassword();
LoginForward getLoginForward();
}

View file

@ -15,10 +15,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.model.user.*;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -187,11 +184,14 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
final OAuth2AccessToken token = accessToken.getBody();
final String examId = claims.get(EXAM_ID_CLAIM, String.class);
final EntityKey redirectTo = (StringUtils.isNotBlank(examId))
final EntityKey key = (StringUtils.isNotBlank(examId))
? new EntityKey(examId, EntityType.EXAM)
: null;
final LoginForward loginForward = new LoginForward(
key,
"MONITOR_EXAM_FROM_LIST");
return new TokenLoginInfo(user.username, user.uuid, redirectTo, token);
return new TokenLoginInfo(user.username, user.uuid, loginForward, token);
});
}

View file

@ -144,8 +144,6 @@ public interface FullLmsIntegrationService {
public final String name;
@JsonProperty("url")
public final String url;
@JsonProperty("autologin_url")
public final String autoLoginURL;
@JsonProperty("access_token")
public final String access_token;
@JsonProperty("exam_templates")
@ -156,14 +154,12 @@ public interface FullLmsIntegrationService {
@JsonProperty("id") final String id,
@JsonProperty("name") final String name,
@JsonProperty("url") final String url,
@JsonProperty("autologin_url") final String autoLoginURL,
@JsonProperty("access_token") final String access_token,
@JsonProperty("exam_templates") final Collection<ExamTemplateSelection> exam_templates) {
this.id = id;
this.name = name;
this.url = url;
this.autoLoginURL = autoLoginURL;
this.access_token = access_token;
this.exam_templates = Utils.immutableCollectionOf(exam_templates);
}

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import org.springframework.context.annotation.Lazy;
@ -30,7 +31,7 @@ public interface LmsAPITemplateCacheService {
* @return LmsAPITemplate for specified LmsSetup configuration */
Result<LmsAPITemplate> getLmsAPITemplate(String lmsSetupId);
Result<LmsAPITemplate> getLmsAPITemplateForTesting(String lmsSetupId);
Result<LmsAPITemplate> createInMemoryLmsAPITemplate(LmsSetup lmsSetup);
void clearCache(String lmsSetupId);

View file

@ -49,7 +49,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateCacheServ
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -241,7 +240,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
connectionId,
lmsSetup.name,
getAPIRootURL(),
getAutoLoginURL(),
accessToken,
this.getIntegrationTemplates(lmsSetup.institutionId)
);
@ -317,8 +315,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.flatMap(this::findExam)
.map(this::checkDeletion)
.map(this::logExamDeleted)
.flatMap(teacherAccountServiceImpl::deactivateTeacherAccountsForExam)
.map(exam -> applyExamData(exam, true))
.flatMap(deleteExamAction::deleteExamInternal);
}

View file

@ -251,6 +251,16 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
}).getOrThrow();
}
@Override
public String getCourseIdFromExam(final Exam exam) {
return this.courseAccessAPI.getCourseIdFromExam(exam);
}
@Override
public String getQuizIdFromExam(final Exam exam) {
return this.courseAccessAPI.getQuizIdFromExam(exam);
}
@Override
public LmsSetupTestResult testCourseAccessAPI() {
if (this.courseAccessAPI != null) {

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport.lmsSetupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
@ -94,12 +96,13 @@ public class LmsAPITemplateCacheServiceImpl implements LmsAPITemplateCacheServic
}
@Override
public Result<LmsAPITemplate> getLmsAPITemplateForTesting(final String lmsSetupId) {
return lmsSetupDAO.byModelId(lmsSetupId)
.map(lmsSetup -> new AdHocAPITemplateDataSupplier(
lmsSetup,
this.clientCredentialService))
.flatMap(this::createLmsSetupTemplate);
public Result<LmsAPITemplate> createInMemoryLmsAPITemplate(final LmsSetup lmsSetup) {
return Result.tryCatch(() -> {
final AdHocAPITemplateDataSupplier adHocAPITemplateDataSupplier = new AdHocAPITemplateDataSupplier(
lmsSetup,
this.clientCredentialService);
return this.createLmsSetupTemplate(adHocAPITemplateDataSupplier).getOrThrow();
});
}
@Override

View file

@ -87,7 +87,7 @@ public class LmsTestServiceImpl implements LmsTestService {
public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) {
final Result<LmsAPITemplate> createLmsSetupTemplate = lmsAPITemplateCacheService
.getLmsAPITemplateForTesting(lmsSetup.getModelId());
.createInMemoryLmsAPITemplate(lmsSetup);
if (createLmsSetupTemplate.hasError()) {
return new LmsSetupTestResult(
lmsSetup.lmsType,

View file

@ -379,9 +379,9 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
queryParam.queryParams(queryParams);
}
final boolean usePOST = queryAttributes != null && !queryAttributes.isEmpty();
//final boolean usePOST = queryAttributes != null && !queryAttributes.isEmpty();
final HttpEntity<?> functionReqEntity;
if (usePOST) {
if ( queryAttributes != null && !queryAttributes.isEmpty()) {
final HttpHeaders headers = new HttpHeaders();
headers.set(
HttpHeaders.CONTENT_TYPE,
@ -394,7 +394,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
functionReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
}
return doRequest(functionName, queryParam, usePOST, functionReqEntity);
return doRequest(functionName, queryParam, true, functionReqEntity);
}
@Override
@ -465,7 +465,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
final ResponseEntity<String> response = super.exchange(
this.serverURL + this.tokenPath,
HttpMethod.GET,
HttpMethod.POST,
this.tokenReqEntity,
String.class,
this.tokenReqURIVars);

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
@ -156,7 +157,7 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
final Map<String, Map<String, String>> attributes = new HashMap<>();
final Map<String, String> data_mapping = new HashMap<>();
final Map<String, String> data_mapping = new LinkedHashMap<>();
attributes.put(ATTRIBUTE_EXAM_DATA, data_mapping);
// data[quizid]= int
@ -168,7 +169,7 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
if (BooleanUtils.isTrue(examData.exam_created)) {
data_mapping.put("addordelete", "1");
data_mapping.put("templateid", examData.template_id);
data_mapping.put("showquitlink", BooleanUtils.isTrue(examData.show_quit_link) ? "1" : "2");
data_mapping.put("showquitlink", BooleanUtils.isTrue(examData.show_quit_link) ? "1" : "0");
data_mapping.put("quitsecret", examData.quit_password);
} else {
data_mapping.put("addordelete", "0");