SEBSERV-417 finished first part

This commit is contained in:
anhefti 2024-05-08 14:29:53 +02:00
parent 4b675bc717
commit 3501c5de05
14 changed files with 204 additions and 40 deletions

View file

@ -175,7 +175,7 @@ public final class API {
public static final String LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID = "exam_template_id"; 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_PASSWORD = "quit_password";
public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "quit_link"; public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "quit_link";
public static final String LMS_FULL_INTEGRATION_TIME_ZONE = "account_time_zone";
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount"; public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";

View file

@ -21,6 +21,7 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -95,6 +96,12 @@ public final class UserMod implements UserAccount {
@JsonProperty(PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD) @JsonProperty(PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD)
private final CharSequence confirmNewPassword; private final CharSequence confirmNewPassword;
@JsonProperty(USER.ATTR_LOCAL_ACCOUNT)
private final Boolean isLocalAccount;
@JsonProperty(USER.ATTR_DIRECT_LOGIN)
private final Boolean hasDirectLogin;
@JsonCreator @JsonCreator
public UserMod( public UserMod(
@JsonProperty(USER.ATTR_UUID) final String uuid, @JsonProperty(USER.ATTR_UUID) final String uuid,
@ -107,6 +114,8 @@ public final class UserMod implements UserAccount {
@JsonProperty(USER.ATTR_EMAIL) final String email, @JsonProperty(USER.ATTR_EMAIL) final String email,
@JsonProperty(USER.ATTR_LANGUAGE) final Locale language, @JsonProperty(USER.ATTR_LANGUAGE) final Locale language,
@JsonProperty(USER.ATTR_TIMEZONE) final DateTimeZone timeZone, @JsonProperty(USER.ATTR_TIMEZONE) final DateTimeZone timeZone,
@JsonProperty(USER.ATTR_LOCAL_ACCOUNT) final Boolean isLocalAccount,
@JsonProperty(USER.ATTR_DIRECT_LOGIN) final Boolean hasDirectLogin,
@JsonProperty(USER_ROLE.REFERENCE_NAME) final Set<String> roles) { @JsonProperty(USER_ROLE.REFERENCE_NAME) final Set<String> roles) {
this.uuid = uuid; this.uuid = uuid;
@ -119,6 +128,8 @@ public final class UserMod implements UserAccount {
this.email = email; this.email = email;
this.language = (language != null) ? language : Locale.ENGLISH; this.language = (language != null) ? language : Locale.ENGLISH;
this.timeZone = (timeZone != null) ? timeZone : DateTimeZone.UTC; this.timeZone = (timeZone != null) ? timeZone : DateTimeZone.UTC;
this.isLocalAccount = BooleanUtils.isNotFalse(isLocalAccount);
this.hasDirectLogin = BooleanUtils.isNotFalse(hasDirectLogin);
this.roles = (roles != null) this.roles = (roles != null)
? Collections.unmodifiableSet(roles) ? Collections.unmodifiableSet(roles)
: Collections.emptySet(); : Collections.emptySet();
@ -136,6 +147,8 @@ public final class UserMod implements UserAccount {
this.language = postAttrMapper.getLocale(USER.ATTR_LANGUAGE); this.language = postAttrMapper.getLocale(USER.ATTR_LANGUAGE);
this.timeZone = postAttrMapper.getDateTimeZone(USER.ATTR_TIMEZONE); this.timeZone = postAttrMapper.getDateTimeZone(USER.ATTR_TIMEZONE);
this.roles = postAttrMapper.getStringSet(USER_ROLE.REFERENCE_NAME); this.roles = postAttrMapper.getStringSet(USER_ROLE.REFERENCE_NAME);
this.isLocalAccount = BooleanUtils.isNotFalse(postAttrMapper.getBoolean(USER.ATTR_LOCAL_ACCOUNT));
this.hasDirectLogin = BooleanUtils.isNotFalse(postAttrMapper.getBoolean(USER.ATTR_DIRECT_LOGIN));
} }
@Override @Override
@ -237,6 +250,15 @@ public final class UserMod implements UserAccount {
return false; return false;
} }
public Boolean isLocalAccount() {
return isLocalAccount;
}
public Boolean hasDirectLogin() {
return hasDirectLogin;
}
@JsonIgnore @JsonIgnore
@Override @Override
public EntityKey getEntityKey() { public EntityKey getEntityKey() {
@ -279,7 +301,7 @@ public final class UserMod implements UserAccount {
return new UserMod( return new UserMod(
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
institutionId, institutionId,
null, null, null, null, null, null, null, null, null); null, null, null, null, null, null, null, null, true, true, null);
} }
} }

View file

@ -145,6 +145,8 @@ class AdminUserInitializer {
null, null,
null, null,
null, null,
true,
true,
new HashSet<>(this.webserviceInfo.isLightSetup() ? new HashSet<>(this.webserviceInfo.isLightSetup() ?
UserRole.getLightSetupRoles() : UserRole.getLightSetupRoles() :
List.of(UserRole.SEB_SERVER_ADMIN.name()) List.of(UserRole.SEB_SERVER_ADMIN.name())

View file

@ -50,6 +50,7 @@ public class WebserviceInfo {
"sebserver.webservice.api.exam.endpoint.discovery"; "sebserver.webservice.api.exam.endpoint.discovery";
private static final String WEB_SERVICE_EXTERNAL_ADDRESS_ALIAS = "sebserver.webservice.lms.address.alias"; private static final String WEB_SERVICE_EXTERNAL_ADDRESS_ALIAS = "sebserver.webservice.lms.address.alias";
private static final String WEB_SERVICE_CONTEXT_PATH = "server.servlet.context-path"; private static final String WEB_SERVICE_CONTEXT_PATH = "server.servlet.context-path";
public static final String SEBSERVER_WEBSERVICE_AUTOLOGIN_ENDPOINT = "sebserver.webservice.autologin.endpoint";
private final String sebServerVersion; private final String sebServerVersion;
private final String testProperty; private final String testProperty;
@ -61,6 +62,8 @@ public class WebserviceInfo {
private final String discoveryEndpoint; private final String discoveryEndpoint;
private final String contextPath; private final String contextPath;
private final String autoLoginEndpoint;
private final boolean isLightSetup; private final boolean isLightSetup;
private final String serverURLPrefix; private final String serverURLPrefix;
private final boolean isDistributed; private final boolean isDistributed;
@ -104,6 +107,9 @@ public class WebserviceInfo {
this.webserviceUUID = UUID.randomUUID().toString() this.webserviceUUID = UUID.randomUUID().toString()
+ Constants.UNDERLINE + Constants.UNDERLINE
+ this.sebServerVersion; + this.sebServerVersion;
this.autoLoginEndpoint = environment.getProperty(
SEBSERVER_WEBSERVICE_AUTOLOGIN_ENDPOINT,
"/auto_login");
this.distributedUpdateInterval = environment.getProperty( this.distributedUpdateInterval = environment.getProperty(
"sebserver.webservice.distributed.updateInterval", "sebserver.webservice.distributed.updateInterval",
@ -237,6 +243,10 @@ public class WebserviceInfo {
return this.discoveryEndpoint; return this.discoveryEndpoint;
} }
public String getAutoLoginEndpoint() {
return autoLoginEndpoint;
}
public String getDiscoveryEndpointAddress() { public String getDiscoveryEndpointAddress() {
return this.serverURLPrefix + this.discoveryEndpoint; return this.serverURLPrefix + this.discoveryEndpoint;
} }

View file

@ -113,16 +113,6 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
* happened */ * happened */
Result<Collection<EntityKey>> delete(Set<EntityKey> all); Result<Collection<EntityKey>> delete(Set<EntityKey> all);
@Transactional
default Result<EntityKey> deleteOne(final Long examId) {
if (examId == null) {
return Result.ofRuntimeError("exam Id has null reference");
}
return delete( new HashSet<>(Arrays.asList(new EntityKey(examId, EntityType.EXAM))))
.map(set -> set.iterator().next())
.onError(TransactionHandler::rollback);
}
/** Get a (unordered) collection of all Entities that matches the given filter criteria. /** Get a (unordered) collection of all Entities that matches the given filter criteria.
* The possible filter criteria for a specific Entity type is defined by the entity type. * The possible filter criteria for a specific Entity type is defined by the entity type.
* <p> * <p>

View file

@ -252,8 +252,8 @@ public class UserDAOImpl implements UserDAO {
userMod.language.toLanguageTag(), userMod.language.toLanguageTag(),
userMod.timeZone.getID(), userMod.timeZone.getID(),
BooleanUtils.toInteger(false), BooleanUtils.toInteger(false),
BooleanUtils.toInteger(true), BooleanUtils.toInteger(userMod.hasDirectLogin()),
BooleanUtils.toInteger(true)); BooleanUtils.toInteger(userMod.isLocalAccount()));
this.userRecordMapper.insert(recordToSave); this.userRecordMapper.insert(recordToSave);
final Long newUserPK = recordToSave.getId(); final Long newUserPK = recordToSave.getId();

View file

@ -42,7 +42,8 @@ public interface FullLmsIntegrationService {
String quizId, String quizId,
String examTemplateId, String examTemplateId,
String quitPassword, String quitPassword,
String quitLink); String quitLink,
String timezone);
Result<EntityKey> deleteExam( Result<EntityKey> deleteExam(
String lmsUUID, String lmsUUID,
@ -66,6 +67,9 @@ public interface FullLmsIntegrationService {
public final String name; public final String name;
@JsonProperty("url") @JsonProperty("url")
public final String url; public final String url;
@JsonProperty("autologin_url")
public final String autoLoginURL;
@JsonProperty("access_token") @JsonProperty("access_token")
public final String access_token; public final String access_token;
@JsonProperty("exam_templates") @JsonProperty("exam_templates")
@ -76,12 +80,14 @@ public interface FullLmsIntegrationService {
@JsonProperty("id") final String id, @JsonProperty("id") final String id,
@JsonProperty("name") final String name, @JsonProperty("name") final String name,
@JsonProperty("url") final String url, @JsonProperty("url") final String url,
@JsonProperty("autologin_url") final String autoLoginURL,
@JsonProperty("access_token") final String access_token, @JsonProperty("access_token") final String access_token,
@JsonProperty("exam_templates") final Collection<ExamTemplateSelection> exam_templates) { @JsonProperty("exam_templates") final Collection<ExamTemplateSelection> exam_templates) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.url = url; this.url = url;
this.autoLoginURL = autoLoginURL;
this.access_token = access_token; this.access_token = access_token;
this.exam_templates = Utils.immutableCollectionOf(exam_templates); this.exam_templates = Utils.immutableCollectionOf(exam_templates);
} }

View file

@ -12,12 +12,15 @@ import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Locale;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
@ -26,8 +29,12 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
@ -44,6 +51,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigServi
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -62,6 +70,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
private final LmsSetupDAO lmsSetupDAO; private final LmsSetupDAO lmsSetupDAO;
private final UserActivityLogDAO userActivityLogDAO; private final UserActivityLogDAO userActivityLogDAO;
private final UserDAO userDAO;
private final SEBClientConfigDAO sebClientConfigDAO; private final SEBClientConfigDAO sebClientConfigDAO;
private final ClientConfigService clientConfigService; private final ClientConfigService clientConfigService;
private final DeleteExamAction deleteExamAction; private final DeleteExamAction deleteExamAction;
@ -79,6 +88,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
public FullLmsIntegrationServiceImpl( public FullLmsIntegrationServiceImpl(
final LmsSetupDAO lmsSetupDAO, final LmsSetupDAO lmsSetupDAO,
final UserActivityLogDAO userActivityLogDAO, final UserActivityLogDAO userActivityLogDAO,
final UserDAO userDAO,
final SEBClientConfigDAO sebClientConfigDAO, final SEBClientConfigDAO sebClientConfigDAO,
final ClientConfigService clientConfigService, final ClientConfigService clientConfigService,
final DeleteExamAction deleteExamAction, final DeleteExamAction deleteExamAction,
@ -96,6 +106,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
this.lmsSetupDAO = lmsSetupDAO; this.lmsSetupDAO = lmsSetupDAO;
this.userActivityLogDAO = userActivityLogDAO; this.userActivityLogDAO = userActivityLogDAO;
this.userDAO = userDAO;
this.sebClientConfigDAO = sebClientConfigDAO; this.sebClientConfigDAO = sebClientConfigDAO;
this.clientConfigService = clientConfigService; this.clientConfigService = clientConfigService;
this.deleteExamAction = deleteExamAction; this.deleteExamAction = deleteExamAction;
@ -126,6 +137,11 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
} }
@Override
public void notifyExamDeletion(final ExamDeletionEvent event) {
event.ids.forEach(this::deleteAdHocAccount);
}
@Override @Override
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) { public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
final LmsSetup lmsSetup = event.getLmsSetup(); final LmsSetup lmsSetup = event.getLmsSetup();
@ -187,6 +203,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
connectionId, connectionId,
lmsSetup.name, lmsSetup.name,
getAPIRootURL(), getAPIRootURL(),
getAutoLoginURL(),
accessToken, accessToken,
this.getIntegrationTemplates(lmsSetup.institutionId) this.getIntegrationTemplates(lmsSetup.institutionId)
); );
@ -238,13 +255,14 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
final String quizId, final String quizId,
final String examTemplateId, final String examTemplateId,
final String quitPassword, final String quitPassword,
final String quitLink) { final String quitLink,
final String timezone) {
return lmsSetupDAO return lmsSetupDAO
.getLmsSetupIdByConnectionId(lmsUUID) .getLmsSetupIdByConnectionId(lmsUUID)
.flatMap(lmsAPIService::getLmsAPITemplate) .flatMap(lmsAPIService::getLmsAPITemplate)
.map(findQuizData(courseId, quizId)) .map(findQuizData(courseId, quizId))
.map(createAccountAndExam(examTemplateId, quitPassword)); .map(createAccountAndExam(examTemplateId, quitPassword, timezone));
} }
@Override @Override
@ -260,6 +278,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.flatMap(this::findExam) .flatMap(this::findExam)
.map(this::checkDeletion) .map(this::checkDeletion)
.map(this::logExamDeleted) .map(this::logExamDeleted)
.map(this::deleteAdHocAccount)
.flatMap(deleteExamAction::deleteExamFromLMSIntegration); .flatMap(deleteExamAction::deleteExamFromLMSIntegration);
} }
@ -276,10 +295,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
return exam; return exam;
} }
@Override
public void notifyExamDeletion(final ExamDeletionEvent event) {
event.ids.forEach(this::deleteAdHocAccount);
}
@Override @Override
public Result<Void> streamConnectionConfiguration( public Result<Void> streamConnectionConfiguration(
@ -306,12 +322,15 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
if (StringUtils.isBlank(connectionConfigId)) { if (StringUtils.isBlank(connectionConfigId)) {
connectionConfigId = this.sebClientConfigDAO connectionConfigId = this.sebClientConfigDAO
.all(exam.institutionId, true) .all(exam.institutionId, true)
.map(all -> all.iterator().next()) .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) .map(SEBClientConfig::getModelId)
.getOr(null); .getOr(null);
} }
if (StringUtils.isBlank(connectionConfigId)) { if (StringUtils.isBlank(connectionConfigId)) {
return Result.ofRuntimeError("No active Connection Configuration found"); throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found"));
} }
this.clientConfigService.exportSEBClientConfiguration(out, connectionConfigId, exam.id); this.clientConfigService.exportSEBClientConfiguration(out, connectionConfigId, exam.id);
@ -358,7 +377,8 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
private Function<QuizData, Exam> createAccountAndExam( private Function<QuizData, Exam> createAccountAndExam(
final String examTemplateId, final String examTemplateId,
final String quitPassword) { final String quitPassword,
final String timezone) {
return quizData -> { return quizData -> {
@ -378,8 +398,13 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword); post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
} }
final String accountUUID = createAdHocSupporterAccount(quizData); final String accountUUID = createAdHocSupporterAccount(quizData, timezone);
post.putIfAbsent(Domain.EXAM.ATTR_OWNER, accountUUID); if (accountUUID != null) {
post.putIfAbsent(Domain.EXAM.ATTR_OWNER, accountUUID);
// TODO do we need to apply the ad-hoc teacher account also as supporter?
} else {
post.putIfAbsent(Domain.EXAM.ATTR_OWNER, userService.getCurrentUser().uuid());
}
final Exam exam = new Exam(null, quizData, post); final Exam exam = new Exam(null, quizData, post);
return examDAO return examDAO
@ -390,16 +415,81 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
}; };
} }
private String createAdHocSupporterAccount(final QuizData data) { private String createAdHocSupporterAccount(final QuizData data, final String timezone) {
// TODO create an ad hoc supporter account for this exam and apply it to the exam try {
return "mockAccountUUID";
final String uuid = UUID.randomUUID().toString();
final String name = "teacher-" + uuid;
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,
data.institutionId,
name,
data.id,
name,
uuid,
uuid,
null,
Locale.ENGLISH,
dtz,
true,
false,
Utils.immutableSetOf(UserRole.TEACHER.name()));
userDAO.createNew(adHocTeacherUser)
.flatMap(account -> userDAO.setActive(account, true))
.getOrThrow();
return uuid;
} catch (final Exception e) {
log.error("Failed to create ad-hoc teacher account for importing exam: {}", data, e);
return null;
}
}
private Exam deleteAdHocAccount(final Exam exam) {
deleteAdHocAccount(exam.id);
return exam;
} }
private void deleteAdHocAccount(final Long examId) { private void deleteAdHocAccount(final Long examId) {
try { try {
// TODO check if exam has an ad-hoc account and if true, delete it
final Result<Exam> examResult = examDAO.byPK(examId);
if (examResult.hasError()) {
log.warn("Failed to get exam for id: {}", examId);
return;
}
final String externalId = examResult.get().externalId;
final FilterMap filter = new FilterMap();
filter.putIfAbsent(Domain.USER.ATTR_SURNAME, externalId);
final Collection<UserInfo> accounts = userDAO.allMatching(filter).getOrThrow();
if (accounts.isEmpty()) {
return;
}
if (accounts.size() > 1) {
log.error("Too many accounts found!?... ad-hoc teacher account mapping: {}", externalId);
return;
}
userDAO.delete(Utils.immutableSetOf(new EntityKey(
accounts.iterator().next().uuid,
EntityType.USER)))
.getOrThrow();
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to delete ad hoc account for exam: {}", examId, e); log.error("Failed to delete ad-hoc account for exam: {}", examId, e);
} }
} }
@ -420,6 +510,10 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
return webserviceInfo.getExternalServerURL() + lmsAPIEndpoint; return webserviceInfo.getExternalServerURL() + lmsAPIEndpoint;
} }
private String getAutoLoginURL() {
return webserviceInfo.getExternalServerURL() + webserviceInfo.getAutoLoginEndpoint();
}
private Exam logExamCreated(final Exam exam) { private Exam logExamCreated(final Exam exam) {
this.userActivityLogDAO this.userActivityLogDAO
.logCreate(exam) .logCreate(exam)

View file

@ -637,8 +637,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
public void streamLightExamConfig(final String modelId, final HttpServletResponse response) throws IOException{ public void streamLightExamConfig(final String modelId, final HttpServletResponse response) throws IOException{
final ServletOutputStream outputStream = response.getOutputStream(); final ServletOutputStream outputStream = response.getOutputStream();
PipedOutputStream pout; PipedOutputStream pout = null;
PipedInputStream pin; PipedInputStream pin= null;
try { try {
pout = new PipedOutputStream(); pout = new PipedOutputStream();

View file

@ -670,6 +670,8 @@ class ScreenProctoringAPIBinding {
userInfo.email, userInfo.email,
userInfo.language, userInfo.language,
userInfo.timeZone, userInfo.timeZone,
true,
true,
spsUserRoles); spsUserRoles);
} }

View file

@ -8,18 +8,23 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -53,9 +58,17 @@ public class LmsIntegrationController {
@RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID, required = true) final String templateId, @RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID, required = true) final String templateId,
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_PASSWORD, required = false) final String quitPassword, @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, @RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_LINK, required = false) final String quitLink,
@RequestParam(name = API.LMS_FULL_INTEGRATION_TIME_ZONE, required = false) final String timezone,
final HttpServletResponse response) { final HttpServletResponse response) {
final Exam exam = fullLmsIntegrationService.importExam(lmsUUId, courseId, quizId, templateId, quitPassword, quitLink) final Exam exam = fullLmsIntegrationService.importExam(
lmsUUId,
courseId,
quizId,
templateId,
quitPassword,
quitLink,
timezone)
.onError(e -> log.error( .onError(e -> log.error(
"Failed to create/import exam: lmsId:{}, courseId: {}, quizId: {}, templateId: {}", "Failed to create/import exam: lmsId:{}, courseId: {}, quizId: {}, templateId: {}",
lmsUUId, courseId, quizId, templateId, e)) lmsUUId, courseId, quizId, templateId, e))
@ -94,11 +107,33 @@ public class LmsIntegrationController {
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId, @RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
final HttpServletResponse response) throws IOException { final HttpServletResponse response) throws IOException {
fullLmsIntegrationService.streamConnectionConfiguration(lmsUUId, courseId, quizId, response.getOutputStream()) final ServletOutputStream outputStream = response.getOutputStream();
.onError(e -> log.error( final PipedOutputStream pout;
"Failed to stream connection configuration for exam: lmsId:{}, courseId: {}, quizId: {}", final PipedInputStream pin;
lmsUUId, courseId, quizId, e)) try {
.getOrThrow(); pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
} fullLmsIntegrationService
.streamConnectionConfiguration(lmsUUId, courseId, quizId, pout)
.getOrThrow();
IOUtils.copyLarge(pin, outputStream);
response.setStatus(HttpStatus.OK.value());
outputStream.flush();
} catch (final APIMessage.APIMessageException me) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
throw me;
} catch (final Exception e) {
log.error(
"Failed to stream connection configuration for exam: lmsId:{}, courseId: {}, quizId: {}",
lmsUUId, courseId, quizId, e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
} finally {
outputStream.flush();
outputStream.close();
}
}
} }

View file

@ -19,6 +19,7 @@ sebserver.gui.date.displayformat=de
sebserver.gui.http.external.scheme=${sebserver.webservice.http.external.scheme} sebserver.gui.http.external.scheme=${sebserver.webservice.http.external.scheme}
sebserver.gui.http.external.servername=${sebserver.webservice.http.external.servername} sebserver.gui.http.external.servername=${sebserver.webservice.http.external.servername}
sebserver.gui.http.external.port=${sebserver.webservice.http.external.port} sebserver.gui.http.external.port=${sebserver.webservice.http.external.port}
sebserver.gui.http.external.autologin.endpoint=/auto_login
sebserver.gui.http.webservice.scheme=http sebserver.gui.http.webservice.scheme=http
sebserver.gui.http.webservice.servername=localhost sebserver.gui.http.webservice.servername=localhost

View file

@ -47,6 +47,7 @@ sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port= sebserver.webservice.http.external.port=
sebserver.webservice.http.redirect.gui=/gui sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.ping.service.strategy=BLOCKING sebserver.webservice.ping.service.strategy=BLOCKING
sebserver.webservice.autologin.endpoint=/auto_login
### webservice API ### webservice API

View file

@ -96,6 +96,7 @@ public class ModelObjectJSONGenerator {
domainObject = new UserMod( domainObject = new UserMod(
"UUID", 1L, "NAME", "SURNAME", "USERNAME", "newPassword", "confirmNewPassword", "EMAIL", "UUID", 1L, "NAME", "SURNAME", "USERNAME", "newPassword", "confirmNewPassword", "EMAIL",
Locale.ENGLISH, DateTimeZone.UTC, Locale.ENGLISH, DateTimeZone.UTC,
true, true,
new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name(), UserRole.EXAM_SUPPORTER.name()))); new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name(), UserRole.EXAM_SUPPORTER.name())));
System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(domainObject.getClass().getSimpleName() + ":");
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));