SEBSERV-418
This commit is contained in:
parent
1ae00cc4ab
commit
9618b942fb
15 changed files with 613 additions and 135 deletions
7
pom.xml
7
pom.xml
|
@ -348,13 +348,18 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.8</version>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.vladimir-bukhtoyarov</groupId>
|
||||
<artifactId>bucket4j-core</artifactId>
|
||||
<version>4.10.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Testing -->
|
||||
|
|
|
@ -64,8 +64,11 @@ public final class API {
|
|||
|
||||
public static final String OAUTH_ENDPOINT = "/oauth";
|
||||
public static final String OAUTH_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/token";
|
||||
public static final String OAUTH_JWTTOKEN_ENDPOINT = OAUTH_ENDPOINT + "/jwttoken";
|
||||
|
||||
public static final String OAUTH_JWT_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/jwttoken";
|
||||
public static final String OAUTH_JWT_TOKEN_VERIFY_ENDPOINT = OAUTH_JWT_TOKEN_ENDPOINT + "/verify";
|
||||
public static final String OAUTH_REVOKE_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/revoke-token";
|
||||
public static final String SPS_OAUTH_JWT_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/jwttoken";
|
||||
|
||||
public static final String GRANT_TYPE_PASSWORD = "password";
|
||||
public static final String GRANT_TYPE_CLIENT = "client_credentials";
|
||||
|
@ -175,6 +178,8 @@ 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_TIME_ZONE = "account_time_zone";
|
||||
|
||||
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gbl.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
|
||||
public class TokenLoginInfo {
|
||||
@JsonProperty
|
||||
public final String username;
|
||||
@JsonProperty
|
||||
public final String userUUID;
|
||||
@JsonProperty
|
||||
public final String redirect;
|
||||
@JsonProperty
|
||||
public final OAuth2AccessToken login;
|
||||
|
||||
@JsonCreator
|
||||
public TokenLoginInfo(
|
||||
@JsonProperty final String username,
|
||||
@JsonProperty final String userUUID,
|
||||
@JsonProperty final String redirect,
|
||||
@JsonProperty final OAuth2AccessToken login) {
|
||||
|
||||
this.username = username;
|
||||
this.userUUID = userUUID;
|
||||
this.redirect = redirect;
|
||||
this.login = login;
|
||||
}
|
||||
}
|
|
@ -49,6 +49,10 @@ public class Cryptor {
|
|||
this.internalPWD = environment.getProperty("sebserver.webservice.internalSecret");
|
||||
}
|
||||
|
||||
public CharSequence getInternalPWD() {
|
||||
return this.internalPWD;
|
||||
}
|
||||
|
||||
/** Use this to encrypt a text with the internal password
|
||||
*
|
||||
* @param text The text to encrypt with the internal password
|
||||
|
|
|
@ -330,7 +330,7 @@ public class MonitoringProctoringService {
|
|||
String.class);
|
||||
|
||||
// JWT token request URL
|
||||
final String jwtTokenURL = settings.spsServiceURL + API.OAUTH_JWTTOKEN_ENDPOINT;
|
||||
final String jwtTokenURL = settings.spsServiceURL + API.SPS_OAUTH_JWT_TOKEN_ENDPOINT;
|
||||
|
||||
// Basic Auth header and content type header
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.authorization;
|
||||
|
||||
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;
|
||||
|
||||
public interface TeacherAccountService {
|
||||
|
||||
Result<UserInfo> createNewTeacherAccountForExam(
|
||||
Exam exam,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone);
|
||||
|
||||
Result<Exam> deleteTeacherAccountsForExam(final Exam exam);
|
||||
|
||||
Result<String> getOneTimeTokenForTeacherAccount(
|
||||
Exam exam,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone,
|
||||
final boolean createIfNotExists);
|
||||
|
||||
Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(String token);
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
* 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.authorization.impl;
|
||||
|
||||
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;
|
||||
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.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
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.session.ScreenProctoringService;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.AdminAPIClientDetails;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TeacherAccountServiceImpl.class);
|
||||
|
||||
private static final String SUBJECT_CLAIM_NAME = "sub";
|
||||
private static final String USER_CLAIM = "usr";
|
||||
private static final String EXAM_ID_CLAIM = "exam";
|
||||
|
||||
private static final String EXAM_OTT_SUBJECT_PREFIX = "EXAM_OTT_SUBJECT_";
|
||||
|
||||
private final UserDAO userDAO;
|
||||
private final ScreenProctoringService screenProctoringService;
|
||||
private final ExamDAO examDAO;
|
||||
private final Cryptor cryptor;
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
final TokenEndpoint tokenEndpoint;
|
||||
private final AdminAPIClientDetails adminAPIClientDetails;
|
||||
|
||||
public TeacherAccountServiceImpl(
|
||||
final UserDAO userDAO,
|
||||
final ScreenProctoringService screenProctoringService,
|
||||
final ExamDAO examDAO,
|
||||
final Cryptor cryptor,
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final TokenEndpoint tokenEndpoint,
|
||||
final AdminAPIClientDetails adminAPIClientDetails) {
|
||||
|
||||
this.userDAO = userDAO;
|
||||
this.screenProctoringService = screenProctoringService;
|
||||
this.examDAO = examDAO;
|
||||
this.cryptor = cryptor;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.tokenEndpoint = tokenEndpoint;
|
||||
this.adminAPIClientDetails = adminAPIClientDetails;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Result<UserInfo> createNewTeacherAccountForExam(
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
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();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> deleteTeacherAccountsForExam(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();
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getOneTimeTokenForTeacherAccount(
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone,
|
||||
final boolean createIfNotExists) {
|
||||
|
||||
return this.userDAO
|
||||
.byModelId(userId)
|
||||
.onErrorDo(error -> handleAccountDoesNotExistYet(createIfNotExists, exam, userId, username, timezone))
|
||||
.map(account -> applySupporter(account, exam))
|
||||
.map(account -> {
|
||||
this.screenProctoringService.synchronizeSPSUserForExam(exam.id);
|
||||
return account;
|
||||
})
|
||||
.map(account -> this.createOneTimeToken(account, exam.id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(final String loginToken) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final Claims claims = checkJWTValid(loginToken);
|
||||
final String userId = claims.get(USER_CLAIM, String.class);
|
||||
|
||||
// check if requested user exists
|
||||
final UserInfo user = this.userDAO
|
||||
.byModelId(userId)
|
||||
.getOrThrow(error -> new BadCredentialsException("Unknown user claim", error));
|
||||
|
||||
// login the user by getting access token
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put("grant_type", "password");
|
||||
params.put("username", user.username);
|
||||
params.put("password", user.uuid);
|
||||
//final WebAuthenticationDetails details = new WebAuthenticationDetails("localhost", null);
|
||||
final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
this.adminAPIClientDetails, // TODO are this the correct details?
|
||||
null,
|
||||
Collections.emptyList());
|
||||
final ResponseEntity<OAuth2AccessToken> accessToken =
|
||||
this.tokenEndpoint.postAccessToken(usernamePasswordAuthenticationToken, params);
|
||||
final OAuth2AccessToken token = accessToken.getBody();
|
||||
|
||||
return new TokenLoginInfo(user.username, user.uuid, null, token);
|
||||
});
|
||||
}
|
||||
|
||||
private UserInfo handleAccountDoesNotExistYet(
|
||||
final boolean createIfNotExists,
|
||||
final Exam exam,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
|
||||
if (createIfNotExists) {
|
||||
return this
|
||||
.createNewTeacherAccountForExam(exam, userId, username, timezone)
|
||||
.getOrThrow();
|
||||
} else {
|
||||
throw new RuntimeException("Teacher Account with userId "+ userId + " and username "+username+" does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private UserInfo applySupporter(final UserInfo account, final Exam exam) {
|
||||
if (!exam.supporter.contains(account.uuid)) {
|
||||
this.examDAO.applySupporter(exam, account.uuid)
|
||||
.onError(error -> log.error(
|
||||
"Failed to apply ad-hoc-teacher account to supporter list of exam: {} user: {}",
|
||||
exam, account, error));
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
private String createOneTimeToken(final UserInfo account, final Long examId) {
|
||||
|
||||
// create a subject claim for this token only
|
||||
final String subjectClaim = UUID.randomUUID().toString();
|
||||
this.storeSubjectForExam(examId, account.uuid, subjectClaim);
|
||||
|
||||
final Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(USER_CLAIM, account.uuid);
|
||||
claims.put(EXAM_ID_CLAIM, String.valueOf(examId));
|
||||
|
||||
return createToken(claims, subjectClaim);
|
||||
}
|
||||
|
||||
// NOTE Token is expired in 30 seconds and is signed with internal secret
|
||||
private String createToken(final Map<String, Object> claims, final String subject) {
|
||||
final long millisecondsNow = Utils.getMillisecondsNow();
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(new Date(millisecondsNow))
|
||||
.setExpiration(new Date(millisecondsNow + 30 * Constants.SECOND_IN_MILLIS))
|
||||
.signWith(SignatureAlgorithm.HS256, this.cryptor.getInternalPWD().toString())
|
||||
.compact();
|
||||
}
|
||||
|
||||
private Claims checkJWTValid(final String loginToken) {
|
||||
// decode given JWT
|
||||
final Claims claims = Jwts.parser()
|
||||
.setSigningKey(this.cryptor.getInternalPWD().toString())
|
||||
.parseClaimsJws(loginToken)
|
||||
.getBody();
|
||||
|
||||
// check expiration date
|
||||
final long expirationTime = claims.getExpiration().getTime();
|
||||
final long now = Utils.getMillisecondsNow();
|
||||
if (expirationTime < now) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Token expired"));
|
||||
}
|
||||
|
||||
// check user claim
|
||||
final String userId = claims.get(USER_CLAIM, String.class);
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("User not found"));
|
||||
}
|
||||
|
||||
// get exam id
|
||||
final String examId = claims.get(EXAM_ID_CLAIM, String.class);
|
||||
if (StringUtils.isBlank(examId)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Exam id not found"));
|
||||
}
|
||||
final Long examPK = Long.parseLong(examId);
|
||||
|
||||
// check subject
|
||||
final String subjectClaim = getSubjectForExam(examPK, userId);
|
||||
if (StringUtils.isBlank(subjectClaim)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Subject not found"));
|
||||
}
|
||||
final String subject = claims.get(SUBJECT_CLAIM_NAME, String.class);
|
||||
if (!subjectClaim.equals(subject)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Token subject mismatch"));
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private void storeSubjectForExam(final Long examId, final String userId, final String subject) {
|
||||
additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
EXAM_OTT_SUBJECT_PREFIX + userId,
|
||||
subject)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private void deleteSubjectForExam(final Long examId, final String userId) {
|
||||
additionalAttributesDAO.delete(EntityType.EXAM, examId, EXAM_OTT_SUBJECT_PREFIX + userId);
|
||||
}
|
||||
|
||||
private String getSubjectForExam(final Long examId, final String userId) {
|
||||
return additionalAttributesDAO
|
||||
.getAdditionalAttribute(EntityType.EXAM, examId, EXAM_OTT_SUBJECT_PREFIX + userId)
|
||||
.map(AdditionalAttributeRecord::getValue)
|
||||
.onError(error -> log.warn("Failed to get OTT subject from exam: {}", error.getMessage()))
|
||||
.getOrElse(null);
|
||||
}
|
||||
|
||||
private static String getTeacherAccountIdentifier(final Exam exam) {
|
||||
return "AdHoc-Teacher-Account-" + exam.id;
|
||||
}
|
||||
}
|
|
@ -228,6 +228,11 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
key = "#examId")
|
||||
void markUpdate(Long examId);
|
||||
|
||||
@CacheEvict(
|
||||
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||
key = "#examId")
|
||||
Result<Exam> applySupporter(Exam exam, String userUUID);
|
||||
|
||||
/** This is used by the internal update process to mark exams for which the LMS related data availability
|
||||
*
|
||||
* @param externalQuizId The exams external UUID or quiz id of the exam to mark
|
||||
|
@ -238,4 +243,5 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
void updateQuitPassword(Exam exam, String quitPassword);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ 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;
|
||||
|
@ -189,8 +190,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
UpdateDSL.updateWithMapper(
|
||||
this.examRecordMapper::update,
|
||||
ExamRecordDynamicSqlSupport.examRecord)
|
||||
.set(ExamRecordDynamicSqlSupport.lastModified)
|
||||
.equalTo(millisecondsNow)
|
||||
.set(ExamRecordDynamicSqlSupport.lastModified).equalTo(millisecondsNow)
|
||||
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(examId))
|
||||
.build()
|
||||
.execute();
|
||||
|
@ -200,6 +200,30 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<Exam> applySupporter(final Exam exam, final String userUUID) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final long millisecondsNow = Utils.getMillisecondsNow();
|
||||
final List<String> newSupporter = new ArrayList<>(exam.supporter);
|
||||
newSupporter.add(userUUID);
|
||||
|
||||
UpdateDSL.updateWithMapper(
|
||||
this.examRecordMapper::update,
|
||||
ExamRecordDynamicSqlSupport.examRecord)
|
||||
.set(supporter).equalTo(StringUtils.join(newSupporter, Constants.LIST_SEPARATOR_CHAR))
|
||||
.set(ExamRecordDynamicSqlSupport.lastModified).equalTo(millisecondsNow)
|
||||
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(exam.id))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
return examRecordMapper.selectByPrimaryKey(exam.id);
|
||||
})
|
||||
.flatMap(this::toDomainModel)
|
||||
.onError(TransactionHandler::rollback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId) {
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
|||
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
@ -42,8 +41,7 @@ public interface FullLmsIntegrationService {
|
|||
String quizId,
|
||||
String examTemplateId,
|
||||
String quitPassword,
|
||||
String quitLink,
|
||||
String timezone);
|
||||
String quitLink);
|
||||
|
||||
Result<EntityKey> deleteExam(
|
||||
String lmsUUID,
|
||||
|
@ -59,6 +57,14 @@ public interface FullLmsIntegrationService {
|
|||
String quizId,
|
||||
OutputStream out);
|
||||
|
||||
Result<String> getOneTimeLoginToken(
|
||||
String lmsUUId,
|
||||
String courseId,
|
||||
String quizId,
|
||||
String userId,
|
||||
String username,
|
||||
String timezone);
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
final class IntegrationData {
|
||||
@JsonProperty("id")
|
||||
|
|
|
@ -12,15 +12,12 @@ import java.io.OutputStream;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||
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.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
|
@ -29,15 +26,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.institution.LmsSetup;
|
||||
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.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
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.TeacherAccountServiceImpl;
|
||||
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;
|
||||
|
@ -49,9 +43,9 @@ 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.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -70,7 +64,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
|
||||
private final LmsSetupDAO lmsSetupDAO;
|
||||
private final UserActivityLogDAO userActivityLogDAO;
|
||||
private final UserDAO userDAO;
|
||||
private final TeacherAccountServiceImpl teacherAccountServiceImpl;
|
||||
private final SEBClientConfigDAO sebClientConfigDAO;
|
||||
private final ClientConfigService clientConfigService;
|
||||
private final DeleteExamAction deleteExamAction;
|
||||
|
@ -90,6 +84,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final UserDAO userDAO,
|
||||
final SEBClientConfigDAO sebClientConfigDAO,
|
||||
final ScreenProctoringService screenProctoringService,
|
||||
final ClientConfigService clientConfigService,
|
||||
final DeleteExamAction deleteExamAction,
|
||||
final LmsAPIService lmsAPIService,
|
||||
|
@ -100,13 +95,14 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
final WebserviceInfo webserviceInfo,
|
||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||
final UserService userService,
|
||||
final TeacherAccountServiceImpl teacherAccountServiceImpl,
|
||||
@Value("${sebserver.webservice.lms.api.endpoint}") final String lmsAPIEndpoint,
|
||||
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
||||
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) {
|
||||
|
||||
this.lmsSetupDAO = lmsSetupDAO;
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.userDAO = userDAO;
|
||||
this.teacherAccountServiceImpl = teacherAccountServiceImpl;
|
||||
this.sebClientConfigDAO = sebClientConfigDAO;
|
||||
this.clientConfigService = clientConfigService;
|
||||
this.deleteExamAction = deleteExamAction;
|
||||
|
@ -139,7 +135,11 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
|
||||
@Override
|
||||
public void notifyExamDeletion(final ExamDeletionEvent event) {
|
||||
event.ids.forEach(this::deleteAdHocAccount);
|
||||
event.ids.forEach( examId -> {
|
||||
this.examDAO.byPK(examId)
|
||||
.map(this.teacherAccountServiceImpl::deleteTeacherAccountsForExam)
|
||||
.onError(error -> log.warn("Failed delete teacher accounts for exam: {}", examId));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -255,14 +255,13 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
final String quizId,
|
||||
final String examTemplateId,
|
||||
final String quitPassword,
|
||||
final String quitLink,
|
||||
final String timezone) {
|
||||
final String quitLink) {
|
||||
|
||||
return lmsSetupDAO
|
||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||
.map(findQuizData(courseId, quizId))
|
||||
.map(createAccountAndExam(examTemplateId, quitPassword, timezone));
|
||||
.map(createExam(examTemplateId, quitPassword));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -278,7 +277,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
.flatMap(this::findExam)
|
||||
.map(this::checkDeletion)
|
||||
.map(this::logExamDeleted)
|
||||
.map(this::deleteAdHocAccount)
|
||||
.flatMap(teacherAccountServiceImpl::deleteTeacherAccountsForExam)
|
||||
.flatMap(deleteExamAction::deleteExamFromLMSIntegration);
|
||||
}
|
||||
|
||||
|
@ -341,6 +340,25 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getOneTimeLoginToken(
|
||||
final String lmsUUID,
|
||||
final String courseId,
|
||||
final String quizId,
|
||||
final String userId,
|
||||
final String username,
|
||||
final String timezone) {
|
||||
|
||||
return lmsSetupDAO
|
||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
||||
.map(findQuizData(courseId, quizId))
|
||||
.flatMap(this::findExam)
|
||||
.flatMap(exam -> this.teacherAccountServiceImpl
|
||||
.getOneTimeTokenForTeacherAccount(exam, userId, username, timezone, true));
|
||||
}
|
||||
|
||||
|
||||
private Function<LmsAPITemplate, QuizData> findQuizData(
|
||||
final String courseId,
|
||||
final String quizId) {
|
||||
|
@ -375,10 +393,9 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
return examDAO.byExternalIdLike(quizData.id);
|
||||
}
|
||||
|
||||
private Function<QuizData, Exam> createAccountAndExam(
|
||||
private Function<QuizData, Exam> createExam(
|
||||
final String examTemplateId,
|
||||
final String quitPassword,
|
||||
final String timezone) {
|
||||
final String quitPassword) {
|
||||
|
||||
return quizData -> {
|
||||
|
||||
|
@ -398,14 +415,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
post.putIfAbsent(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
|
||||
}
|
||||
|
||||
final String accountUUID = createAdHocSupporterAccount(quizData, timezone);
|
||||
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);
|
||||
return examDAO
|
||||
.createNew(exam)
|
||||
|
@ -415,83 +424,82 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
};
|
||||
}
|
||||
|
||||
private String createAdHocSupporterAccount(final QuizData data, final String timezone) {
|
||||
try {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
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();
|
||||
// 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;
|
||||
// }
|
||||
|
||||
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) {
|
||||
try {
|
||||
|
||||
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) {
|
||||
log.error("Failed to delete ad-hoc account for exam: {}", examId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ExamTemplateSelection> getIntegrationTemplates(final Long institutionId) {
|
||||
return examTemplateDAO
|
||||
|
|
|
@ -91,6 +91,9 @@ public interface ScreenProctoringService extends SessionUpdateTask {
|
|||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
void synchronizeSPSUser(final String userUUID);
|
||||
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
void synchronizeSPSUserForExam(final Long examId);
|
||||
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
void deleteSPSUser(String userUUID);
|
||||
|
||||
|
|
|
@ -1004,7 +1004,7 @@ class ScreenProctoringAPIBinding {
|
|||
10 * Constants.SECOND_IN_MILLIS,
|
||||
10 * Constants.SECOND_IN_MILLIS);
|
||||
|
||||
ClientCredentials clientCredentials = new ClientCredentials(
|
||||
final ClientCredentials clientCredentials = new ClientCredentials(
|
||||
spsAPIAccessData.getSpsAPIKey(),
|
||||
spsAPIAccessData.getSpsAPISecret());
|
||||
|
||||
|
|
|
@ -268,6 +268,13 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
this.screenProctoringAPIBinding.synchronizeUserAccount(userUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void synchronizeSPSUserForExam(final Long examId) {
|
||||
this.examDAO.byPK(examId)
|
||||
.onSuccess(this.screenProctoringAPIBinding::synchronizeUserAccounts)
|
||||
.onError(error -> log.error("Failed to synchronize SPS user accounts for exam: {}", examId, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
public void deleteSPSUser(final String userUUID) {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -18,11 +17,9 @@ import java.io.PipedOutputStream;
|
|||
|
||||
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.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -58,7 +55,6 @@ public class LmsIntegrationController {
|
|||
@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_LINK, required = false) final String quitLink,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_TIME_ZONE, required = false) final String timezone,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
final Exam exam = fullLmsIntegrationService.importExam(
|
||||
|
@ -67,8 +63,7 @@ public class LmsIntegrationController {
|
|||
quizId,
|
||||
templateId,
|
||||
quitPassword,
|
||||
quitLink,
|
||||
timezone)
|
||||
quitLink)
|
||||
.onError(e -> log.error(
|
||||
"Failed to create/import exam: lmsId:{}, courseId: {}, quizId: {}, templateId: {}",
|
||||
lmsUUId, courseId, quizId, templateId, e))
|
||||
|
@ -107,33 +102,52 @@ public class LmsIntegrationController {
|
|||
@RequestParam(name = API.LMS_FULL_INTEGRATION_QUIZ_ID, required = true) final String quizId,
|
||||
final HttpServletResponse response) throws IOException {
|
||||
|
||||
final ServletOutputStream outputStream = response.getOutputStream();
|
||||
final PipedOutputStream pout;
|
||||
final PipedInputStream pin;
|
||||
try {
|
||||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
final ServletOutputStream outputStream = response.getOutputStream();
|
||||
final PipedOutputStream pout;
|
||||
final PipedInputStream pin;
|
||||
try {
|
||||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
|
||||
fullLmsIntegrationService
|
||||
.streamConnectionConfiguration(lmsUUId, courseId, quizId, pout)
|
||||
.getOrThrow();
|
||||
fullLmsIntegrationService
|
||||
.streamConnectionConfiguration(lmsUUId, courseId, quizId, pout)
|
||||
.getOrThrow();
|
||||
|
||||
IOUtils.copyLarge(pin, outputStream);
|
||||
IOUtils.copyLarge(pin, outputStream);
|
||||
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
outputStream.flush();
|
||||
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();
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.LMS_FULL_INTEGRATION_LOGIN_TOKEN_ENDPOINT,
|
||||
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,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_USER_NAME, required = false) final String username,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_TIME_ZONE, required = false) final String timezone,
|
||||
final HttpServletResponse response) throws IOException {
|
||||
|
||||
return this.fullLmsIntegrationService
|
||||
.getOneTimeLoginToken(lmsUUId, courseId, quizId, userId, username, timezone)
|
||||
.getOrThrow();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue