OpenEdX API Setup plus tests

This commit is contained in:
anhefti 2019-02-03 21:40:59 +01:00
parent 44dbe1e714
commit cda50a15e7
9 changed files with 260 additions and 131 deletions

View file

@ -24,7 +24,8 @@ public class InternalEncryptionService {
private static final Logger log = LoggerFactory.getLogger(InternalEncryptionService.class); private static final Logger log = LoggerFactory.getLogger(InternalEncryptionService.class);
private static final String NO_SALT = "NO_SALT"; static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
private static final String DEFAULT_SALT = "b7dbe99bbfa3e21e";
private final Environment environment; private final Environment environment;
@ -35,8 +36,8 @@ public class InternalEncryptionService {
public String encrypt(final String text) { public String encrypt(final String text) {
try { try {
return Encryptors.text( return Encryptors.text(
this.environment.getRequiredProperty("sebserver.webservice.internalSecret"), this.environment.getRequiredProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY),
NO_SALT).encrypt(text); DEFAULT_SALT).encrypt(text);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to encrypt text: ", e); log.error("Failed to encrypt text: ", e);
return text; return text;
@ -46,8 +47,8 @@ public class InternalEncryptionService {
public String decrypt(final String text) { public String decrypt(final String text) {
try { try {
return Encryptors.text( return Encryptors.text(
this.environment.getRequiredProperty("sebserver.webservice.internalSecret"), this.environment.getRequiredProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY),
NO_SALT).decrypt(text); DEFAULT_SALT).decrypt(text);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to decrypt text: ", e); log.error("Failed to decrypt text: ", e);
return text; return text;
@ -57,7 +58,7 @@ public class InternalEncryptionService {
public String encrypt(final String text, final CharSequence salt) { public String encrypt(final String text, final CharSequence salt) {
try { try {
return Encryptors.text( return Encryptors.text(
this.environment.getRequiredProperty("sebserver.webservice.internalSecret"), this.environment.getRequiredProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY),
salt).encrypt(text); salt).encrypt(text);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to encrypt text: ", e); log.error("Failed to encrypt text: ", e);
@ -68,7 +69,7 @@ public class InternalEncryptionService {
public String decrypt(final String text, final CharSequence salt) { public String decrypt(final String text, final CharSequence salt) {
try { try {
return Encryptors.text( return Encryptors.text(
this.environment.getRequiredProperty("sebserver.webservice.internalSecret"), this.environment.getRequiredProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY),
salt).decrypt(text); salt).decrypt(text);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to decrypt text: ", e); log.error("Failed to decrypt text: ", e);

View file

@ -9,8 +9,25 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, BulkActionSupportDAO<LmsSetup> { public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, BulkActionSupportDAO<LmsSetup> {
Result<Credentials> getLmsAPIAccessCredentials(String lmsSetupId);
Result<Credentials> getSEBClientCredentials(String lmsSetupId);
final class Credentials {
public final String clientId;
public final String secret;
public final String accessToken;
public Credentials(final String clientId, final String secret, final String accessToken) {
super();
this.clientId = clientId;
this.secret = secret;
this.accessToken = accessToken;
}
}
} }

View file

@ -254,6 +254,34 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
}); });
} }
@Override
@Transactional(readOnly = true)
public Result<Credentials> getLmsAPIAccessCredentials(final String lmsSetupId) {
return Result.tryCatch(() -> {
final LmsSetupRecord record = this.lmsSetupRecordMapper
.selectByPrimaryKey(Long.parseLong(lmsSetupId));
return new Credentials(
record.getLmsClientname(),
record.getLmsClientsecret(),
record.getLmsRestApiToken());
});
}
@Override
@Transactional(readOnly = true)
public Result<Credentials> getSEBClientCredentials(final String lmsSetupId) {
return Result.tryCatch(() -> {
final LmsSetupRecord record = this.lmsSetupRecordMapper
.selectByPrimaryKey(Long.parseLong(lmsSetupId));
return new Credentials(
record.getSebClientname(),
record.getSebClientsecret(),
null);
});
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) { private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
return this.lmsSetupRecordMapper.selectIdsByExample() return this.lmsSetupRecordMapper.selectIdsByExample()

View file

@ -20,7 +20,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
public interface LmsAPITemplate { public interface LmsAPITemplate {
LmsSetup lmsSetup(); Result<LmsSetup> lmsSetup();
LmsSetupTestResult testLmsSetup(); LmsSetupTestResult testLmsSetup();
@ -35,4 +35,6 @@ public interface LmsAPITemplate {
Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId); Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
void reset();
} }

View file

@ -33,6 +33,8 @@ public class LmsAPIServiceImpl implements LmsAPIService {
private final ClientHttpRequestFactory clientHttpRequestFactory; private final ClientHttpRequestFactory clientHttpRequestFactory;
private final String[] openEdxAlternativeTokenRequestPaths; private final String[] openEdxAlternativeTokenRequestPaths;
// TODO internal caching of LmsAPITemplate per LmsSetup (Id)
public LmsAPIServiceImpl( public LmsAPIServiceImpl(
final LmsSetupDAO lmsSetupDAO, final LmsSetupDAO lmsSetupDAO,
final InternalEncryptionService internalEncryptionService, final InternalEncryptionService internalEncryptionService,
@ -59,10 +61,14 @@ public class LmsAPIServiceImpl implements LmsAPIService {
public Result<LmsAPITemplate> createLmsAPITemplate(final LmsSetup lmsSetup) { public Result<LmsAPITemplate> createLmsAPITemplate(final LmsSetup lmsSetup) {
switch (lmsSetup.lmsType) { switch (lmsSetup.lmsType) {
case MOCKUP: case MOCKUP:
return Result.of(new MockupLmsAPITemplate(lmsSetup)); return Result.of(new MockupLmsAPITemplate(
this.lmsSetupDAO,
lmsSetup,
this.internalEncryptionService));
case OPEN_EDX: case OPEN_EDX:
return Result.of(new OpenEdxLmsAPITemplate( return Result.of(new OpenEdxLmsAPITemplate(
lmsSetup, lmsSetup.getModelId(),
this.lmsSetupDAO,
this.internalEncryptionService, this.internalEncryptionService,
this.clientHttpRequestFactory, this.clientHttpRequestFactory,
this.openEdxAlternativeTokenRequestPaths)); this.openEdxAlternativeTokenRequestPaths));

View file

@ -24,16 +24,30 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.InternalEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
final class MockupLmsAPITemplate implements LmsAPITemplate { final class MockupLmsAPITemplate implements LmsAPITemplate {
public static final String MOCKUP_LMS_CLIENT_NAME = "mockupLmsClientName";
public static final String MOCKUP_LMS_CLIENT_SECRET = "mockupLmsClientSecret";
private final InternalEncryptionService internalEncryptionService;
private final LmsSetupDAO lmsSetupDao;
private final LmsSetup setup; private final LmsSetup setup;
private LmsSetupDAO.Credentials credentials = null;
private final Collection<QuizData> mockups; private final Collection<QuizData> mockups;
MockupLmsAPITemplate(final LmsSetup setup) { MockupLmsAPITemplate(
final LmsSetupDAO lmsSetupDao,
final LmsSetup setup,
final InternalEncryptionService internalEncryptionService) {
this.lmsSetupDao = lmsSetupDao;
this.internalEncryptionService = internalEncryptionService;
if (!setup.isActive() || setup.lmsType != LmsType.MOCKUP) { if (!setup.isActive() || setup.lmsType != LmsType.MOCKUP) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
@ -64,8 +78,8 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
} }
@Override @Override
public LmsSetup lmsSetup() { public Result<LmsSetup> lmsSetup() {
return this.setup; return Result.of(this.setup);
} }
@Override @Override
@ -73,10 +87,8 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
if (this.setup.lmsType != LmsType.MOCKUP) { if (this.setup.lmsType != LmsType.MOCKUP) {
return LmsSetupTestResult.ofMissingAttributes(LMS_SETUP.ATTR_LMS_TYPE); return LmsSetupTestResult.ofMissingAttributes(LMS_SETUP.ATTR_LMS_TYPE);
} }
if (this.setup.lmsApiUrl.equals("mockup") && initCredentials();
this.setup.lmsAuthName.equals("mockup") && if (this.credentials != null) {
this.setup.lmsAuthSecret.equals("mockup")) {
return LmsSetupTestResult.ofOkay(); return LmsSetupTestResult.ofOkay();
} else { } else {
return LmsSetupTestResult.ofMissingAttributes( return LmsSetupTestResult.ofMissingAttributes(
@ -121,6 +133,11 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
final int pageSize) { final int pageSize) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
initCredentials();
if (this.credentials == null) {
throw new IllegalArgumentException("Wrong clientId or secret");
}
final int startIndex = pageNumber * pageSize; final int startIndex = pageNumber * pageSize;
final int endIndex = startIndex + pageSize; final int endIndex = startIndex + pageSize;
int index = 0; int index = 0;
@ -142,6 +159,11 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
@Override @Override
public Collection<Result<QuizData>> getQuizzes(final Set<String> ids) { public Collection<Result<QuizData>> getQuizzes(final Set<String> ids) {
initCredentials();
if (this.credentials == null) {
throw new IllegalArgumentException("Wrong clientId or secret");
}
return this.mockups.stream() return this.mockups.stream()
.filter(mockup -> ids.contains(mockup.id)) .filter(mockup -> ids.contains(mockup.id))
.map(mockup -> Result.of(mockup)) .map(mockup -> Result.of(mockup))
@ -150,7 +172,31 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
@Override @Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeUserId) { public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeUserId) {
initCredentials();
if (this.credentials == null) {
throw new IllegalArgumentException("Wrong clientId or secret");
}
return Result.of(new ExamineeAccountDetails(examineeUserId, "mockup", "mockup", "mockup")); return Result.of(new ExamineeAccountDetails(examineeUserId, "mockup", "mockup", "mockup"));
} }
@Override
public void reset() {
this.credentials = null;
}
private void initCredentials() {
try {
this.credentials = this.lmsSetupDao
.getLmsAPIAccessCredentials(this.setup.getModelId())
.getOrThrow();
assert (this.credentials.clientId.equals("lmsMockupClientId")) : "wrong clientId";
assert (this.internalEncryptionService.decrypt(this.credentials.clientId)
.equals("lmsMockupSecret")) : "wrong clientId";
} catch (final Exception e) {
this.credentials = null;
}
}
} }

View file

@ -24,11 +24,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
@ -40,6 +37,8 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.InternalEncryptionService; import ch.ethz.seb.sebserver.webservice.servicelayer.InternalEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO.Credentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
final class OpenEdxLmsAPITemplate implements LmsAPITemplate { final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
@ -50,7 +49,8 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
private static final String OPEN_EDX_DEFAULT_COURSE_ENDPOINT = "/api/courses/v1/courses/"; private static final String OPEN_EDX_DEFAULT_COURSE_ENDPOINT = "/api/courses/v1/courses/";
private static final String OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX = "/courses/"; private static final String OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX = "/courses/";
private final LmsSetup lmsSetup; private final String lmsSetupId;
private final LmsSetupDAO lmsSetupDAO;
private final ClientHttpRequestFactory clientHttpRequestFactory; private final ClientHttpRequestFactory clientHttpRequestFactory;
private final InternalEncryptionService internalEncryptionService; private final InternalEncryptionService internalEncryptionService;
private final Set<String> knownTokenAccessPaths; private final Set<String> knownTokenAccessPaths;
@ -58,12 +58,14 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
private OAuth2RestTemplate restTemplate = null; private OAuth2RestTemplate restTemplate = null;
OpenEdxLmsAPITemplate( OpenEdxLmsAPITemplate(
final LmsSetup lmsSetup, final String lmsSetupId,
final LmsSetupDAO lmsSetupDAO,
final InternalEncryptionService internalEncryptionService, final InternalEncryptionService internalEncryptionService,
final ClientHttpRequestFactory clientHttpRequestFactory, final ClientHttpRequestFactory clientHttpRequestFactory,
final String[] alternativeTokenRequestPaths) { final String[] alternativeTokenRequestPaths) {
this.lmsSetup = lmsSetup; this.lmsSetupId = lmsSetupId;
this.lmsSetupDAO = lmsSetupDAO;
this.clientHttpRequestFactory = clientHttpRequestFactory; this.clientHttpRequestFactory = clientHttpRequestFactory;
this.internalEncryptionService = internalEncryptionService; this.internalEncryptionService = internalEncryptionService;
@ -75,27 +77,30 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
} }
@Override @Override
public LmsSetup lmsSetup() { public Result<LmsSetup> lmsSetup() {
return this.lmsSetup; return this.lmsSetupDAO
.byModelId(this.lmsSetupId);
} }
@Override @Override
public LmsSetupTestResult testLmsSetup() { public LmsSetupTestResult testLmsSetup() {
log.info("Test Lms Binding for OpenEdX and LmsSetup: {}", this.lmsSetup); final LmsSetup lmsSetup = lmsSetup().getOrThrow();
log.info("Test Lms Binding for OpenEdX and LmsSetup: {}", lmsSetup);
// validation of LmsSetup // validation of LmsSetup
if (this.lmsSetup.lmsType != LmsType.MOCKUP) { if (lmsSetup.lmsType != LmsType.MOCKUP) {
return LmsSetupTestResult.ofMissingAttributes(LMS_SETUP.ATTR_LMS_TYPE); return LmsSetupTestResult.ofMissingAttributes(LMS_SETUP.ATTR_LMS_TYPE);
} }
final List<String> missingAttrs = new ArrayList<>(); final List<String> missingAttrs = new ArrayList<>();
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) { if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
missingAttrs.add(LMS_SETUP.ATTR_LMS_TYPE); missingAttrs.add(LMS_SETUP.ATTR_LMS_TYPE);
} }
if (StringUtils.isBlank(this.lmsSetup.getLmsAuthName())) { if (StringUtils.isBlank(lmsSetup.getLmsAuthName())) {
missingAttrs.add(LMS_SETUP.ATTR_LMS_CLIENTNAME); missingAttrs.add(LMS_SETUP.ATTR_LMS_CLIENTNAME);
} }
if (StringUtils.isBlank(this.lmsSetup.getLmsAuthSecret())) { if (StringUtils.isBlank(lmsSetup.getLmsAuthSecret())) {
missingAttrs.add(LMS_SETUP.ATTR_LMS_CLIENTSECRET); missingAttrs.add(LMS_SETUP.ATTR_LMS_CLIENTSECRET);
} }
@ -104,7 +109,7 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
} }
// request OAuth2 access token on OpenEdx API // request OAuth2 access token on OpenEdx API
initRestTemplateAndRequestAccessToken(); initRestTemplateAndRequestAccessToken(lmsSetup);
if (this.restTemplate == null) { if (this.restTemplate == null) {
return LmsSetupTestResult.ofTokenRequestError( return LmsSetupTestResult.ofTokenRequestError(
"Failed to gain access token form OpenEdX Rest API: tried token endpoints: " + "Failed to gain access token form OpenEdX Rest API: tried token endpoints: " +
@ -124,14 +129,15 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
final int pageNumber, final int pageNumber,
final int pageSize) { final int pageSize) {
return Result.tryCatch(() -> { return this.lmsSetup()
initRestTemplateAndRequestAccessToken(); .flatMap(this::initRestTemplateAndRequestAccessToken)
.map(lmsSetup -> {
// TODO sort and pagination // TODO sort and pagination
final HttpHeaders httpHeaders = new HttpHeaders(); final HttpHeaders httpHeaders = new HttpHeaders();
final ResponseEntity<EdXPage> response = this.restTemplate.exchange( final ResponseEntity<EdXPage> response = this.restTemplate.exchange(
this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
HttpMethod.GET, HttpMethod.GET,
new HttpEntity<>(httpHeaders), new HttpEntity<>(httpHeaders),
EdXPage.class); EdXPage.class);
@ -142,7 +148,7 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
.reduce( .reduce(
new ArrayList<QuizData>(), new ArrayList<QuizData>(),
(list, courseData) -> { (list, courseData) -> {
list.add(quizDataOf(courseData)); list.add(quizDataOf(lmsSetup, courseData));
return list; return list;
}, },
(list1, list2) -> { (list1, list2) -> {
@ -166,14 +172,20 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
return null; return null;
} }
private void initRestTemplateAndRequestAccessToken() { @Override
public void reset() {
this.restTemplate = null;
}
log.info("Initialize Rest Template for OpenEdX API access. LmsSetup: {}", this.lmsSetup); private Result<LmsSetup> initRestTemplateAndRequestAccessToken(final LmsSetup lmsSetup) {
log.info("Initialize Rest Template for OpenEdX API access. LmsSetup: {}", lmsSetup);
return Result.tryCatch(() -> {
if (this.restTemplate != null) { if (this.restTemplate != null) {
try { try {
this.restTemplate.getAccessToken(); this.restTemplate.getAccessToken();
return; return lmsSetup;
} catch (final Exception e) { } catch (final Exception e) {
log.warn( log.warn(
"Error while trying to get access token within already existing OAuth2RestTemplate instance. Try to create new one.", "Error while trying to get access token within already existing OAuth2RestTemplate instance. Try to create new one.",
@ -182,29 +194,45 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
} }
} }
final Credentials credentials = this.lmsSetupDAO
.getLmsAPIAccessCredentials(this.lmsSetupId)
.getOrThrow();
final Iterator<String> tokenAccessPaths = this.knownTokenAccessPaths.iterator(); final Iterator<String> tokenAccessPaths = this.knownTokenAccessPaths.iterator();
while (this.restTemplate == null && tokenAccessPaths.hasNext()) { while (tokenAccessPaths.hasNext()) {
final String accessTokenRequestPath = tokenAccessPaths.next(); final String accessTokenRequestPath = tokenAccessPaths.next();
try { try {
final OAuth2RestTemplate template = createRestTemplate(accessTokenRequestPath);
final OAuth2RestTemplate template = createRestTemplate(
lmsSetup,
credentials,
accessTokenRequestPath);
final OAuth2AccessToken accessToken = template.getAccessToken(); final OAuth2AccessToken accessToken = template.getAccessToken();
if (accessToken != null) { if (accessToken != null) {
this.restTemplate = template; this.restTemplate = template;
storeAccessToken(accessToken); return lmsSetup;
} }
} catch (final Exception e) { } catch (final Exception e) {
log.info("Failed to request access token on access token request path: {}", accessTokenRequestPath, e); log.info("Failed to request access token on access token request path: {}", accessTokenRequestPath,
} e);
} }
} }
private OAuth2RestTemplate createRestTemplate(final String accessTokenRequestPath) { throw new IllegalArgumentException("Unable to establish OpenEdX API connection for lmsSetup: " + lmsSetup);
});
}
final String lmsAuthSecret = this.internalEncryptionService.decrypt(this.lmsSetup.lmsAuthSecret); private OAuth2RestTemplate createRestTemplate(
final LmsSetup lmsSetup,
final LmsSetupDAO.Credentials credentials,
final String accessTokenRequestPath) {
final String lmsAuthSecret = this.internalEncryptionService.decrypt(credentials.secret);
final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri(this.lmsSetup.lmsApiUrl + accessTokenRequestPath); details.setAccessTokenUri(lmsSetup.lmsApiUrl + accessTokenRequestPath);
details.setClientId(this.lmsSetup.lmsAuthName); details.setClientId(credentials.clientId);
details.setClientSecret(lmsAuthSecret); details.setClientSecret(lmsAuthSecret);
details.setGrantType("client_credentials"); details.setGrantType("client_credentials");
@ -214,51 +242,14 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
final OAuth2RestTemplate template = new OAuth2RestTemplate(details); final OAuth2RestTemplate template = new OAuth2RestTemplate(details);
template.setRequestFactory(this.clientHttpRequestFactory); template.setRequestFactory(this.clientHttpRequestFactory);
final OAuth2AccessToken previousAccessToken = loadPreviousAccessToken();
if (previousAccessToken != null) {
template.getOAuth2ClientContext().setAccessToken(previousAccessToken);
}
return template; return template;
} }
private void storeAccessToken(final OAuth2AccessToken accessToken) { private QuizData quizDataOf(
try { final LmsSetup lmsSetup,
final CourseData courseData) {
final String accessTokenString = accessToken.getValue(); final String startURI = lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX + courseData.id;
final TextEncryptor textEncryptor = Encryptors.text(this.lmsSetup.lmsAuthSecret, this.lmsSetup.lmsAuthName);
final String accessTokenEncrypted = textEncryptor.encrypt(accessTokenString);
// TODO store the accessTokenEncrypted to additional attributes of LmsSetup
} catch (final Exception e) {
log.warn("Failed to store access token for later use.", e);
}
}
private OAuth2AccessToken loadPreviousAccessToken() {
// TODO get the previous token from additional attributes of LmsSetup
final String prevTokenEncrypted = null;
if (StringUtils.isBlank(prevTokenEncrypted)) {
return null;
}
try {
final TextEncryptor textEncryptor = Encryptors.text(this.lmsSetup.lmsAuthSecret, this.lmsSetup.lmsAuthName);
final String prevTokenDecrypt = textEncryptor.decrypt(prevTokenEncrypted);
return new DefaultOAuth2AccessToken(prevTokenDecrypt);
} catch (final Exception e) {
log.warn("Failed to decrypt previous access-token.", e);
return null;
}
}
private QuizData quizDataOf(final CourseData courseData) {
final String startURI = this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX + courseData.id;
return new QuizData( return new QuizData(
courseData.id, courseData.id,
courseData.name, courseData.name,

View file

@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints; import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints;
import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityType; import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
@ -23,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; 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.LmsAPITemplate;
@ -51,6 +53,10 @@ public class QuizImportController {
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public Page<QuizData> search( public Page<QuizData> search(
@RequestParam(
name = Entity.FILTER_ATTR_INSTITUTION,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = LMS_SETUP.ATTR_ID, required = true) final Long lmsSetupId, @RequestParam(name = LMS_SETUP.ATTR_ID, required = true) final Long lmsSetupId,
@RequestParam(name = QuizData.FILTER_ATTR_NAME, required = false) final String nameLike, @RequestParam(name = QuizData.FILTER_ATTR_NAME, required = false) final String nameLike,
@RequestParam(name = QuizData.FILTER_ATTR_START_TIME, required = false) final String startTime, @RequestParam(name = QuizData.FILTER_ATTR_START_TIME, required = false) final String startTime,
@ -65,7 +71,7 @@ public class QuizImportController {
this.authorizationGrantService.checkPrivilege( this.authorizationGrantService.checkPrivilege(
EntityType.EXAM, EntityType.EXAM,
PrivilegeType.READ_ONLY, PrivilegeType.READ_ONLY,
lmsAPITemplate.lmsSetup().institutionId); institutionId);
return lmsAPITemplate.getQuizzes( return lmsAPITemplate.getQuizzes(
nameLike, nameLike,

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* 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;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.springframework.core.env.Environment;
public class InternalEncryptionServiceTest {
@Test
public void testEncryptSimpleSecret() {
final Environment envMock = mock(Environment.class);
when(envMock.getRequiredProperty(InternalEncryptionService.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
.thenReturn("secret1");
final InternalEncryptionService service = new InternalEncryptionService(envMock);
final String encrypt = service.encrypt("text1");
final String decrypt = service.decrypt(encrypt);
assertEquals("text1", decrypt);
}
}