fixed missing ping indicator and try to create initial access token for SEB Client Config

This commit is contained in:
anhefti 2020-03-02 14:01:12 +01:00
parent 66b77f5737
commit a37ab31ff1
9 changed files with 425 additions and 377 deletions

View file

@ -460,7 +460,7 @@ public final class ClientConnectionTable {
}
void updateData(final TableItem tableItem) {
tableItem.setText(0, getConnectionIdentifer());
tableItem.setText(0, getConnectionIdentifier());
tableItem.setText(1, getConnectionAddress());
tableItem.setText(2, getStatusName());
}
@ -533,7 +533,7 @@ public final class ClientConnectionTable {
public int compareTo(final UpdatableTableItem other) {
return Comparator.comparingInt(UpdatableTableItem::statusWeight)
.thenComparingInt(UpdatableTableItem::thresholdsWeight)
.thenComparing(UpdatableTableItem::getConnectionIdentifer)
.thenComparing(UpdatableTableItem::getConnectionIdentifier)
.compare(this, other);
}
@ -580,7 +580,7 @@ public final class ClientConnectionTable {
return Constants.EMPTY_NOTE;
}
String getConnectionIdentifer() {
String getConnectionIdentifier() {
if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) {
return this.connectionData.clientConnection.userSessionId;
}
@ -608,10 +608,7 @@ public final class ClientConnectionTable {
final IndicatorData indicatorData =
ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
if (indicatorData == null) {
log.error("No IndicatorData of type: {} found", indicatorValue.getType());
} else {
if (indicatorData != null) {
final double value = indicatorValue.getValue();
final int indicatorWeight = IndicatorData.getWeight(indicatorData, value);
if (this.indicatorWeights[indicatorData.index] != indicatorWeight) {

View file

@ -1,113 +1,106 @@
/*
* Copyright (c) 2018 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;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.SEBServerInit;
import ch.ethz.seb.sebserver.SEBServerInitEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Component
@WebServiceProfile
@Import(DataSourceAutoConfiguration.class)
public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> {
private final SEBServerInit sebServerInit;
private final Environment environment;
private final WebserviceInfo webserviceInfo;
private final AdminUserInitializer adminUserInitializer;
private final ApplicationEventPublisher applicationEventPublisher;
protected WebserviceInit(
final SEBServerInit sebServerInit,
final Environment environment,
final WebserviceInfo webserviceInfo,
final AdminUserInitializer adminUserInitializer,
final ApplicationEventPublisher applicationEventPublisher) {
this.sebServerInit = sebServerInit;
this.environment = environment;
this.webserviceInfo = webserviceInfo;
this.adminUserInitializer = adminUserInitializer;
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
this.sebServerInit.init();
SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****");
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Init Database with flyway...");
SEBServerInit.INIT_LOGGER.info("----> TODO ");
// TODO integration of Flyway for database initialization and migration: https://flywaydb.org
// see also https://flywaydb.org/getstarted/firststeps/api
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Intitialize Services...");
SEBServerInit.INIT_LOGGER.info("----> ");
this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this));
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** ");
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> *** Info:");
try {
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}",
InetAddress.getLoopbackAddress().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}",
InetAddress.getLoopbackAddress().getHostName());
} catch (final UnknownHostException e) {
SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e);
}
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL());
SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}",
this.webserviceInfo.getLmsExternalAddressAlias());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
}
@PreDestroy
public void gracefulShutdown() {
SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****",
this.webserviceInfo.getHostAddress());
}
}
/*
* Copyright (c) 2018 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;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.SEBServerInit;
import ch.ethz.seb.sebserver.SEBServerInitEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Component
@WebServiceProfile
@Import(DataSourceAutoConfiguration.class)
public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> {
private final SEBServerInit sebServerInit;
private final Environment environment;
private final WebserviceInfo webserviceInfo;
private final AdminUserInitializer adminUserInitializer;
private final ApplicationEventPublisher applicationEventPublisher;
protected WebserviceInit(
final SEBServerInit sebServerInit,
final Environment environment,
final WebserviceInfo webserviceInfo,
final AdminUserInitializer adminUserInitializer,
final ApplicationEventPublisher applicationEventPublisher) {
this.sebServerInit = sebServerInit;
this.environment = environment;
this.webserviceInfo = webserviceInfo;
this.adminUserInitializer = adminUserInitializer;
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
this.sebServerInit.init();
SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****");
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Intitialize Services...");
SEBServerInit.INIT_LOGGER.info("----> ");
this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this));
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** ");
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> *** Info:");
try {
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}",
InetAddress.getLoopbackAddress().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}",
InetAddress.getLoopbackAddress().getHostName());
} catch (final UnknownHostException e) {
SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e);
}
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL());
SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}",
this.webserviceInfo.getLmsExternalAddressAlias());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
}
@PreDestroy
public void gracefulShutdown() {
SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****",
this.webserviceInfo.getHostAddress());
}
}

View file

@ -60,10 +60,10 @@ public interface ClientConfigService {
unless = "#result.hasError()")
Result<ClientDetails> getClientConfigDetails(String clientName);
@CacheEvict(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
@EventListener(BulkActionEvent.class)
void flushClientConfigData(BulkActionEvent event);
/** Internally used to check OAuth2 access for a active SebClientConfig.
*
* @param config the SebClientConfig to check access
* @return true if the system was able to gain an access token for the client. False otherwise
*/
boolean checkAccess(SebClientConfig config);
}

View file

@ -10,8 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import ch.ethz.seb.sebserver.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
@ -19,8 +18,6 @@ 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.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
@ -38,12 +35,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.io.InputStream;
@ -51,6 +57,7 @@ import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
@ -316,19 +323,48 @@ public class ClientConfigServiceImpl implements ClientConfigService {
}
@Override
public void flushClientConfigData(final BulkActionEvent event) {
public boolean checkAccess(SebClientConfig config) {
if(!config.isActive()) {
return false;
}
try {
final BulkAction bulkAction = event.getBulkAction();
RestTemplate restTemplate = new RestTemplate();
String externalServerURL = webserviceInfo.getExternalServerURL() +
API.OAUTH_TOKEN_ENDPOINT;
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
ClientCredentials credentials = sebClientConfigDAO
.getSebClientCredentials(config.getModelId())
.getOrThrow();
CharSequence plainClientSecret = clientCredentialService.getPlainClientSecret(credentials);
String basicAuth = credentials.clientId +
String.valueOf(Constants.COLON) +
plainClientSecret;
String encoded = Base64.getEncoder()
.encodeToString(basicAuth.getBytes());
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
.forEach(this::flushClientConfigData);
headers.add(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
HttpEntity<String> entity = new HttpEntity<>(
"grant_type=client_credentials&scope=read write",
headers);
ResponseEntity<String> exchange = restTemplate.exchange(
externalServerURL,
HttpMethod.POST,
entity,
String.class);
if (exchange.getStatusCode().value() == HttpStatus.OK.value()) {
return true;
} else {
log.warn("Failed to check access SebClientConfig {} response: {}", config, exchange.getStatusCode());
return false;
}
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data ", e);
} catch (Exception e) {
log.warn("Failed to check access for SebClientConfig: {} cause: {}", config, e.getMessage());
return false;
}
}

View file

@ -1,109 +1,121 @@
/*
* Copyright (c) 2018 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.session.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
@Lazy
@Component
@WebServiceProfile
public class ClientIndicatorFactory {
private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class);
private final ApplicationContext applicationContext;
private final IndicatorDAO indicatorDAO;
private final boolean enableCaching;
@Autowired
public ClientIndicatorFactory(
final ApplicationContext applicationContext,
final IndicatorDAO indicatorDAO,
@Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) {
this.applicationContext = applicationContext;
this.indicatorDAO = indicatorDAO;
this.enableCaching = enableCaching;
}
public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>();
if (clientConnection.examId == null) {
return result;
}
try {
final Collection<Indicator> examIndicators = this.indicatorDAO
.allForExam(clientConnection.examId)
.getOrThrow();
boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) {
try {
final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class);
if (!pingIndicatorAvailable) {
pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING;
}
indicator.init(
indicatorDef,
clientConnection.id,
this.enableCaching);
result.add(indicator);
} catch (final Exception e) {
log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type,
e);
}
}
// If there is no ping interval indicator set from the exam, we add a hidden one
// to at least create missing ping events and track missing state
if (!pingIndicatorAvailable) {
final PingIntervalClientIndicator pingIndicator = this.applicationContext
.getBean(PingIntervalClientIndicator.class);
pingIndicator.hidden = true;
result.add(pingIndicator);
}
} catch (final RuntimeException e) {
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection);
throw e;
} catch (final Exception e) {
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection);
}
return Collections.unmodifiableList(result);
}
}
/*
* Copyright (c) 2018 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.session.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
@Lazy
@Component
@WebServiceProfile
public class ClientIndicatorFactory {
private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class);
private final ApplicationContext applicationContext;
private final IndicatorDAO indicatorDAO;
private final boolean enableCaching;
@Autowired
public ClientIndicatorFactory(
final ApplicationContext applicationContext,
final IndicatorDAO indicatorDAO,
@Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) {
this.applicationContext = applicationContext;
this.indicatorDAO = indicatorDAO;
this.enableCaching = enableCaching;
}
public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>();
if (clientConnection.examId == null) {
return result;
}
try {
final Collection<Indicator> examIndicators = this.indicatorDAO
.allForExam(clientConnection.examId)
.getOrThrow();
boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) {
try {
final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class);
if (!pingIndicatorAvailable) {
pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING;
}
indicator.init(
indicatorDef,
clientConnection.id,
this.enableCaching);
result.add(indicator);
} catch (final Exception e) {
log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type,
e);
}
}
// If there is no ping interval indicator set from the exam, we add a hidden one
// to at least create missing ping events and track missing state
if (!pingIndicatorAvailable) {
final PingIntervalClientIndicator pingIndicator = this.applicationContext
.getBean(PingIntervalClientIndicator.class);
pingIndicator.hidden = true;
final Indicator indicator = new Indicator(
null,
clientConnection.examId,
"hidden_ping_indicator",
IndicatorType.LAST_PING,
"",
Arrays.asList(new Indicator.Threshold(5000d, "")));
pingIndicator.init(
indicator,
clientConnection.id,
this.enableCaching);
result.add(pingIndicator);
}
} catch (final RuntimeException e) {
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection);
throw e;
} catch (final Exception e) {
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection);
}
return Collections.unmodifiableList(result);
}
}

View file

@ -38,7 +38,6 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
long pingErrorThreshold;
boolean missingPing = false;
boolean hidden = false;
public PingIntervalClientIndicator(final ClientEventExtensionMapper clientEventExtensionMapper) {

View file

@ -39,6 +39,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
@ -144,6 +145,15 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
.map(this::checkPasswordMatch);
}
@Override
protected Result<SebClientConfig> notifySaved(SebClientConfig entity) {
if (entity.isActive()) {
// try to get access token for SEB client
sebClientConfigService.checkAccess(entity);
}
return super.notifySaved(entity);
}
private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
Collection<APIMessage> errors = new ArrayList<>();
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) {

View file

@ -1,58 +1,59 @@
/*
* 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.weblayer.oauth;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
public class DefaultTokenServicesFallback extends DefaultTokenServices {
private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class);
@Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication)
throws AuthenticationException {
try {
return super.createAccessToken(authentication);
} catch (final DuplicateKeyException e) {
log.info(
"Catched DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time");
final String clientName = authentication.getName();
if (StringUtils.isNotBlank(clientName)) {
// wait a second...
try {
Thread.sleep(1000);
} catch (final InterruptedException e1) {
log.warn("Failed to sleep: {}", e1.getMessage());
}
final OAuth2AccessToken accessToken = this.getAccessToken(authentication);
if (accessToken != null) {
log.info("Found original accees token for client: {} token: {}", clientName, accessToken);
return accessToken;
}
}
// If no access token is available, propagate the original exception
log.error("Unable the handle DuplicateKeyException properly", e);
throw e;
}
}
}
/*
* 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.weblayer.oauth;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
// TODO check if we can apply some caching here to get better performance for SEB client connection attempts
public class DefaultTokenServicesFallback extends DefaultTokenServices {
private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class);
@Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication)
throws AuthenticationException {
try {
return super.createAccessToken(authentication);
} catch (final DuplicateKeyException e) {
log.warn(
"Caught DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time");
final String clientName = authentication.getName();
if (StringUtils.isNotBlank(clientName)) {
// wait some time...
try {
Thread.sleep(500);
} catch (final InterruptedException e1) {
log.warn("Failed to sleep: {}", e1.getMessage());
}
final OAuth2AccessToken accessToken = this.getAccessToken(authentication);
if (accessToken != null) {
log.debug("Found original access token for client: {} ", clientName);
return accessToken;
}
}
// If no access token is available, propagate the original exception
log.error("Unable the handle DuplicateKeyException properly", e);
throw e;
}
}
}

View file

@ -1,71 +1,71 @@
server.address=localhost
server.port=8090
logging.file=log/sebserver.log
# data source configuration
spring.datasource.initialize=true
spring.datasource.initialization-mode=always
spring.datasource.url=jdbc:mariadb://localhost:3306/SEBServer?createDatabaseIfNotExist=true&verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.flyway.enabled=true
spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev
spring.flyway.baselineOnMigrate=true
spring.datasource.hikari.initializationFailTimeout=30000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.idleTimeout=600000
spring.datasource.hikari.maxLifetime=1800000
sebserver.http.client.connect-timeout=15000
sebserver.http.client.connection-request-timeout=10000
sebserver.http.client.read-timeout=20000
# webservice configuration
sebserver.init.adminaccount.gen-on-init=false
sebserver.webservice.distributed=false
sebserver.webservice.http.scheme=http
sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port=
sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.api.admin.endpoint=/admin-api/v1
sebserver.webservice.api.admin.accessTokenValiditySeconds=3600
sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1
sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml
sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml
sebserver.webservice.api.exam.update-interval=1 * * * * *
sebserver.webservice.api.exam.time-prefix=0
sebserver.webservice.api.exam.time-suffix=0
sebserver.webservice.api.exam.endpoint=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery
sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
sebserver.webservice.api.exam.accessTokenValiditySeconds=3600
sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY
sebserver.webservice.api.exam.enable-indicator-cache=true
sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths=
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
# NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to
# apply on load-balanced infrastructure or infrastructure that has several layers of cache.
# The reason for this is that the API (Open edX system) internally don't apply a resource-change that is
# done within HTTP API call immediately from an outside perspective.
# After a resource-change on the API is done, the system toggles between the old and the new resource
# while constantly calling GET. This usually happens for about a minute or two then it stabilizes on the new resource
#
# This may source on load-balancing or internally caching on Open edX side.
# To mitigate this effect the SEB Server can be configured to apply a resource-change on the
# API several times in a row to flush as match caches and reach as match as possible server instances.
#
# Since this is a brute-force method to mitigate the problem, this should only be a temporary
# work-around until a better solution on Open edX SEB integration side has been found and applied.
#sebserver.webservice.lms.openedx.seb.restriction.push-count=10
# actuator configuration
management.server.port=${server.port}
management.endpoints.web.base-path=/management
management.endpoints.web.exposure.include=logfile,loggers,jolokia
server.address=localhost
server.port=8090
logging.file=log/sebserver.log
# data source configuration
spring.datasource.initialize=true
spring.datasource.initialization-mode=always
spring.datasource.url=jdbc:mariadb://localhost:3306/SEBServer?createDatabaseIfNotExist=true&verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.flyway.enabled=true
spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev
spring.flyway.baselineOnMigrate=true
spring.datasource.hikari.initializationFailTimeout=30000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.idleTimeout=600000
spring.datasource.hikari.maxLifetime=1800000
sebserver.http.client.connect-timeout=15000
sebserver.http.client.connection-request-timeout=10000
sebserver.http.client.read-timeout=20000
# webservice configuration
sebserver.init.adminaccount.gen-on-init=false
sebserver.webservice.distributed=false
sebserver.webservice.http.scheme=http
sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port=${server.port}
sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.api.admin.endpoint=/admin-api/v1
sebserver.webservice.api.admin.accessTokenValiditySeconds=3600
sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1
sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml
sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml
sebserver.webservice.api.exam.update-interval=1 * * * * *
sebserver.webservice.api.exam.time-prefix=0
sebserver.webservice.api.exam.time-suffix=0
sebserver.webservice.api.exam.endpoint=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery
sebserver.webservice.api.exam.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
sebserver.webservice.api.exam.accessTokenValiditySeconds=3600
sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY
sebserver.webservice.api.exam.enable-indicator-cache=true
sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths=
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
# NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to
# apply on load-balanced infrastructure or infrastructure that has several layers of cache.
# The reason for this is that the API (Open edX system) internally don't apply a resource-change that is
# done within HTTP API call immediately from an outside perspective.
# After a resource-change on the API is done, the system toggles between the old and the new resource
# while constantly calling GET. This usually happens for about a minute or two then it stabilizes on the new resource
#
# This may source on load-balancing or internally caching on Open edX side.
# To mitigate this effect the SEB Server can be configured to apply a resource-change on the
# API several times in a row to flush as match caches and reach as match as possible server instances.
#
# Since this is a brute-force method to mitigate the problem, this should only be a temporary
# work-around until a better solution on Open edX SEB integration side has been found and applied.
#sebserver.webservice.lms.openedx.seb.restriction.push-count=10
# actuator configuration
management.server.port=${server.port}
management.endpoints.web.base-path=/management
management.endpoints.web.exposure.include=logfile,loggers,jolokia
management.endpoints.web.path-mapping.jolokia=jmx