SEBSP-129 and SEBSERV-418
This commit is contained in:
parent
9618b942fb
commit
41b056edce
42 changed files with 1047 additions and 515 deletions
|
@ -178,8 +178,12 @@ public final class API {
|
|||
public static final String LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID = "exam_template_id";
|
||||
public static final String LMS_FULL_INTEGRATION_QUIT_PASSWORD = "quit_password";
|
||||
public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "quit_link";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_ID = "user_id";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_NAME = "user_name";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_ID = "userid_id";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_NAME = "userid_username ";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_EMAIL = "userid_email";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_FIRST_NAME = "user_firstname";
|
||||
public static final String LMS_FULL_INTEGRATION_USER_LAST_NAME = "user_lastname";
|
||||
|
||||
public static final String LMS_FULL_INTEGRATION_TIME_ZONE = "account_time_zone";
|
||||
|
||||
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";
|
||||
|
|
|
@ -18,8 +18,7 @@ import java.util.Map;
|
|||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
|
@ -65,6 +64,7 @@ public final class Exam implements GrantEntity {
|
|||
public static final String ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS = "ALLOWED_SEB_VERSIONS";
|
||||
|
||||
public static final String ADDITIONAL_ATTR_DEFAULT_CONNECTION_CONFIGURATION = "DEFAULT_CONNECTION_CONFIGURATION";
|
||||
public static final String ADDITIONAL_ATTR_QUIZ_ATTRIBUTES = "ADDITIONAL_QUIZ_ATTRIBUTES";
|
||||
|
||||
public enum ExamStatus {
|
||||
UP_COMING,
|
||||
|
@ -239,7 +239,15 @@ public final class Exam implements GrantEntity {
|
|||
}
|
||||
public Exam(final String modelId, final QuizData quizData, final POSTMapper mapper) {
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>(quizData.getAdditionalAttributes());
|
||||
String additionalQuizData = null;
|
||||
try {
|
||||
additionalQuizData = new JSONMapper().writeValueAsString(quizData.getAdditionalAttributes());
|
||||
} catch (final Exception ignored) {}
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||
if (additionalQuizData != null) {
|
||||
additionalAttributes.put(ADDITIONAL_ATTR_QUIZ_ATTRIBUTES, additionalQuizData);
|
||||
}
|
||||
additionalAttributes.put(QuizData.QUIZ_ATTR_DESCRIPTION, quizData.description);
|
||||
additionalAttributes.put(QuizData.QUIZ_ATTR_START_URL, quizData.startURL);
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@ public class ScreenProctoringSettings implements SPSAPIAccessData {
|
|||
|
||||
public static final String ATTR_ENABLE_SCREEN_PROCTORING = "enableScreenProctoring";
|
||||
public static final String ATTR_SPS_SERVICE_URL = "spsServiceURL";
|
||||
public static final String ATTR_COLLECTING_STRATEGY = "spsCollectingStrategy";
|
||||
public static final String ATTR_COLLECTING_GROUP_SIZE = "collectingGroupSize";
|
||||
|
||||
public static final String ATTR_SPS_API_KEY = "spsAPIKey";
|
||||
public static final String ATTR_SPS_API_SECRET = "spsAPISecret";
|
||||
|
@ -34,6 +32,9 @@ public class ScreenProctoringSettings implements SPSAPIAccessData {
|
|||
public static final String ATTR_SPS_ACCOUNT_ID = "spsAccountId";
|
||||
public static final String ATTR_SPS_ACCOUNT_PASSWORD = "spsAccountPassword";
|
||||
|
||||
public static final String ATTR_COLLECTING_STRATEGY = "spsCollectingStrategy";
|
||||
public static final String ATTR_COLLECTING_GROUP_SIZE = "spsCollectingGroupSize";
|
||||
|
||||
public static final String ATTR_SPS_BUNDLED = "bundled";
|
||||
|
||||
@JsonProperty(Domain.EXAM.ATTR_ID)
|
||||
|
|
|
@ -8,27 +8,33 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
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.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
|
||||
public interface TeacherAccountService {
|
||||
|
||||
default String getTeacherAccountIdentifier(
|
||||
final Exam exam,
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
||||
return getTeacherAccountIdentifier(exam.getModelId(), adHocAccountData.userId);
|
||||
}
|
||||
|
||||
String getTeacherAccountIdentifier(String examId, String userId);
|
||||
|
||||
Result<UserInfo> createNewTeacherAccountForExam(
|
||||
Exam exam,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone);
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData);
|
||||
|
||||
Result<Exam> deleteTeacherAccountsForExam(final Exam exam);
|
||||
Result<Exam> deactivateTeacherAccountsForExam(Exam exam);
|
||||
|
||||
Result<String> getOneTimeTokenForTeacherAccount(
|
||||
Exam exam,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone,
|
||||
final boolean createIfNotExists);
|
||||
FullLmsIntegrationService.AdHocAccountData adHocAccountData,
|
||||
boolean createIfNotExists);
|
||||
|
||||
Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(String token);
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ import java.util.*;
|
|||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
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;
|
||||
|
@ -28,8 +26,8 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttribut
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.TeacherAccountService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.AdminAPIClientDetails;
|
||||
import io.jsonwebtoken.Claims;
|
||||
|
@ -45,7 +43,6 @@ import org.springframework.security.authentication.BadCredentialsException;
|
|||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Lazy
|
||||
|
@ -87,35 +84,41 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
this.adminAPIClientDetails = adminAPIClientDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTeacherAccountIdentifier(final String examId, final String userId) {
|
||||
if (examId == null || userId == null) {
|
||||
throw new RuntimeException("examId and/or userId cannot be null");
|
||||
}
|
||||
|
||||
return userId + Constants.UNDERLINE + examId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<UserInfo> createNewTeacherAccountForExam(
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final String uuid = UUID.randomUUID().toString();
|
||||
DateTimeZone dtz = DateTimeZone.UTC;
|
||||
if (StringUtils.isNotBlank(timezone)) {
|
||||
if (StringUtils.isNotBlank(adHocAccountData.timezone)) {
|
||||
try {
|
||||
dtz = DateTimeZone.forID(timezone);
|
||||
dtz = DateTimeZone.forID(adHocAccountData.timezone);
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to set requested time zone for ad-hoc teacher account: {}", timezone);
|
||||
log.warn("Failed to set requested time zone for ad-hoc teacher account: {}", adHocAccountData.timezone);
|
||||
}
|
||||
}
|
||||
|
||||
final UserMod adHocTeacherUser = new UserMod(
|
||||
uuid,
|
||||
getTeacherAccountIdentifier(exam, adHocAccountData),
|
||||
exam.institutionId,
|
||||
userId,
|
||||
getTeacherAccountIdentifier(exam),
|
||||
username,
|
||||
adHocAccountData.firstName != null ? adHocAccountData.firstName : adHocAccountData.userId,
|
||||
adHocAccountData.lastName != null ? adHocAccountData.lastName : adHocAccountData.userId,
|
||||
adHocAccountData.username != null ? adHocAccountData.username : adHocAccountData.userId,
|
||||
uuid,
|
||||
uuid,
|
||||
null,
|
||||
adHocAccountData.userMail,
|
||||
Locale.ENGLISH,
|
||||
dtz,
|
||||
true,
|
||||
|
@ -130,27 +133,14 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> deleteTeacherAccountsForExam(final Exam exam) {
|
||||
public Result<Exam> deactivateTeacherAccountsForExam(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final String externalId = exam.externalId;
|
||||
final FilterMap filter = new FilterMap();
|
||||
filter.putIfAbsent(Domain.USER.ATTR_SURNAME, getTeacherAccountIdentifier(exam));
|
||||
final Collection<UserInfo> accounts = userDAO.allMatching(filter).getOrThrow();
|
||||
|
||||
if (accounts.isEmpty()) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (accounts.size() > 1) {
|
||||
log.error("Too many accounts found!?... ad-hoc teacher account mapping: {}", externalId);
|
||||
return exam;
|
||||
}
|
||||
|
||||
userDAO.delete(Utils.immutableSetOf(new EntityKey(
|
||||
accounts.iterator().next().uuid,
|
||||
EntityType.USER)))
|
||||
.getOrThrow();
|
||||
exam.supporter.stream()
|
||||
.map(userUUID -> userDAO.byModelId(userUUID).getOr(null))
|
||||
.filter(user -> user != null && user.roles.contains(UserRole.TEACHER.name()))
|
||||
.filter( user -> user.roles.size() == 1)
|
||||
.forEach( user -> userDAO.setActive(user, false));
|
||||
|
||||
return exam;
|
||||
});
|
||||
|
@ -159,19 +149,14 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
@Override
|
||||
public Result<String> getOneTimeTokenForTeacherAccount(
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone,
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData,
|
||||
final boolean createIfNotExists) {
|
||||
|
||||
return this.userDAO
|
||||
.byModelId(userId)
|
||||
.onErrorDo(error -> handleAccountDoesNotExistYet(createIfNotExists, exam, userId, username, timezone))
|
||||
.byModelId(getTeacherAccountIdentifier(exam, adHocAccountData))
|
||||
.onErrorDo(error -> handleAccountDoesNotExistYet(createIfNotExists, exam, adHocAccountData))
|
||||
.map(account -> applySupporter(account, exam))
|
||||
.map(account -> {
|
||||
this.screenProctoringService.synchronizeSPSUserForExam(exam.id);
|
||||
return account;
|
||||
})
|
||||
.map(account -> synchronizeSPSUserForExam(account, exam.id))
|
||||
.map(account -> this.createOneTimeToken(account, exam.id));
|
||||
}
|
||||
|
||||
|
@ -209,20 +194,26 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
private UserInfo handleAccountDoesNotExistYet(
|
||||
final boolean createIfNotExists,
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
||||
|
||||
if (createIfNotExists) {
|
||||
return this
|
||||
.createNewTeacherAccountForExam(exam, userId, username, timezone)
|
||||
.createNewTeacherAccountForExam(exam, adHocAccountData)
|
||||
.getOrThrow();
|
||||
} else {
|
||||
throw new RuntimeException("Teacher Account with userId "+ userId + " and username "+username+" does not exist.");
|
||||
throw new RuntimeException("Teacher Account with user "+ adHocAccountData + " does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private UserInfo applySupporter(final UserInfo account, final Exam exam) {
|
||||
// activate ad-hoc account if not active
|
||||
if (!account.isActive()) {
|
||||
userDAO.setActive(account, true)
|
||||
.onError(error -> log.error(
|
||||
"Failed to activate ad-hoc teacher account: {}, exam: {}, error {}",
|
||||
account.uuid, exam.externalId, error.getMessage()));
|
||||
}
|
||||
|
||||
if (!exam.supporter.contains(account.uuid)) {
|
||||
this.examDAO.applySupporter(exam, account.uuid)
|
||||
.onError(error -> log.error(
|
||||
|
@ -317,7 +308,10 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
.getOrElse(null);
|
||||
}
|
||||
|
||||
private static String getTeacherAccountIdentifier(final Exam exam) {
|
||||
return "AdHoc-Teacher-Account-" + exam.id;
|
||||
private UserInfo synchronizeSPSUserForExam(final UserInfo account, final Long examId) {
|
||||
if (this.screenProctoringService.isScreenProctoringEnabled(examId)) {
|
||||
this.screenProctoringService.synchronizeSPSUserForExam(examId);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ public class DeleteExamAction implements BatchActionExec {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public Result<EntityKey> deleteExamFromLMSIntegration(final Exam exam) {
|
||||
public Result<EntityKey> deleteExamInternal(final Exam exam) {
|
||||
return deleteExamDependencies(exam)
|
||||
.flatMap(this::deleteExamWithRefs)
|
||||
.map(Exam::getEntityKey)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
|
@ -109,6 +110,8 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
* @return Result refer to all exams for LMS update or to an error when happened */
|
||||
Result<Collection<Exam>> allForLMSUpdate();
|
||||
|
||||
Result<Collection<Exam>> allActiveForLMSSetup(Collection<Long> lmsId);
|
||||
|
||||
/** This is used to get all Exams that potentially needs a state change.
|
||||
* Checks if the stored running time frame of the exam is not in sync with the current state and return
|
||||
* all exams for this is the case.
|
||||
|
@ -243,5 +246,4 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
void updateQuitPassword(Exam exam, String quitPassword);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecord
|
|||
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport.examRecord;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||
|
||||
import java.sql.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -25,6 +24,7 @@ import java.util.function.Function;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -67,17 +67,20 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
private final ExamRecordDAO examRecordDAO;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
private final JSONMapper jsonMapper;
|
||||
|
||||
public ExamDAOImpl(
|
||||
final ExamRecordMapper examRecordMapper,
|
||||
final ExamRecordDAO examRecordDAO,
|
||||
final ApplicationEventPublisher applicationEventPublisher,
|
||||
final AdditionalAttributesDAO additionalAttributesDAO) {
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final JSONMapper jsonMapper) {
|
||||
|
||||
this.examRecordMapper = examRecordMapper;
|
||||
this.examRecordDAO = examRecordDAO;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -363,6 +366,25 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
.flatMap(this::toDomainModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<Exam>> allActiveForLMSSetup(final Collection<Long> lmsIds) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examRecordMapper.selectByExample()
|
||||
.where(
|
||||
ExamRecordDynamicSqlSupport.active,
|
||||
isEqualTo(BooleanUtils.toInteger(true)))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.lmsSetupId,
|
||||
isIn(lmsIds))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isNotEqualTo(ExamStatus.ARCHIVED.name()))
|
||||
.build()
|
||||
.execute();
|
||||
}).flatMap(this::toDomainModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<Exam>> allThatNeedsStatusUpdate(final long leadTime, final long followupTime) {
|
||||
return this.examRecordDAO
|
||||
|
@ -862,7 +884,15 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
}
|
||||
|
||||
private QuizData saveAdditionalQuizAttributes(final Long examId, final QuizData quizData) {
|
||||
final Map<String, String> additionalAttributes = new HashMap<>(quizData.getAdditionalAttributes());
|
||||
String additionalQuizData = null;
|
||||
try {
|
||||
additionalQuizData = jsonMapper.writeValueAsString(quizData.getAdditionalAttributes());
|
||||
} catch (final Exception ignored) {}
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||
if (additionalQuizData != null) {
|
||||
additionalAttributes.put(Exam.ADDITIONAL_ATTR_QUIZ_ATTRIBUTES, additionalQuizData);
|
||||
}
|
||||
if (StringUtils.isNotBlank(quizData.description)) {
|
||||
additionalAttributes.put(QuizData.QUIZ_ATTR_DESCRIPTION, quizData.description);
|
||||
} else {
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.Objects;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -50,15 +51,18 @@ public class ProctoringSettingsDAOImpl implements ProctoringSettingsDAO {
|
|||
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||
private final WebserviceInfo.ScreenProctoringServiceBundle screenProctoringServiceBundle;
|
||||
private final Cryptor cryptor;
|
||||
|
||||
public ProctoringSettingsDAOImpl(
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
this.screenProctoringServiceBundle = webserviceInfo.getScreenProctoringServiceBundle();
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
|
@ -165,8 +169,6 @@ public class ProctoringSettingsDAOImpl implements ProctoringSettingsDAO {
|
|||
return Result.tryCatch(() -> {
|
||||
final Long entityId = Long.parseLong(entityKey.modelId);
|
||||
|
||||
//checkType(parentEntityKey);
|
||||
|
||||
return this.additionalAttributesDAO
|
||||
.getAdditionalAttributes(entityKey.entityType, entityId)
|
||||
.map(attrs -> attrs.stream()
|
||||
|
@ -174,16 +176,31 @@ public class ProctoringSettingsDAOImpl implements ProctoringSettingsDAO {
|
|||
AdditionalAttributeRecord::getName,
|
||||
Function.identity())))
|
||||
.map(mapping -> {
|
||||
return new ScreenProctoringSettings(
|
||||
entityId,
|
||||
getScreenproctoringEnabled(mapping),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_SERVICE_URL),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_API_KEY),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_API_SECRET),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD),
|
||||
getScreenProctoringCollectingStrategy(mapping),
|
||||
getScreenProctoringCollectingSize(mapping));
|
||||
if (screenProctoringServiceBundle.bundled) {
|
||||
return new ScreenProctoringSettings(
|
||||
entityId,
|
||||
getScreenproctoringEnabled(mapping),
|
||||
screenProctoringServiceBundle.serviceURL,
|
||||
screenProctoringServiceBundle.clientId,
|
||||
screenProctoringServiceBundle.clientSecret.toString(),
|
||||
screenProctoringServiceBundle.apiAccountName,
|
||||
screenProctoringServiceBundle.apiAccountPassword.toString(),
|
||||
getScreenProctoringCollectingStrategy(mapping),
|
||||
getScreenProctoringCollectingSize(mapping),
|
||||
true);
|
||||
} else {
|
||||
return new ScreenProctoringSettings(
|
||||
entityId,
|
||||
getScreenproctoringEnabled(mapping),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_SERVICE_URL),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_API_KEY),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_API_SECRET),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID),
|
||||
getString(mapping, ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD),
|
||||
getScreenProctoringCollectingStrategy(mapping),
|
||||
getScreenProctoringCollectingSize(mapping),
|
||||
false);
|
||||
}
|
||||
})
|
||||
.getOrThrow();
|
||||
});
|
||||
|
@ -203,33 +220,43 @@ public class ProctoringSettingsDAOImpl implements ProctoringSettingsDAO {
|
|||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING,
|
||||
String.valueOf(screenProctoringSettings.enableScreenProctoring));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_SERVICE_URL,
|
||||
StringUtils.trim(screenProctoringSettings.spsServiceURL));
|
||||
|
||||
// we need to store this only if it is an unbundled setup otherwise attributes are known by the service
|
||||
if (!screenProctoringServiceBundle.bundled) {
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_SERVICE_URL,
|
||||
StringUtils.trim(screenProctoringSettings.spsServiceURL));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_API_KEY,
|
||||
StringUtils.trim(screenProctoringSettings.spsAPIKey));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_API_SECRET,
|
||||
encryptSecret(Utils.trim(screenProctoringSettings.spsAPISecret)));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID,
|
||||
StringUtils.trim(screenProctoringSettings.spsAccountId));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD,
|
||||
encryptSecret(Utils.trim(screenProctoringSettings.spsAccountPassword)));
|
||||
}
|
||||
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_COLLECTING_STRATEGY,
|
||||
String.valueOf(screenProctoringSettings.collectingStrategy));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_COLLECTING_GROUP_SIZE,
|
||||
String.valueOf(screenProctoringSettings.collectingGroupSize));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_API_KEY,
|
||||
StringUtils.trim(screenProctoringSettings.spsAPIKey));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_API_SECRET,
|
||||
encryptSecret(Utils.trim(screenProctoringSettings.spsAPISecret)));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_ID,
|
||||
StringUtils.trim(screenProctoringSettings.spsAccountId));
|
||||
attributes.put(
|
||||
ScreenProctoringSettings.ATTR_SPS_ACCOUNT_PASSWORD,
|
||||
encryptSecret(Utils.trim(screenProctoringSettings.spsAccountPassword)));
|
||||
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttributes(
|
||||
entityKey.entityType,
|
||||
entityId,
|
||||
attributes,
|
||||
true);
|
||||
true)
|
||||
.onError(error -> log.warn(
|
||||
"Failed to store SPS attributes for Exam: {} error: {}",
|
||||
entityKey,
|
||||
error.getMessage()));
|
||||
|
||||
return screenProctoringSettings;
|
||||
});
|
||||
|
|
|
@ -59,7 +59,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint.RevokeExamTokenEvent;
|
||||
|
||||
@Lazy
|
||||
|
@ -722,7 +722,7 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
|
|||
|
||||
// clear cache
|
||||
this.cacheManager
|
||||
.getCache(ClientConfigService.EXAM_CLIENT_DETAILS_CACHE)
|
||||
.getCache(ConnectionConfigurationService.EXAM_CLIENT_DETAILS_CACHE)
|
||||
.evictIfPresent(rec.getClientName());
|
||||
|
||||
} catch (final Exception e) {
|
||||
|
|
|
@ -18,20 +18,12 @@ public interface ExamAdminService {
|
|||
|
||||
ProctoringAdminService getProctoringAdminService();
|
||||
|
||||
Result<Exam> applyExamImportInitialization(Exam exam);
|
||||
|
||||
/** Get the exam domain object for the exam identifier (PK).
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the domain object or to an error when happened */
|
||||
Result<Exam> examForPK(Long examId);
|
||||
|
||||
/** Initializes initial additional attributes for a yet created exam.
|
||||
*
|
||||
* @param exam The exam that has been created
|
||||
* @return The exam with the initial additional attributes */
|
||||
Result<Exam> initAdditionalAttributes(final Exam exam);
|
||||
|
||||
/** Saves the security key settings for an specific exam.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
|
@ -45,13 +37,6 @@ public interface ExamAdminService {
|
|||
Boolean enabled,
|
||||
Integer numThreshold);
|
||||
|
||||
/** Applies all additional SEB restriction attributes that are defined by the
|
||||
* type of the LMS of a given Exam to this given Exam.
|
||||
*
|
||||
* @param exam the Exam to apply all additional SEB restriction attributes
|
||||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> applyAdditionalSEBRestrictions(Exam exam);
|
||||
|
||||
/** Indicates whether a specific exam is being restricted with SEB restriction feature on the LMS or not.
|
||||
*
|
||||
* @param exam The exam instance
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.webservice.servicelayer.exam;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
public interface ExamImportService {
|
||||
|
||||
Result<Exam> applyExamImportInitialization(Exam exam);
|
||||
|
||||
/** Initializes initial additional attributes for a yet created exam.
|
||||
*
|
||||
* @param exam The exam that has been created
|
||||
* @return The exam with the initial additional attributes */
|
||||
Result<Exam> initAdditionalAttributes(final Exam exam);
|
||||
}
|
|
@ -8,46 +8,31 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.*;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
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.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringService;
|
||||
|
||||
@Lazy
|
||||
|
@ -63,13 +48,10 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final boolean appSignatureKeyEnabled;
|
||||
private final int defaultNumericalTrustThreshold;
|
||||
|
||||
private final ExamConfigurationValueService examConfigurationValueService;
|
||||
private final SEBRestrictionService sebRestrictionService;
|
||||
|
||||
private final ExamTemplateService examTemplateService;
|
||||
|
||||
protected ExamAdminServiceImpl(
|
||||
final ExamDAO examDAO,
|
||||
final ProctoringAdminService proctoringAdminService,
|
||||
|
@ -78,10 +60,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final ExamConfigurationValueService examConfigurationValueService,
|
||||
final SEBRestrictionService sebRestrictionService,
|
||||
final ExamTemplateService examTemplateService,
|
||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled,
|
||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) {
|
||||
final SEBRestrictionService sebRestrictionService) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.proctoringAdminService = proctoringAdminService;
|
||||
|
@ -90,10 +69,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.examConfigurationValueService = examConfigurationValueService;
|
||||
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
||||
this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold;
|
||||
this.sebRestrictionService = sebRestrictionService;
|
||||
this.examTemplateService = examTemplateService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -101,80 +77,11 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
return this.proctoringAdminService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyExamImportInitialization(final Exam exam) {
|
||||
final List<APIMessage> errors = new ArrayList<>();
|
||||
|
||||
this.initAdditionalAttributes(exam)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)))
|
||||
.flatMap(this.examTemplateService::addDefinedIndicators)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error)))
|
||||
.flatMap(this.examTemplateService::addDefinedClientGroups)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CLIENT_GROUPS.of(error)))
|
||||
.flatMap(this.examTemplateService::initAdditionalTemplateAttributes)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)))
|
||||
.flatMap(this.examTemplateService::initExamConfiguration)
|
||||
.onError(error -> {
|
||||
if (error instanceof APIMessageException) {
|
||||
errors.addAll(((APIMessageException) error).getAPIMessages());
|
||||
} else {
|
||||
errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG.of(error));
|
||||
}
|
||||
})
|
||||
.flatMap(this::applyAdditionalSEBRestrictions)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error)))
|
||||
.flatMap(this::applyQuitPassword)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_QUIT_PASSWORD.of(error)))
|
||||
.flatMap(examTemplateService::applyScreenProctoringSettingsForExam)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_SCREEN_PROCTORING_SETTINGS.of(error)));
|
||||
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
errors.add(0, APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
|
||||
exam.getModelId(),
|
||||
API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + exam.getModelId()));
|
||||
|
||||
log.warn("Exam successfully created but some initialization did go wrong: {}", errors);
|
||||
|
||||
throw new APIMessageException(errors);
|
||||
} else {
|
||||
return this.examDAO.byPK(exam.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> examForPK(final Long examId) {
|
||||
return this.examDAO.byPK(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> initAdditionalAttributes(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
final Long examId = exam.getId();
|
||||
|
||||
// initialize App-Signature-Key feature attributes
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
||||
String.valueOf(this.appSignatureKeyEnabled));
|
||||
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD,
|
||||
String.valueOf(this.defaultNumericalTrustThreshold));
|
||||
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT,
|
||||
KeyGenerators.string().generateKey());
|
||||
|
||||
return exam;
|
||||
}).flatMap(this::initAdditionalAttributesForMoodleExams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> saveSecurityKeySettings(
|
||||
final Long institutionId,
|
||||
|
@ -207,44 +114,6 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}).flatMap(v -> this.examDAO.byPK(examId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// this only applies to exams that are attached to an LMS
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply additional SEB restrictions for exam: {}",
|
||||
exam.externalId);
|
||||
}
|
||||
|
||||
final LmsSetup lmsSetup = this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsSetup.lmsType == LmsType.OPEN_EDX) {
|
||||
final List<String> permissions = Arrays.asList(
|
||||
OpenEdxSEBRestriction.PermissionComponent.ALWAYS_ALLOW_STAFF.key,
|
||||
OpenEdxSEBRestriction.PermissionComponent.CHECK_CONFIG_KEY.key);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
SEBRestrictionService.SEB_RESTRICTION_ADDITIONAL_PROPERTY_NAME_PREFIX +
|
||||
OpenEdxSEBRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
StringUtils.join(permissions, Constants.LIST_SEPARATOR_CHAR))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return this.examDAO
|
||||
.byPK(exam.id)
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> isRestricted(final Exam exam) {
|
||||
if (exam == null || exam.lmsSetupId == null) {
|
||||
|
@ -283,7 +152,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
|
||||
@Override
|
||||
public Result<ProctoringServiceSettings> getProctoringServiceSettings(final Long examId) {
|
||||
return this.proctoringAdminService.getProctoringSettings(new EntityKey(examId, EntityType.EXAM));
|
||||
return this.proctoringAdminService
|
||||
.getProctoringSettings(new EntityKey(examId, EntityType.EXAM));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -377,60 +247,27 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyQuitPassword(final Exam exam) {
|
||||
public Result<Exam> applyQuitPassword(final Exam exam) {
|
||||
return this.examConfigurationValueService
|
||||
.applyQuitPasswordToConfigs(exam.id, exam.quitPassword)
|
||||
.flatMap(id -> this.sebRestrictionService.applySEBClientRestriction(exam))
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(e.id, true))
|
||||
.onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
|
||||
.map(id -> applySEBRestrictionIfExamRunning(exam))
|
||||
.onError(t -> log.error("Failed to quit password for Exam: {}", exam, t));
|
||||
}
|
||||
|
||||
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
// TODO check if this is still needed
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsType.MOODLE) {
|
||||
lmsTemplate.getQuiz(exam.externalId)
|
||||
.flatMap(quizData -> this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
quizData.name))
|
||||
.onError(error -> log.error("Failed to create additional moodle quiz name attribute: ", error));
|
||||
}
|
||||
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsType.MOODLE_PLUGIN) {
|
||||
// Save additional Browser Exam Key for Moodle plugin integration SEBSERV-372
|
||||
try {
|
||||
|
||||
final String moodleBEKUUID = UUID.randomUUID().toString();
|
||||
final MessageDigest hasher = MessageDigest.getInstance(Constants.SHA_256);
|
||||
hasher.update(Utils.toByteArray(moodleBEKUUID));
|
||||
final String moodleBEK = Hex.toHexString(hasher.digest());
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
SEBRestrictionService.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK,
|
||||
moodleBEK)
|
||||
.getOrThrow();
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to create additional moodle SEB BEK attribute: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Exam applySEBRestrictionIfExamRunning(final Exam exam) {
|
||||
if (exam.status != ExamStatus.RUNNING) {
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
return this.sebRestrictionService
|
||||
.applySEBClientRestriction(exam)
|
||||
.flatMap(e -> this.examDAO.setSEBRestriction(e.id, true))
|
||||
.onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t))
|
||||
.getOr(exam);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Result<Exam> archiveExam(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* 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.webservice.servicelayer.exam.impl;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
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 ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ExamImportServiceImpl implements ExamImportService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamImportServiceImpl.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
// private final FullLmsIntegrationService fullLmsIntegrationService;
|
||||
private final ExamTemplateService examTemplateService;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final boolean appSignatureKeyEnabled;
|
||||
private final int defaultNumericalTrustThreshold;
|
||||
|
||||
public ExamImportServiceImpl(
|
||||
final ExamDAO examDAO,
|
||||
final ExamTemplateService examTemplateService,
|
||||
final ExamAdminService examAdminService,
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled,
|
||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.examTemplateService = examTemplateService;
|
||||
this.examAdminService = examAdminService;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
||||
this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold;
|
||||
}
|
||||
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyExamImportInitialization(final Exam exam) {
|
||||
final List<APIMessage> errors = new ArrayList<>();
|
||||
|
||||
this.initAdditionalAttributes(exam)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)))
|
||||
.flatMap(this.examTemplateService::addDefinedIndicators)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error)))
|
||||
.flatMap(this.examTemplateService::addDefinedClientGroups)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CLIENT_GROUPS.of(error)))
|
||||
.flatMap(this.examTemplateService::initAdditionalTemplateAttributes)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error)))
|
||||
.flatMap(this.examTemplateService::initExamConfiguration)
|
||||
.onError(error -> {
|
||||
if (error instanceof APIMessage.APIMessageException) {
|
||||
errors.addAll(((APIMessage.APIMessageException) error).getAPIMessages());
|
||||
} else {
|
||||
errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG.of(error));
|
||||
}
|
||||
})
|
||||
.flatMap(this::applyAdditionalSEBRestrictions)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error)))
|
||||
.flatMap(examAdminService::applyQuitPassword)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_QUIT_PASSWORD.of(error)))
|
||||
.flatMap(examTemplateService::applyScreenProctoringSettingsForExam)
|
||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_SCREEN_PROCTORING_SETTINGS.of(error)));
|
||||
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
errors.add(0, APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
|
||||
exam.getModelId(),
|
||||
API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + exam.getModelId()));
|
||||
|
||||
log.warn("Exam successfully created but some initialization did go wrong: {}", errors);
|
||||
|
||||
throw new APIMessage.APIMessageException(errors);
|
||||
} else {
|
||||
return this.examDAO.byPK(exam.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> initAdditionalAttributes(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
final Long examId = exam.getId();
|
||||
|
||||
// initialize App-Signature-Key feature attributes
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
||||
String.valueOf(this.appSignatureKeyEnabled));
|
||||
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD,
|
||||
String.valueOf(this.defaultNumericalTrustThreshold));
|
||||
|
||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT,
|
||||
KeyGenerators.string().generateKey());
|
||||
|
||||
return exam;
|
||||
}).flatMap(this::initAdditionalAttributesForMoodleExams);
|
||||
}
|
||||
|
||||
private Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// this only applies to exams that are attached to an LMS
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply additional SEB restrictions for exam: {}",
|
||||
exam.externalId);
|
||||
}
|
||||
|
||||
final LmsSetup lmsSetup = this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsSetup.lmsType == LmsSetup.LmsType.OPEN_EDX) {
|
||||
final List<String> permissions = Arrays.asList(
|
||||
OpenEdxSEBRestriction.PermissionComponent.ALWAYS_ALLOW_STAFF.key,
|
||||
OpenEdxSEBRestriction.PermissionComponent.CHECK_CONFIG_KEY.key);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
SEBRestrictionService.SEB_RESTRICTION_ADDITIONAL_PROPERTY_NAME_PREFIX +
|
||||
OpenEdxSEBRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
StringUtils.join(permissions, Constants.LIST_SEPARATOR_CHAR))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return this.examDAO
|
||||
.byPK(exam.id)
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsSetup.LmsType.MOODLE) {
|
||||
lmsTemplate.getQuiz(exam.externalId)
|
||||
.flatMap(quizData -> this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
quizData.name))
|
||||
.onError(error -> log.error("Failed to create additional moodle quiz name attribute: ", error));
|
||||
}
|
||||
|
||||
if (lmsTemplate.lmsSetup().lmsType == LmsSetup.LmsType.MOODLE_PLUGIN) {
|
||||
// Save additional Browser Exam Key for Moodle plugin integration SEBSERV-372
|
||||
try {
|
||||
|
||||
final String moodleBEKUUID = UUID.randomUUID().toString();
|
||||
final MessageDigest hasher = MessageDigest.getInstance(Constants.SHA_256);
|
||||
hasher.update(Utils.toByteArray(moodleBEKUUID));
|
||||
final String moodleBEK = Hex.toHexString(hasher.digest());
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
exam.id,
|
||||
SEBRestrictionService.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK,
|
||||
moodleBEK)
|
||||
.getOrThrow();
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to create additional moodle SEB BEK attribute: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -100,25 +100,25 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
|
|||
|
||||
checkType(parentEntityKey);
|
||||
|
||||
ScreenProctoringSettings settings = this.proctoringSettingsDAO
|
||||
return this.proctoringSettingsDAO
|
||||
.getScreenProctoringSettings(parentEntityKey)
|
||||
.getOrThrow();
|
||||
|
||||
if (this.screenProctoringServiceBundle.bundled) {
|
||||
settings = new ScreenProctoringSettings(
|
||||
settings.examId,
|
||||
settings.enableScreenProctoring,
|
||||
this.screenProctoringServiceBundle.serviceURL,
|
||||
this.screenProctoringServiceBundle.clientId,
|
||||
null,
|
||||
this.screenProctoringServiceBundle.apiAccountName,
|
||||
null,
|
||||
settings.collectingStrategy,
|
||||
settings.collectingGroupSize,
|
||||
true);
|
||||
}
|
||||
|
||||
return settings;
|
||||
// if (this.screenProctoringServiceBundle.bundled) {
|
||||
// settings = new ScreenProctoringSettings(
|
||||
// settings.examId,
|
||||
// settings.enableScreenProctoring,
|
||||
// this.screenProctoringServiceBundle.serviceURL,
|
||||
// this.screenProctoringServiceBundle.clientId,
|
||||
// null,
|
||||
// this.screenProctoringServiceBundle.apiAccountName,
|
||||
// null,
|
||||
// settings.collectingStrategy,
|
||||
// settings.collectingGroupSize,
|
||||
// true);
|
||||
// }
|
||||
//
|
||||
// return settings;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,14 @@ public interface CourseAccessAPI {
|
|||
}
|
||||
}
|
||||
|
||||
default String getCourseIdFromExam(final Exam exam) {
|
||||
return exam.externalId;
|
||||
}
|
||||
|
||||
default String getQuizIdFromExam(final Exam exam) {
|
||||
return exam.externalId;
|
||||
}
|
||||
|
||||
void fetchQuizzes(FilterMap filterMap, AsyncQuizFetchBuffer asyncQuizFetchBuffer);
|
||||
|
||||
/** Get all {@link QuizData } for the set of {@link QuizData } identifiers from LMS API in a collection
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.ExamData;
|
||||
|
||||
public interface FullLmsIntegrationAPI {
|
||||
|
||||
|
@ -24,6 +26,10 @@ public interface FullLmsIntegrationAPI {
|
|||
|
||||
Result<IntegrationData> applyConnectionDetails(IntegrationData data);
|
||||
|
||||
Result<ExamData> applyExamData(ExamData examData);
|
||||
|
||||
Result<Exam> applyConnectionConfiguration(Exam exam, byte[] configData);
|
||||
|
||||
Result<String> deleteConnectionDetails();
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
|||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -18,10 +19,12 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
public interface FullLmsIntegrationService {
|
||||
|
||||
|
@ -30,6 +33,16 @@ public interface FullLmsIntegrationService {
|
|||
|
||||
@EventListener
|
||||
void notifyExamTemplateChange(final ExamTemplateChangeEvent event);
|
||||
@EventListener(ConnectionConfigurationChangeEvent.class)
|
||||
void notifyConnectionConfigurationChange(ConnectionConfigurationChangeEvent event);
|
||||
|
||||
@EventListener(ExamDeletionEvent.class)
|
||||
void notifyExamDeletion(ExamDeletionEvent event);
|
||||
|
||||
/** Applies the exam data to LMS to inform the LMS that the exam exists on SEB Server site.
|
||||
* @param exam The Exam
|
||||
*/
|
||||
Result<Exam> applyExamDataToLMS(Exam exam);
|
||||
|
||||
Result<IntegrationData> applyFullLmsIntegration(Long lmsSetupId);
|
||||
|
||||
|
@ -48,8 +61,7 @@ public interface FullLmsIntegrationService {
|
|||
String courseId,
|
||||
String quizId);
|
||||
|
||||
@EventListener(ExamDeletionEvent.class)
|
||||
void notifyExamDeletion(ExamDeletionEvent event);
|
||||
|
||||
|
||||
Result<Void> streamConnectionConfiguration(
|
||||
String lmsUUID,
|
||||
|
@ -61,9 +73,68 @@ public interface FullLmsIntegrationService {
|
|||
String lmsUUId,
|
||||
String courseId,
|
||||
String quizId,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone);
|
||||
AdHocAccountData adHocAccountData);
|
||||
|
||||
final class AdHocAccountData {
|
||||
public final String userId;
|
||||
public final String username;
|
||||
public final String userMail;
|
||||
public final String firstName;
|
||||
public final String lastName;
|
||||
public final String timezone;
|
||||
|
||||
public AdHocAccountData(
|
||||
final String userId,
|
||||
final String username,
|
||||
final String userMail,
|
||||
final String firstName,
|
||||
final String lastName,
|
||||
final String timezone) {
|
||||
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.userMail = userMail;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.timezone = timezone;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
final class ExamData {
|
||||
@JsonProperty("id")
|
||||
public final String id;
|
||||
@JsonProperty("course_id")
|
||||
public final String course_id;
|
||||
@JsonProperty("quiz_id")
|
||||
public final String quiz_id;
|
||||
@JsonProperty("exam_created")
|
||||
public final Boolean exam_created;
|
||||
@JsonProperty("template_id")
|
||||
public final String template_id;
|
||||
@JsonProperty("show_quit_link")
|
||||
public final Boolean show_quit_link;
|
||||
@JsonProperty("quit_password")
|
||||
public final String quit_password;
|
||||
|
||||
public ExamData(
|
||||
final String id,
|
||||
final String course_id,
|
||||
final String quiz_id,
|
||||
final Boolean exam_created,
|
||||
final String template_id,
|
||||
final Boolean show_quit_link,
|
||||
final String quit_password) {
|
||||
|
||||
this.id = id;
|
||||
this.course_id = course_id;
|
||||
this.quiz_id = quiz_id;
|
||||
this.exam_created = exam_created;
|
||||
this.template_id = template_id;
|
||||
this.show_quit_link = show_quit_link;
|
||||
this.quit_password = quit_password;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
final class IntegrationData {
|
||||
|
@ -75,7 +146,6 @@ public interface FullLmsIntegrationService {
|
|||
public final String url;
|
||||
@JsonProperty("autologin_url")
|
||||
public final String autoLoginURL;
|
||||
|
||||
@JsonProperty("access_token")
|
||||
public final String access_token;
|
||||
@JsonProperty("exam_templates")
|
||||
|
@ -117,6 +187,20 @@ public interface FullLmsIntegrationService {
|
|||
this.template_name = template_name;
|
||||
this.template_description = template_description;
|
||||
}
|
||||
}
|
||||
|
||||
final class TokenLoginResponse {
|
||||
@JsonProperty("id")
|
||||
public final String id;
|
||||
@JsonProperty("login_link")
|
||||
public final String loginLink;
|
||||
|
||||
public TokenLoginResponse(
|
||||
final String id,
|
||||
final String loginLink) {
|
||||
|
||||
this.id = id;
|
||||
this.loginLink = loginLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess;
|
||||
|
|
|
@ -8,14 +8,17 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
|
@ -35,13 +38,15 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.TeacherA
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.DeleteExamAction;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
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.lang3.StringUtils;
|
||||
|
@ -66,11 +71,12 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
private final TeacherAccountServiceImpl teacherAccountServiceImpl;
|
||||
private final SEBClientConfigDAO sebClientConfigDAO;
|
||||
private final ClientConfigService clientConfigService;
|
||||
private final ConnectionConfigurationService connectionConfigurationService;
|
||||
private final DeleteExamAction deleteExamAction;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamImportService examImportService;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final ExamConfigurationValueService examConfigurationValueService;
|
||||
private final ExamDAO examDAO;
|
||||
private final ExamTemplateDAO examTemplateDAO;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
|
@ -85,12 +91,13 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
final UserDAO userDAO,
|
||||
final SEBClientConfigDAO sebClientConfigDAO,
|
||||
final ScreenProctoringService screenProctoringService,
|
||||
final ClientConfigService clientConfigService,
|
||||
final ConnectionConfigurationService connectionConfigurationService,
|
||||
final DeleteExamAction deleteExamAction,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamSessionService examSessionService,
|
||||
final ExamConfigurationValueService examConfigurationValueService,
|
||||
final ExamDAO examDAO,
|
||||
final ExamImportService examImportService,
|
||||
final ExamTemplateDAO examTemplateDAO,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
|
@ -104,16 +111,17 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.teacherAccountServiceImpl = teacherAccountServiceImpl;
|
||||
this.sebClientConfigDAO = sebClientConfigDAO;
|
||||
this.clientConfigService = clientConfigService;
|
||||
this.connectionConfigurationService = connectionConfigurationService;
|
||||
this.deleteExamAction = deleteExamAction;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examSessionService = examSessionService;
|
||||
this.examDAO = examDAO;
|
||||
this.examTemplateDAO = examTemplateDAO;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
this.lmsAPIEndpoint = lmsAPIEndpoint;
|
||||
this.userService = userService;
|
||||
this.examConfigurationValueService = examConfigurationValueService;
|
||||
this.examImportService = examImportService;
|
||||
|
||||
resource = new ClientCredentialsResourceDetails();
|
||||
resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI());
|
||||
|
@ -133,13 +141,18 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyExamDataToLMS(final Exam exam) {
|
||||
return Result.tryCatch(() -> this.applyExamData(exam, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyExamDeletion(final ExamDeletionEvent event) {
|
||||
event.ids.forEach( examId -> {
|
||||
this.examDAO.byPK(examId)
|
||||
.map(this.teacherAccountServiceImpl::deleteTeacherAccountsForExam)
|
||||
.onError(error -> log.warn("Failed delete teacher accounts for exam: {}", examId));
|
||||
});
|
||||
event.ids.forEach( examId -> this.examDAO.byPK(examId)
|
||||
.flatMap(this.teacherAccountServiceImpl::deactivateTeacherAccountsForExam)
|
||||
.map(exam -> applyExamData(exam, true))
|
||||
.onError(error -> log.warn("Failed delete teacher accounts for exam: {}", examId))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -170,17 +183,37 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
lmsSetupDAO.idsOfActiveWithFullIntegration(examTemplate.institutionId)
|
||||
.onSuccess(all -> all.stream()
|
||||
.map(this::applyFullLmsIntegration)
|
||||
.forEach(res -> {
|
||||
.forEach(res ->
|
||||
res.onError(error -> log.warn(
|
||||
"Failed to update LMS Full Integration: {}",
|
||||
error.getMessage()) );
|
||||
}))
|
||||
error.getMessage()) )
|
||||
))
|
||||
.onError(error -> log.warn(
|
||||
"Failed to apply LMS Full Integration change caused by Exam Template: {}",
|
||||
examTemplate,
|
||||
error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionConfigurationChange(final ConnectionConfigurationChangeEvent event) {
|
||||
lmsSetupDAO.idsOfActiveWithFullIntegration(event.institutionId)
|
||||
.flatMap(examDAO::allActiveForLMSSetup)
|
||||
.onError(error -> log.error("Failed to notifyConnectionConfigurationChange: {}", error.getMessage()))
|
||||
.getOr(Collections.emptyList())
|
||||
.stream()
|
||||
.filter(exam -> this.needsConnectionConfigurationChange(exam, event.configId))
|
||||
.forEach(this::applyConnectionConfiguration);
|
||||
}
|
||||
|
||||
private boolean needsConnectionConfigurationChange(final Exam exam, final Long ccId) {
|
||||
if (exam.status == Exam.ExamStatus.ARCHIVED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String configId = getConnectionConfigurationId(exam);
|
||||
return StringUtils.isNotBlank(configId) && configId.equals(String.valueOf(ccId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IntegrationData> applyFullLmsIntegration(final Long lmsSetupId) {
|
||||
return lmsSetupDAO
|
||||
|
@ -261,7 +294,9 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||
.map(findQuizData(courseId, quizId))
|
||||
.map(createExam(examTemplateId, quitPassword));
|
||||
.map(createExam(examTemplateId, quitPassword))
|
||||
.map(exam -> applyExamData(exam, false))
|
||||
.map(this::applyConnectionConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -277,25 +312,11 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.flatMap(this::findExam)
|
||||
.map(this::checkDeletion)
|
||||
.map(this::logExamDeleted)
|
||||
.flatMap(teacherAccountServiceImpl::deleteTeacherAccountsForExam)
|
||||
.flatMap(deleteExamAction::deleteExamFromLMSIntegration);
|
||||
.flatMap(teacherAccountServiceImpl::deactivateTeacherAccountsForExam)
|
||||
.map(exam -> applyExamData(exam, true))
|
||||
.flatMap(deleteExamAction::deleteExamInternal);
|
||||
}
|
||||
|
||||
private Exam checkDeletion(final Exam exam) {
|
||||
// TODO check if Exam can be deleted according to the Spec
|
||||
|
||||
// check if there are no active SEB client connections
|
||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
throw new APIMessage.APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam currently has active SEB Client connections."));
|
||||
}
|
||||
|
||||
return exam;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Result<Void> streamConnectionConfiguration(
|
||||
final String lmsUUID,
|
||||
|
@ -317,22 +338,12 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
|
||||
final Exam exam = examResult.get();
|
||||
|
||||
String connectionConfigId = exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_DEFAULT_CONNECTION_CONFIGURATION);
|
||||
if (StringUtils.isBlank(connectionConfigId)) {
|
||||
connectionConfigId = this.sebClientConfigDAO
|
||||
.all(exam.institutionId, true)
|
||||
.map(all -> all.stream().filter(config -> config.configPurpose == SEBClientConfig.ConfigPurpose.START_EXAM)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new APIMessage.APIMessageException(
|
||||
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"))))
|
||||
.map(SEBClientConfig::getModelId)
|
||||
.getOr(null);
|
||||
}
|
||||
final String connectionConfigId = getConnectionConfigurationId(exam);
|
||||
if (StringUtils.isBlank(connectionConfigId)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"));
|
||||
}
|
||||
|
||||
this.clientConfigService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||
this.connectionConfigurationService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||
return Result.EMPTY;
|
||||
|
||||
} catch (final Exception e) {
|
||||
|
@ -340,14 +351,27 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
}
|
||||
}
|
||||
|
||||
private String getConnectionConfigurationId(final Exam exam) {
|
||||
String connectionConfigId = exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_DEFAULT_CONNECTION_CONFIGURATION);
|
||||
if (StringUtils.isBlank(connectionConfigId)) {
|
||||
connectionConfigId = this.sebClientConfigDAO
|
||||
.all(exam.institutionId, true)
|
||||
.map(all -> all.stream().filter(config -> config.configPurpose == SEBClientConfig.ConfigPurpose.START_EXAM)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new APIMessage.APIMessageException(
|
||||
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"))))
|
||||
.map(SEBClientConfig::getModelId)
|
||||
.getOr(null);
|
||||
}
|
||||
return connectionConfigId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getOneTimeLoginToken(
|
||||
final String lmsUUID,
|
||||
final String courseId,
|
||||
final String quizId,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
final AdHocAccountData adHocAccountData) {
|
||||
|
||||
return lmsSetupDAO
|
||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||
|
@ -355,7 +379,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.map(findQuizData(courseId, quizId))
|
||||
.flatMap(this::findExam)
|
||||
.flatMap(exam -> this.teacherAccountServiceImpl
|
||||
.getOneTimeTokenForTeacherAccount(exam, userId, username, timezone, true));
|
||||
.getOneTimeTokenForTeacherAccount(exam, adHocAccountData, true));
|
||||
}
|
||||
|
||||
|
||||
|
@ -408,9 +432,19 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
return existingExam.get();
|
||||
}
|
||||
|
||||
final ExamTemplate examTemplate = examTemplateDAO
|
||||
.byModelId(examTemplateId)
|
||||
.getOrThrow();
|
||||
|
||||
|
||||
// import exam
|
||||
final POSTMapper post = new POSTMapper(null, null);
|
||||
post.putIfAbsent(Domain.EXAM.ATTR_EXAM_TEMPLATE_ID, examTemplateId);
|
||||
post.putIfAbsent(Domain.EXAM.ATTR_OWNER, userService.getCurrentUser().uuid());
|
||||
post.putIfAbsent(
|
||||
Domain.EXAM.ATTR_SUPPORTER,
|
||||
StringUtils.join(examTemplate.supporter, Constants.LIST_SEPARATOR));
|
||||
post.putIfAbsent(Domain.EXAM.ATTR_TYPE, examTemplate.examType.name());
|
||||
if (StringUtils.isNotBlank(quitPassword)) {
|
||||
post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
|
||||
}
|
||||
|
@ -418,87 +452,24 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
final Exam exam = new Exam(null, quizData, post);
|
||||
return examDAO
|
||||
.createNew(exam)
|
||||
.flatMap(examAdminService::applyExamImportInitialization)
|
||||
.flatMap(examImportService::applyExamImportInitialization)
|
||||
.map(this::logExamCreated)
|
||||
.getOrThrow();
|
||||
};
|
||||
}
|
||||
|
||||
// private UserInfo createNewAdHocAccount(
|
||||
// final Exam exam,
|
||||
// final String userId,
|
||||
// final String username,
|
||||
// final String timezone) {
|
||||
// try {
|
||||
//
|
||||
// final String uuid = UUID.randomUUID().toString();
|
||||
// DateTimeZone dtz = DateTimeZone.UTC;
|
||||
// if (StringUtils.isNotBlank(timezone)) {
|
||||
// try {
|
||||
// dtz = DateTimeZone.forID(timezone);
|
||||
// } catch (final Exception e) {
|
||||
// log.warn("Failed to set requested time zone for ad-hoc teacher account: {}", timezone);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// final UserMod adHocTeacherUser = new UserMod(
|
||||
// uuid,
|
||||
// exam.institutionId,
|
||||
// userId,
|
||||
// getTeacherAccountIdentifier(exam),
|
||||
// username,
|
||||
// uuid,
|
||||
// uuid,
|
||||
// null,
|
||||
// Locale.ENGLISH,
|
||||
// dtz,
|
||||
// true,
|
||||
// false,
|
||||
// Utils.immutableSetOf(UserRole.TEACHER.name()));
|
||||
//
|
||||
// return userDAO.createNew(adHocTeacherUser)
|
||||
// .flatMap(account -> userDAO.setActive(account, true))
|
||||
// .getOrThrow();
|
||||
//
|
||||
// } catch (final Exception e) {
|
||||
// log.error("Failed to create ad-hoc teacher account for importing exam: {}", exam, e);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// private Exam deleteAdHocAccounts(final Exam exam) {
|
||||
// try {
|
||||
//
|
||||
// final String externalId = exam.externalId;
|
||||
// final FilterMap filter = new FilterMap();
|
||||
// filter.putIfAbsent(Domain.USER.ATTR_SURNAME, getTeacherAccountIdentifier(exam));
|
||||
// final Collection<UserInfo> accounts = userDAO.allMatching(filter).getOrThrow();
|
||||
//
|
||||
// if (accounts.isEmpty()) {
|
||||
// return exam;
|
||||
// }
|
||||
//
|
||||
// if (accounts.size() > 1) {
|
||||
// log.error("Too many accounts found!?... ad-hoc teacher account mapping: {}", externalId);
|
||||
// return exam;
|
||||
// }
|
||||
//
|
||||
// userDAO.delete(Utils.immutableSetOf(new EntityKey(
|
||||
// accounts.iterator().next().uuid,
|
||||
// EntityType.USER)))
|
||||
// .getOrThrow();
|
||||
//
|
||||
// } catch (final Exception e) {
|
||||
// log.error("Failed to delete ad-hoc account for exam: {}", exam, e);
|
||||
// }
|
||||
//
|
||||
// return exam;
|
||||
// }
|
||||
|
||||
private Exam checkDeletion(final Exam exam) {
|
||||
// TODO check if Exam can be deleted according to the Spec
|
||||
|
||||
// check if there are no active SEB client connections
|
||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
throw new APIMessage.APIMessageException(
|
||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||
.of("Exam currently has active SEB Client connections."));
|
||||
}
|
||||
|
||||
return exam;
|
||||
}
|
||||
|
||||
|
||||
private Collection<ExamTemplateSelection> getIntegrationTemplates(final Long institutionId) {
|
||||
|
@ -514,6 +485,54 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
private Exam applyExamData(final Exam exam, final boolean deletion) {
|
||||
try {
|
||||
|
||||
final LmsAPITemplate lmsAPITemplate = lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
final String lmsUUID = lmsAPITemplate.lmsSetup().connectionId;
|
||||
final String courseId = lmsAPITemplate.getCourseIdFromExam(exam);
|
||||
final String quizId = lmsAPITemplate.getQuizIdFromExam(exam);
|
||||
|
||||
final String templateId = deletion ? null : String.valueOf(exam.examTemplateId);
|
||||
final String quitPassword = deletion ? null : examConfigurationValueService.getQuitPassword(exam.id);
|
||||
final Boolean quitLink = deletion ? null : StringUtils.isNotBlank(examConfigurationValueService.getQuitLink(exam.id));
|
||||
|
||||
final ExamData examData = new ExamData(
|
||||
lmsUUID,
|
||||
courseId,
|
||||
quizId,
|
||||
!deletion,
|
||||
templateId,
|
||||
quitLink,
|
||||
quitPassword);
|
||||
|
||||
lmsAPITemplate.applyExamData(examData).getOrThrow();
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to apply exam data to LMS for exam: {} error: {}", exam, e.getMessage());
|
||||
}
|
||||
return exam;
|
||||
}
|
||||
|
||||
private Exam applyConnectionConfiguration(final Exam exam) {
|
||||
return lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.flatMap(template -> {
|
||||
final String connectionConfigId = getConnectionConfigurationId(exam);
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.connectionConfigurationService
|
||||
.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
|
||||
|
||||
// TODO check if this works as expected
|
||||
return template.applyConnectionConfiguration(exam, out.toByteArray());
|
||||
})
|
||||
.onError(error -> log.error("Failed to apply ConnectionConfiguration for exam: {} error: {}", exam, error.getMessage()))
|
||||
.getOr(exam);
|
||||
}
|
||||
|
||||
private String getAPIRootURL() {
|
||||
return webserviceInfo.getExternalServerURL() + lmsAPIEndpoint;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Set;
|
|||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.*;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.ExamData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
@ -55,8 +56,9 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
private final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
||||
|
||||
private final CircuitBreaker<SEBRestriction> restrictionRequest;
|
||||
private final CircuitBreaker<Exam> releaseRestrictionRequest;
|
||||
private final CircuitBreaker<Exam> examRequest;
|
||||
private final CircuitBreaker<IntegrationData> lmsAccessRequest;
|
||||
private final CircuitBreaker<ExamData> applyExamDataRequest;
|
||||
private final CircuitBreaker<String> deleteLmsAccessRequest;
|
||||
|
||||
public LmsAPITemplateAdapter(
|
||||
|
@ -88,15 +90,29 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
|
||||
lmsAccessRequest = asyncService.createCircuitBreaker(
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.attempts",
|
||||
"sebserver.webservice.circuitbreaker.lmsAccessRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.blockingTime",
|
||||
"sebserver.webservice.circuitbreaker.lmsAccessRequest.blockingTime",
|
||||
Long.class,
|
||||
Constants.SECOND_IN_MILLIS * 20),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.lmsTestRequest.timeToRecover",
|
||||
"sebserver.webservice.circuitbreaker.lmsAccessRequest.timeToRecover",
|
||||
Long.class,
|
||||
0L));
|
||||
|
||||
applyExamDataRequest = asyncService.createCircuitBreaker(
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.applyExamDataRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.applyExamDataRequest.blockingTime",
|
||||
Long.class,
|
||||
Constants.SECOND_IN_MILLIS * 20),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.applyExamDataRequest.timeToRecover",
|
||||
Long.class,
|
||||
0L));
|
||||
|
||||
|
@ -198,17 +214,17 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
Long.class,
|
||||
0L));
|
||||
|
||||
this.releaseRestrictionRequest = asyncService.createCircuitBreaker(
|
||||
this.examRequest = asyncService.createCircuitBreaker(
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.sebrestriction.attempts",
|
||||
"sebserver.webservice.circuitbreaker.examRequest.attempts",
|
||||
Integer.class,
|
||||
2),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.sebrestriction.blockingTime",
|
||||
"sebserver.webservice.circuitbreaker.examRequest.blockingTime",
|
||||
Long.class,
|
||||
Constants.SECOND_IN_MILLIS * 10),
|
||||
environment.getProperty(
|
||||
"sebserver.webservice.circuitbreaker.sebrestriction.timeToRecover",
|
||||
"sebserver.webservice.circuitbreaker.examRequest.timeToRecover",
|
||||
Long.class,
|
||||
0L));
|
||||
}
|
||||
|
@ -456,7 +472,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
log.debug("Release course restriction: {} for LMSSetup: {}", exam.externalId, lmsSetup());
|
||||
}
|
||||
|
||||
final Result<Exam> protectedRun = this.releaseRestrictionRequest.protectedRun(() -> this.sebRestrictionAPI
|
||||
final Result<Exam> protectedRun = this.examRequest.protectedRun(() -> this.sebRestrictionAPI
|
||||
.releaseSEBClientRestriction(exam)
|
||||
.onError(error -> log.error(
|
||||
"Failed to release SEB restrictions: {}",
|
||||
|
@ -498,6 +514,39 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
|
|||
.getOrThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ExamData> applyExamData(final ExamData examData) {
|
||||
if (this.lmsIntegrationAPI == null) {
|
||||
return Result.ofError(
|
||||
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name()));
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply exam data: {} for LMSSetup: {}", examData, lmsSetup());
|
||||
}
|
||||
|
||||
return this.applyExamDataRequest.protectedRun(() -> this.lmsIntegrationAPI
|
||||
.applyExamData(examData)
|
||||
.getOrThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyConnectionConfiguration(final Exam exam, final byte[] configData) {
|
||||
if (this.lmsIntegrationAPI == null) {
|
||||
return Result.ofError(
|
||||
new UnsupportedOperationException("LMS Integration API Not Supported For: " + getType().name()));
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply Connection Configuration for exam: {} for LMSSetup: {}", exam, lmsSetup());
|
||||
}
|
||||
|
||||
return this.examRequest.protectedRun(() -> this.lmsIntegrationAPI
|
||||
.applyConnectionConfiguration(exam, configData)
|
||||
.getOrThrow());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Result<String> deleteConnectionDetails() {
|
||||
if (this.lmsIntegrationAPI == null) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
@ -431,6 +432,16 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
|
|||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<FullLmsIntegrationService.ExamData> applyExamData(final FullLmsIntegrationService.ExamData examData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyConnectionConfiguration(final Exam exam, final byte[] configData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> deleteConnectionDetails() {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData;
|
||||
|
||||
public class MockupFullIntegration implements FullLmsIntegrationAPI {
|
||||
|
@ -26,6 +28,16 @@ public class MockupFullIntegration implements FullLmsIntegrationAPI {
|
|||
return Result.ofRuntimeError("TODO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<FullLmsIntegrationService.ExamData> applyExamData(final FullLmsIntegrationService.ExamData examData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyConnectionConfiguration(final Exam exam, final byte[] configData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> deleteConnectionDetails() {
|
||||
return Result.ofRuntimeError("TODO");
|
||||
|
|
|
@ -40,13 +40,18 @@ public interface MoodleAPIRestTemplate {
|
|||
String postToMoodleAPIFunction(String functionName, String body);
|
||||
|
||||
String callMoodleAPIFunction(
|
||||
final String functionName,
|
||||
final MultiValueMap<String, String> queryAttributes);
|
||||
String functionName,
|
||||
MultiValueMap<String, String> queryAttributes);
|
||||
|
||||
String callMoodleAPIFunction(
|
||||
final String functionName,
|
||||
final MultiValueMap<String, String> queryParams,
|
||||
final MultiValueMap<String, String> queryAttributes);
|
||||
String functionName,
|
||||
MultiValueMap<String, String> queryParams,
|
||||
MultiValueMap<String, String> queryAttributes);
|
||||
|
||||
String uploadMultiPart(
|
||||
String uploadEndpoint,
|
||||
MultiValueMap<String, Object> multiPartAttributes);
|
||||
|
||||
|
||||
/** This maps a Moodle warning JSON object */
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -149,14 +149,13 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
this. activeRestTemplate = this.knownTokenAccessPaths
|
||||
.stream()
|
||||
.map(path -> this.createRestTemplate(service, path))
|
||||
.map(result -> {
|
||||
.peek(result -> {
|
||||
if (result.hasError()) {
|
||||
log.warn("Failed to get access token for LMS: {}({}), error {}",
|
||||
lmsSetup.name,
|
||||
lmsSetup.id,
|
||||
result.getError().getMessage());
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.filter(Result::hasValue)
|
||||
.findFirst()
|
||||
|
@ -284,7 +283,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
|
||||
final List<String> missingAPIFunctions = Arrays.stream(functions)
|
||||
.filter(f -> !webserviceInfo.functions.containsKey(f))
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
|
||||
if (!missingAPIFunctions.isEmpty()) {
|
||||
throw new RuntimeException("Missing Moodle Webservice API functions: " + missingAPIFunctions);
|
||||
|
@ -328,7 +327,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
headers.set(
|
||||
HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_JSON_VALUE);
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
|
||||
final HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
|
||||
|
||||
return doRequest(functionName, queryParam, true, httpEntity);
|
||||
}
|
||||
|
@ -369,6 +368,22 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
return doRequest(functionName, queryParam, usePOST, functionReqEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadMultiPart(
|
||||
final String uploadEndpoint,
|
||||
final MultiValueMap<String, Object> multiPartAttributes) {
|
||||
|
||||
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||
final String uri = lmsSetup.lmsApiUrl + uploadEndpoint;
|
||||
getAccessToken();
|
||||
multiPartAttributes.add("token", this.accessToken);
|
||||
|
||||
return super.postForObject(
|
||||
uploadEndpoint,
|
||||
multiPartAttributes,
|
||||
String.class);
|
||||
}
|
||||
|
||||
private String doRequest(
|
||||
final String functionName,
|
||||
final UriComponentsBuilder queryParam,
|
||||
|
@ -474,7 +489,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
final String privatetoken;
|
||||
|
||||
@JsonCreator
|
||||
protected MoodleToken(
|
||||
private MoodleToken(
|
||||
@JsonProperty(value = "token") final String token,
|
||||
@JsonProperty(value = "privatetoken", required = false) final String privatetoken) {
|
||||
|
||||
|
@ -491,7 +506,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
Map<String, FunctionInfo> functions;
|
||||
|
||||
@JsonCreator
|
||||
protected WebserviceInfo(
|
||||
private WebserviceInfo(
|
||||
@JsonProperty(value = "username") final String username,
|
||||
@JsonProperty(value = "userid") final String userid,
|
||||
@JsonProperty(value = "functions") final Collection<FunctionInfo> functions) {
|
||||
|
@ -513,7 +528,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
String version;
|
||||
|
||||
@JsonCreator
|
||||
protected FunctionInfo(
|
||||
private FunctionInfo(
|
||||
@JsonProperty(value = "name") final String name,
|
||||
@JsonProperty(value = "version") final String version) {
|
||||
|
||||
|
|
|
@ -61,9 +61,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils.MoodleUserDetails;
|
||||
|
||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||
*
|
||||
* <p>
|
||||
* See also: https://docs.moodle.org/dev/Web_service_API_functions
|
||||
*
|
||||
* <p>
|
||||
* NOTE: Because of the missing integration on Moodle side so far the MoodleCourseAccess
|
||||
* needs to deal with Moodle's standard API functions that don't allow to filter and page course/quiz data
|
||||
* in an easy and proper way. Therefore we have to fetch all course and quiz data from Moodle before
|
||||
|
@ -141,6 +141,16 @@ public class MoodleCourseAccess implements CourseAccessAPI {
|
|||
return this.restTemplateFactory.getApiTemplateDataSupplier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCourseIdFromExam(final Exam exam) {
|
||||
return MoodleUtils.getCourseId(exam.externalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuizIdFromExam(final Exam exam) {
|
||||
return MoodleUtils.getQuizId(exam.externalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetupTestResult testCourseAccessAPI() {
|
||||
final LmsSetupTestResult attributesCheck = this.restTemplateFactory.test();
|
||||
|
|
|
@ -144,6 +144,16 @@ public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess impleme
|
|||
return this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup().id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCourseIdFromExam(final Exam exam) {
|
||||
return MoodleUtils.getCourseId(exam.externalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuizIdFromExam(final Exam exam) {
|
||||
return MoodleUtils.getQuizId(exam.externalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LmsSetupTestResult testCourseAccessAPI() {
|
||||
final LmsSetupTestResult attributesCheck = this.restTemplateFactory.test();
|
||||
|
|
|
@ -10,17 +10,21 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.ExamData;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationAPI;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleResponseException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
|
@ -30,6 +34,10 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
|||
|
||||
private static final String FUNCTION_NAME_SEBSERVER_CONNECTION = "quizaccess_sebserver_connection";
|
||||
private static final String FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE = "quizaccess_sebserver_connection_delete";
|
||||
private static final String FUNCTION_NAME_SET_EXAM_DATA = "quizaccess_sebserver_set_exam_data";
|
||||
|
||||
private static final String UPLOAD_ENDPOINT = "/mod/quiz/accessrule/sebserver/uploadconfig.php";
|
||||
|
||||
private final JSONMapper jsonMapper;
|
||||
private final MoodleRestTemplateFactory restTemplateFactory;
|
||||
|
||||
|
@ -60,7 +68,8 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
|||
final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
|
||||
restTemplate.testAPIConnection(
|
||||
FUNCTION_NAME_SEBSERVER_CONNECTION,
|
||||
FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE);
|
||||
FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE,
|
||||
FUNCTION_NAME_SET_EXAM_DATA);
|
||||
|
||||
} catch (final RuntimeException e) {
|
||||
return LmsSetupTestResult.ofQuizAccessAPIError(LmsSetup.LmsType.MOODLE_PLUGIN, e.getMessage());
|
||||
|
@ -112,6 +121,70 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ExamData> applyExamData(final ExamData examData) {
|
||||
return Result.tryCatch(() -> {
|
||||
// validation
|
||||
if (StringUtils.isBlank( examData.id)) {
|
||||
throw new APIMessage.FieldValidationException("ExamData:id", "id is mandatory");
|
||||
}
|
||||
if (StringUtils.isBlank( examData.course_id)) {
|
||||
throw new APIMessage.FieldValidationException("ExamData:course_id", "course_id is mandatory");
|
||||
}
|
||||
if (StringUtils.isBlank( examData.quiz_id)) {
|
||||
throw new APIMessage.FieldValidationException("ExamData:quiz_id", "quiz_id is mandatory");
|
||||
}
|
||||
|
||||
final LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup();
|
||||
final String jsonPayload = jsonMapper.writeValueAsString(examData);
|
||||
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
||||
final String response = rest.postToMoodleAPIFunction(FUNCTION_NAME_SET_EXAM_DATA, jsonPayload);
|
||||
|
||||
if (response != null && (response.startsWith("{\"exception\":") || response.startsWith("0"))) {
|
||||
log.warn("Failed to apply Exam data to moodle: {}", examData);
|
||||
}
|
||||
|
||||
return examData;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyConnectionConfiguration(final Exam exam, final byte[] configData) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final String quizId = MoodleUtils.getQuizId(exam.externalId);
|
||||
final String fileName = getConnectionConfigFileName(exam);
|
||||
|
||||
|
||||
final MultiValueMap<String, Object> multiPartAttributes = new LinkedMultiValueMap<>();
|
||||
multiPartAttributes.add("quizid", quizId);
|
||||
multiPartAttributes.add("name", fileName);
|
||||
multiPartAttributes.add("filename", fileName);
|
||||
final ByteArrayResource contentsAsResource = new ByteArrayResource(configData) {
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return fileName; // Filename has to be returned in order to be able to post.
|
||||
}
|
||||
};
|
||||
|
||||
multiPartAttributes.add("file", contentsAsResource);
|
||||
|
||||
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
||||
final String response = rest.uploadMultiPart(UPLOAD_ENDPOINT, multiPartAttributes);
|
||||
|
||||
if (response != null && (response.startsWith("{\"exception\":") || response.startsWith("0"))) {
|
||||
log.warn("Failed to apply Connection Configuration to LMS for Exam: {}", exam.externalId);
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
private String getConnectionConfigFileName(final Exam exam) {
|
||||
return "SEBServerConnectionConfiguration-" + exam.id + ".seb";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Result<String> deleteConnectionDetails() {
|
||||
return Result.tryCatch(() -> {
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService.IntegrationData;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
|
@ -418,6 +419,16 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
|
|||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<FullLmsIntegrationService.ExamData> applyExamData(final FullLmsIntegrationService.ExamData examData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyConnectionConfiguration(final Exam exam, final byte[] configData) {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> deleteConnectionDetails() {
|
||||
return Result.ofRuntimeError("Not Supported");
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.webservice.servicelayer.sebconfig;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class ConnectionConfigurationChangeEvent extends ApplicationEvent {
|
||||
|
||||
public final Long institutionId;
|
||||
public final Long configId;
|
||||
public ConnectionConfigurationChangeEvent(final Long institutionId, final Long configId) {
|
||||
super(configId);
|
||||
this.institutionId = institutionId;
|
||||
this.configId = configId;
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
|||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
public interface ClientConfigService {
|
||||
public interface ConnectionConfigurationService {
|
||||
|
||||
Logger log = LoggerFactory.getLogger(ClientConfigService.class);
|
||||
Logger log = LoggerFactory.getLogger(ConnectionConfigurationService.class);
|
||||
|
||||
/** The cache name of ClientDetails */
|
||||
String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
|
|
@ -18,7 +18,7 @@ import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService.Strategy;
|
||||
|
||||
/** Interface for a SEB Configuration encryption and decryption strategy.
|
||||
*
|
||||
* <p>
|
||||
* To support a new SEB Configuration encryption and decryption strategy use this interface
|
||||
* to implement a concrete strategy for encryption and decryption of SEB configurations */
|
||||
public interface SEBConfigCryptor {
|
||||
|
|
|
@ -57,7 +57,7 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.CertificateDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionContext;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SEBConfigEncryptionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
|
||||
|
@ -67,9 +67,9 @@ import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfigu
|
|||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ClientConfigServiceImpl implements ClientConfigService {
|
||||
public class ConnectionConfigurationServiceImpl implements ConnectionConfigurationService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(ConnectionConfigurationServiceImpl.class);
|
||||
|
||||
//@formatter:off
|
||||
private static final String SEB_CLIENT_CONFIG_EXAM_PROP_NAME = "exam";
|
||||
|
@ -171,7 +171,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
private final long defaultPingInterval;
|
||||
private final int examAPITokenValiditySeconds;
|
||||
|
||||
protected ClientConfigServiceImpl(
|
||||
protected ConnectionConfigurationServiceImpl(
|
||||
final SEBClientConfigDAO sebClientConfigDAO,
|
||||
final ClientCredentialService clientCredentialService,
|
||||
final SEBConfigEncryptionService sebConfigEncryptionService,
|
|
@ -33,6 +33,8 @@ public interface ScreenProctoringService extends SessionUpdateTask {
|
|||
updateClientConnections();
|
||||
}
|
||||
|
||||
boolean isScreenProctoringEnabled(Long examId);
|
||||
|
||||
/** This is testing the given ScreenProctoringSettings on integrity and if we can
|
||||
* connect to the given SEB screen proctoring service.
|
||||
*
|
||||
|
|
|
@ -31,7 +31,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
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.SEBClientConnectionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
||||
|
@ -87,7 +87,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
|||
private final SecurityKeyService securityKeyService;
|
||||
private final SEBClientEventBatchService sebClientEventBatchService;
|
||||
private final SEBClientInstructionService sebClientInstructionService;
|
||||
private final ClientConfigService clientConfigService;
|
||||
private final ConnectionConfigurationService connectionConfigurationService;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final boolean isDistributedSetup;
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
|||
final WebserviceInfo webserviceInfo,
|
||||
final SEBClientEventBatchService sebClientEventBatchService,
|
||||
final SEBClientInstructionService sebClientInstructionService,
|
||||
final ClientConfigService clientConfigService,
|
||||
final ConnectionConfigurationService connectionConfigurationService,
|
||||
final JSONMapper jsonMapper) {
|
||||
|
||||
this.examSessionService = examSessionService;
|
||||
|
@ -115,7 +115,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
|||
this.isDistributedSetup = webserviceInfo.isDistributed();
|
||||
this.sebClientEventBatchService = sebClientEventBatchService;
|
||||
this.sebClientInstructionService = sebClientInstructionService;
|
||||
this.clientConfigService = clientConfigService;
|
||||
this.connectionConfigurationService = connectionConfigurationService;
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
|
@ -644,7 +644,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
|||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
|
||||
this.clientConfigService.exportSEBClientConfiguration(
|
||||
this.connectionConfigurationService.exportSEBClientConfiguration(
|
||||
pout,
|
||||
modelId,
|
||||
null);
|
||||
|
|
|
@ -71,6 +71,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
private final SEBClientInstructionService sebInstructionService;
|
||||
private final ExamSessionCacheService examSessionCacheService;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
private final WebserviceInfo.ScreenProctoringServiceBundle screenProctoringServiceBundle;
|
||||
|
||||
public ScreenProctoringServiceImpl(
|
||||
final Cryptor cryptor,
|
||||
|
@ -94,6 +95,8 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
this.examSessionCacheService = examSessionCacheService;
|
||||
this.proctoringSettingsDAO = proctoringSettingsDAO;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
this.screenProctoringServiceBundle = webserviceInfo.getScreenProctoringServiceBundle();
|
||||
|
||||
this.screenProctoringAPIBinding = new ScreenProctoringAPIBinding(
|
||||
userDAO,
|
||||
cryptor,
|
||||
|
@ -104,6 +107,11 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
webserviceInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScreenProctoringEnabled(final Long examId) {
|
||||
return this.proctoringSettingsDAO.isScreenProctoringEnabled(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ScreenProctoringSettings> testSettings(final ScreenProctoringSettings screenProctoringSettings) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
@ -488,7 +496,9 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
}
|
||||
|
||||
final SPSData spsData = this.screenProctoringAPIBinding.getSPSData(exam.id);
|
||||
final String url = exam.additionalAttributes.get(ScreenProctoringSettings.ATTR_SPS_SERVICE_URL);
|
||||
final String url = screenProctoringServiceBundle.bundled
|
||||
? screenProctoringServiceBundle.serviceURL
|
||||
: exam.additionalAttributes.get(ScreenProctoringSettings.ATTR_SPS_SERVICE_URL);
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
|
||||
attributes.put(SERVICE_TYPE, SERVICE_TYPE_NAME);
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.stream.Collectors;
|
|||
import javax.validation.Valid;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamUtils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -81,11 +82,10 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamAdministrationController.class);
|
||||
|
||||
// TODO reduce dependencies here.
|
||||
// Move SecurityKeyService, SEBRestrictionService RemoteProctoringRoomService into ExamAdminService
|
||||
private final ExamDAO examDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamImportService examImportService;
|
||||
private final RemoteProctoringRoomService remoteProctoringRoomService;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
@ -103,6 +103,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
final LmsAPIService lmsAPIService,
|
||||
final UserDAO userDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamImportService examImportService,
|
||||
final RemoteProctoringRoomService remoteProctoringRoomService,
|
||||
final ExamSessionService examSessionService,
|
||||
final SEBRestrictionService sebRestrictionService,
|
||||
|
@ -119,6 +120,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
this.examDAO = examDAO;
|
||||
this.userDAO = userDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examImportService = examImportService;
|
||||
this.remoteProctoringRoomService = remoteProctoringRoomService;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.examSessionService = examSessionService;
|
||||
|
@ -609,7 +611,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
|
||||
@Override
|
||||
protected Result<Exam> notifyCreated(final Exam entity) {
|
||||
return examAdminService.applyExamImportInitialization(entity);
|
||||
return examImportService.applyExamImportInitialization(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -39,9 +40,14 @@ public class LmsIntegrationController {
|
|||
private static final Logger log = LoggerFactory.getLogger(LmsIntegrationController.class);
|
||||
|
||||
private final FullLmsIntegrationService fullLmsIntegrationService;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
|
||||
public LmsIntegrationController(
|
||||
final FullLmsIntegrationService fullLmsIntegrationService,
|
||||
final WebserviceInfo webserviceInfo) {
|
||||
|
||||
public LmsIntegrationController(final FullLmsIntegrationService fullLmsIntegrationService) {
|
||||
this.fullLmsIntegrationService = fullLmsIntegrationService;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -49,10 +55,10 @@ public class LmsIntegrationController {
|
|||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
public void createExam(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID, required = true) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID, required = true) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID, required = true) final String templateId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID) final String templateId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_PASSWORD, required = false) final String quitPassword,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_LINK, required = false) final String quitLink,
|
||||
final HttpServletResponse response) {
|
||||
|
@ -77,9 +83,9 @@ public class LmsIntegrationController {
|
|||
method = RequestMethod.DELETE,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
public void deleteExam(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID, required = true) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID, required = true) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID) final String quizId,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
final EntityKey examID = fullLmsIntegrationService.deleteExam(lmsUUId, courseId, quizId)
|
||||
|
@ -97,11 +103,13 @@ public class LmsIntegrationController {
|
|||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public void getConnectionConfiguration(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID, required = true) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID, required = true) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID) final String quizId,
|
||||
final HttpServletResponse response) throws IOException {
|
||||
|
||||
// TODO change this according to the outcome of discussion about Moodle Connection Configuration handling
|
||||
|
||||
final ServletOutputStream outputStream = response.getOutputStream();
|
||||
final PipedOutputStream pout;
|
||||
final PipedInputStream pin;
|
||||
|
@ -137,17 +145,34 @@ public class LmsIntegrationController {
|
|||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public String getOneTimeLoginToken(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID, required = true) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID, required = true) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_ID, required = true) final String userId,
|
||||
public FullLmsIntegrationService.TokenLoginResponse getOneTimeLoginToken(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID) final String quizId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_ID) final String userId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_NAME, required = false) final String username,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_EMAIL, required = false) final String userMail,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_FIRST_NAME, required = false) final String firstName,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_LAST_NAME, required = false) final String lastName,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_TIME_ZONE, required = false) final String timezone,
|
||||
final HttpServletResponse response) throws IOException {
|
||||
final HttpServletResponse response) {
|
||||
|
||||
return this.fullLmsIntegrationService
|
||||
.getOneTimeLoginToken(lmsUUId, courseId, quizId, userId, username, timezone)
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData = new FullLmsIntegrationService.AdHocAccountData(
|
||||
userId,
|
||||
username,
|
||||
userMail,
|
||||
firstName,
|
||||
lastName,
|
||||
timezone
|
||||
);
|
||||
|
||||
final String token = this.fullLmsIntegrationService
|
||||
.getOneTimeLoginToken(lmsUUId, courseId, quizId, adHocAccountData)
|
||||
.onError(error -> log.error("Failed to create ad-hoc account with one time login token: ", error))
|
||||
.getOrThrow();
|
||||
|
||||
return new FullLmsIntegrationService.TokenLoginResponse(
|
||||
lmsUUId,
|
||||
webserviceInfo.getExternalServerURL() + API.LMS_FULL_INTEGRATION_LOGIN_TOKEN_ENDPOINT + "?jwt=" + token);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -61,7 +61,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
|
||||
public class SEBClientConfigController extends ActivatableEntityController<SEBClientConfig, SEBClientConfig> {
|
||||
|
||||
private final ClientConfigService sebClientConfigService;
|
||||
private final ConnectionConfigurationService sebConnectionConfigurationService;
|
||||
|
||||
public SEBClientConfigController(
|
||||
final SEBClientConfigDAO sebClientConfigDAO,
|
||||
|
@ -70,7 +70,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
final BulkActionService bulkActionService,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ClientConfigService sebClientConfigService) {
|
||||
final ConnectionConfigurationService sebConnectionConfigurationService) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -79,7 +79,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
paginationService,
|
||||
beanValidationService);
|
||||
|
||||
this.sebClientConfigService = sebClientConfigService;
|
||||
this.sebConnectionConfigurationService = sebConnectionConfigurationService;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -127,7 +127,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
|
||||
this.sebClientConfigService.exportSEBClientConfiguration(
|
||||
this.sebConnectionConfigurationService.exportSEBClientConfiguration(
|
||||
pout,
|
||||
modelId,
|
||||
examId);
|
||||
|
@ -182,7 +182,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) {
|
||||
if (entity.isActive()) {
|
||||
// try to get access token for SEB client
|
||||
this.sebClientConfigService.initialCheckAccess(entity);
|
||||
this.sebConnectionConfigurationService.initialCheckAccess(entity);
|
||||
}
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import org.springframework.security.oauth2.provider.ClientRegistrationException;
|
|||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||
|
||||
/** A ClientDetailsService to manage different API clients of SEB Server webservice API.
|
||||
* <p>
|
||||
|
@ -32,17 +32,17 @@ public class WebClientDetailsService implements ClientDetailsService {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebClientDetailsService.class);
|
||||
|
||||
private final ClientConfigService sebClientConfigService;
|
||||
private final ConnectionConfigurationService sebConnectionConfigurationService;
|
||||
private final AdminAPIClientDetails adminClientDetails;
|
||||
private final LmsAPIClientDetails lmsAPIClientDetails;
|
||||
|
||||
public WebClientDetailsService(
|
||||
final AdminAPIClientDetails adminClientDetails,
|
||||
final ClientConfigService sebClientConfigService,
|
||||
final ConnectionConfigurationService sebConnectionConfigurationService,
|
||||
final LmsAPIClientDetails lmsAPIClientDetails) {
|
||||
|
||||
this.adminClientDetails = adminClientDetails;
|
||||
this.sebClientConfigService = sebClientConfigService;
|
||||
this.sebConnectionConfigurationService = sebConnectionConfigurationService;
|
||||
this.lmsAPIClientDetails = lmsAPIClientDetails;
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ public class WebClientDetailsService implements ClientDetailsService {
|
|||
}
|
||||
|
||||
protected Result<ClientDetails> getForExamClientAPI(final String clientId) {
|
||||
return this.sebClientConfigService.getClientConfigDetails(clientId);
|
||||
return this.sebConnectionConfigurationService.getClientConfigDetails(clientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
|
|||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.AdminAPIClientDetails;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
|
||||
|
||||
|
@ -313,7 +313,7 @@ public abstract class ExamAPIIntegrationTester {
|
|||
@Autowired
|
||||
AdminAPIClientDetails adminClientDetails;
|
||||
@Autowired
|
||||
ClientConfigService sebClientConfigService;
|
||||
ConnectionConfigurationService sebConnectionConfigurationService;
|
||||
@Autowired
|
||||
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME)
|
||||
private PasswordEncoder clientPasswordEncoder;
|
||||
|
|
|
@ -195,6 +195,13 @@ public class MoodleMockupRestTemplateFactory implements MoodleRestTemplateFactor
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadMultiPart(
|
||||
final String uploadEndpoint,
|
||||
final MultiValueMap<String, Object> multiPartAttributes) {
|
||||
throw new UnsupportedOperationException("Not supported yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
|
Loading…
Reference in a new issue