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

View file

@ -1,113 +1,106 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice; package ch.ethz.seb.sebserver.webservice;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.SEBServerInit; import ch.ethz.seb.sebserver.SEBServerInit;
import ch.ethz.seb.sebserver.SEBServerInitEvent; import ch.ethz.seb.sebserver.SEBServerInitEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Component @Component
@WebServiceProfile @WebServiceProfile
@Import(DataSourceAutoConfiguration.class) @Import(DataSourceAutoConfiguration.class)
public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> { public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> {
private final SEBServerInit sebServerInit; private final SEBServerInit sebServerInit;
private final Environment environment; private final Environment environment;
private final WebserviceInfo webserviceInfo; private final WebserviceInfo webserviceInfo;
private final AdminUserInitializer adminUserInitializer; private final AdminUserInitializer adminUserInitializer;
private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
protected WebserviceInit( protected WebserviceInit(
final SEBServerInit sebServerInit, final SEBServerInit sebServerInit,
final Environment environment, final Environment environment,
final WebserviceInfo webserviceInfo, final WebserviceInfo webserviceInfo,
final AdminUserInitializer adminUserInitializer, final AdminUserInitializer adminUserInitializer,
final ApplicationEventPublisher applicationEventPublisher) { final ApplicationEventPublisher applicationEventPublisher) {
this.sebServerInit = sebServerInit; this.sebServerInit = sebServerInit;
this.environment = environment; this.environment = environment;
this.webserviceInfo = webserviceInfo; this.webserviceInfo = webserviceInfo;
this.adminUserInitializer = adminUserInitializer; this.adminUserInitializer = adminUserInitializer;
this.applicationEventPublisher = applicationEventPublisher; this.applicationEventPublisher = applicationEventPublisher;
} }
@Override @Override
public void onApplicationEvent(final ApplicationReadyEvent event) { public void onApplicationEvent(final ApplicationReadyEvent event) {
this.sebServerInit.init(); this.sebServerInit.init();
SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****"); SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****");
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Init Database with flyway..."); SEBServerInit.INIT_LOGGER.info("----> Intitialize Services...");
SEBServerInit.INIT_LOGGER.info("----> TODO "); SEBServerInit.INIT_LOGGER.info("----> ");
// TODO integration of Flyway for database initialization and migration: https://flywaydb.org this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this));
// see also https://flywaydb.org/getstarted/firststeps/api
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** ");
SEBServerInit.INIT_LOGGER.info("----> Intitialize Services..."); SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> *** Info:");
this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this)); try {
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** "); SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("---->"); SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> *** Info:"); SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName());
SEBServerInit.INIT_LOGGER.info("---->");
try { SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}",
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address")); InetAddress.getLoopbackAddress().getHostAddress());
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port")); SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}",
SEBServerInit.INIT_LOGGER.info("---->"); InetAddress.getLoopbackAddress().getHostName());
SEBServerInit.INIT_LOGGER.info("----> Local-Host address: {}", InetAddress.getLocalHost().getHostAddress()); } catch (final UnknownHostException e) {
SEBServerInit.INIT_LOGGER.info("----> Local-Host name: {}", InetAddress.getLocalHost().getHostName()); SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e);
SEBServerInit.INIT_LOGGER.info("---->"); }
SEBServerInit.INIT_LOGGER.info("----> Remote-Host address: {}",
InetAddress.getLoopbackAddress().getHostAddress()); SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Remote-Host name: {}", SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL());
InetAddress.getLoopbackAddress().getHostName()); SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}",
} catch (final UnknownHostException e) { this.webserviceInfo.getLmsExternalAddressAlias());
SEBServerInit.INIT_LOGGER.error("Unknown Host: ", e); SEBServerInit.INIT_LOGGER.info("---->");
} SEBServerInit.INIT_LOGGER.info("----> HTTP Scheme {}", this.webserviceInfo.getHttpScheme());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("---->"); SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());
SEBServerInit.INIT_LOGGER.info("----> External-Host URL: {}", this.webserviceInfo.getExternalServerURL());
SEBServerInit.INIT_LOGGER.info("----> LMS-External-Address-Alias: {}", // Create an initial admin account if requested and not already in the data-base
this.webserviceInfo.getLmsExternalAddressAlias()); this.adminUserInitializer.initAdminAccount();
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()); @PreDestroy
public void gracefulShutdown() {
// Create an initial admin account if requested and not already in the data-base SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****",
this.adminUserInitializer.initAdminAccount(); this.webserviceInfo.getHostAddress());
}
}
}
@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()") unless = "#result.hasError()")
Result<ClientDetails> getClientConfigDetails(String clientName); Result<ClientDetails> getClientConfigDetails(String clientName);
@CacheEvict( /** Internally used to check OAuth2 access for a active SebClientConfig.
cacheNames = EXAM_CLIENT_DETAILS_CACHE, *
allEntries = true) * @param config the SebClientConfig to check access
@EventListener(BulkActionEvent.class) * @return true if the system was able to gain an access token for the client. False otherwise
void flushClientConfigData(BulkActionEvent event); */
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.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; 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.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.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.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
@ -38,12 +35,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy; 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.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -51,6 +57,7 @@ import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.UUID; import java.util.UUID;
@ -316,19 +323,48 @@ public class ClientConfigServiceImpl implements ClientConfigService {
} }
@Override @Override
public void flushClientConfigData(final BulkActionEvent event) { public boolean checkAccess(SebClientConfig config) {
if(!config.isActive()) {
return false;
}
try { try {
final BulkAction bulkAction = event.getBulkAction(); RestTemplate restTemplate = new RestTemplate();
String externalServerURL = webserviceInfo.getExternalServerURL() +
API.OAUTH_TOKEN_ENDPOINT;
if (bulkAction.type == BulkActionType.DEACTIVATE || MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
bulkAction.type == BulkActionType.HARD_DELETE) { 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) headers.add(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
.forEach(this::flushClientConfigData); 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 (Exception e) {
} catch (final Exception e) { log.warn("Failed to check access for SebClientConfig: {} cause: {}", config, e.getMessage());
log.error("Unexpected error while trying to flush ClientConfig data ", e); return false;
} }
} }

View file

@ -1,109 +1,121 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Arrays;
import java.util.Collections; import java.util.Collection;
import java.util.List; import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component; 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.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
@Lazy
@Component @Lazy
@WebServiceProfile @Component
public class ClientIndicatorFactory { @WebServiceProfile
public class ClientIndicatorFactory {
private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class);
private static final Logger log = LoggerFactory.getLogger(ClientIndicatorFactory.class);
private final ApplicationContext applicationContext;
private final IndicatorDAO indicatorDAO; private final ApplicationContext applicationContext;
private final boolean enableCaching; private final IndicatorDAO indicatorDAO;
private final boolean enableCaching;
@Autowired
public ClientIndicatorFactory( @Autowired
final ApplicationContext applicationContext, public ClientIndicatorFactory(
final IndicatorDAO indicatorDAO, final ApplicationContext applicationContext,
@Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) { final IndicatorDAO indicatorDAO,
@Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) {
this.applicationContext = applicationContext;
this.indicatorDAO = indicatorDAO; this.applicationContext = applicationContext;
this.enableCaching = enableCaching; this.indicatorDAO = indicatorDAO;
} this.enableCaching = enableCaching;
}
public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>(); public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>();
if (clientConnection.examId == null) {
return result; if (clientConnection.examId == null) {
} return result;
}
try {
try {
final Collection<Indicator> examIndicators = this.indicatorDAO
.allForExam(clientConnection.examId) final Collection<Indicator> examIndicators = this.indicatorDAO
.getOrThrow(); .allForExam(clientConnection.examId)
.getOrThrow();
boolean pingIndicatorAvailable = false;
boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) {
try { for (final Indicator indicatorDef : examIndicators) {
try {
final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class); final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class);
if (!pingIndicatorAvailable) {
pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING; if (!pingIndicatorAvailable) {
} pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING;
}
indicator.init(
indicatorDef, indicator.init(
clientConnection.id, indicatorDef,
this.enableCaching); clientConnection.id,
this.enableCaching);
result.add(indicator);
} catch (final Exception e) { result.add(indicator);
log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type, } catch (final Exception e) {
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 there is no ping interval indicator set from the exam, we add a hidden one
if (!pingIndicatorAvailable) { // to at least create missing ping events and track missing state
final PingIntervalClientIndicator pingIndicator = this.applicationContext if (!pingIndicatorAvailable) {
.getBean(PingIntervalClientIndicator.class); final PingIntervalClientIndicator pingIndicator = this.applicationContext
pingIndicator.hidden = true; .getBean(PingIntervalClientIndicator.class);
result.add(pingIndicator); pingIndicator.hidden = true;
} final Indicator indicator = new Indicator(
null,
} catch (final RuntimeException e) { clientConnection.examId,
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); "hidden_ping_indicator",
throw e; IndicatorType.LAST_PING,
} catch (final Exception e) { "",
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); Arrays.asList(new Indicator.Threshold(5000d, "")));
} pingIndicator.init(
indicator,
return Collections.unmodifiableList(result); 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; long pingErrorThreshold;
boolean missingPing = false; boolean missingPing = false;
boolean hidden = false; boolean hidden = false;
public PingIntervalClientIndicator(final ClientEventExtensionMapper clientEventExtensionMapper) { 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -144,6 +145,15 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
.map(this::checkPasswordMatch); .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) { private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
Collection<APIMessage> errors = new ArrayList<>(); Collection<APIMessage> errors = new ArrayList<>();
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) { 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) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.weblayer.oauth; package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
public class DefaultTokenServicesFallback extends 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);
private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class);
@Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) @Override
throws AuthenticationException { public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication)
throws AuthenticationException {
try {
return super.createAccessToken(authentication); try {
} catch (final DuplicateKeyException e) { 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"); 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)) { final String clientName = authentication.getName();
if (StringUtils.isNotBlank(clientName)) {
// wait a second...
try { // wait some time...
Thread.sleep(1000); try {
} catch (final InterruptedException e1) { Thread.sleep(500);
log.warn("Failed to sleep: {}", e1.getMessage()); } catch (final InterruptedException e1) {
} log.warn("Failed to sleep: {}", e1.getMessage());
}
final OAuth2AccessToken accessToken = this.getAccessToken(authentication);
if (accessToken != null) { final OAuth2AccessToken accessToken = this.getAccessToken(authentication);
log.info("Found original accees token for client: {} token: {}", clientName, accessToken); if (accessToken != null) {
return accessToken; 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); // If no access token is available, propagate the original exception
throw e; log.error("Unable the handle DuplicateKeyException properly", e);
} throw e;
} }
}
}
}

View file

@ -1,71 +1,71 @@
server.address=localhost server.address=localhost
server.port=8090 server.port=8090
logging.file=log/sebserver.log logging.file=log/sebserver.log
# data source configuration # data source configuration
spring.datasource.initialize=true spring.datasource.initialize=true
spring.datasource.initialization-mode=always spring.datasource.initialization-mode=always
spring.datasource.url=jdbc:mariadb://localhost:3306/SEBServer?createDatabaseIfNotExist=true&verifyServerCertificate=false&useSSL=false&requireSSL=false 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.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.flyway.enabled=true spring.flyway.enabled=true
spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev spring.flyway.locations=classpath:config/sql/base,classpath:config/sql/dev
spring.flyway.baselineOnMigrate=true spring.flyway.baselineOnMigrate=true
spring.datasource.hikari.initializationFailTimeout=30000 spring.datasource.hikari.initializationFailTimeout=30000
spring.datasource.hikari.connectionTimeout=30000 spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.idleTimeout=600000 spring.datasource.hikari.idleTimeout=600000
spring.datasource.hikari.maxLifetime=1800000 spring.datasource.hikari.maxLifetime=1800000
sebserver.http.client.connect-timeout=15000 sebserver.http.client.connect-timeout=15000
sebserver.http.client.connection-request-timeout=10000 sebserver.http.client.connection-request-timeout=10000
sebserver.http.client.read-timeout=20000 sebserver.http.client.read-timeout=20000
# webservice configuration # webservice configuration
sebserver.init.adminaccount.gen-on-init=false sebserver.init.adminaccount.gen-on-init=false
sebserver.webservice.distributed=false sebserver.webservice.distributed=false
sebserver.webservice.http.scheme=http sebserver.webservice.http.scheme=http
sebserver.webservice.http.external.servername= sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port= sebserver.webservice.http.external.port=${server.port}
sebserver.webservice.http.redirect.gui=/gui sebserver.webservice.http.redirect.gui=/gui
sebserver.webservice.api.admin.endpoint=/admin-api/v1 sebserver.webservice.api.admin.endpoint=/admin-api/v1
sebserver.webservice.api.admin.accessTokenValiditySeconds=3600 sebserver.webservice.api.admin.accessTokenValiditySeconds=3600
sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1 sebserver.webservice.api.admin.refreshTokenValiditySeconds=-1
sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml
sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml
sebserver.webservice.api.exam.update-interval=1 * * * * * sebserver.webservice.api.exam.update-interval=1 * * * * *
sebserver.webservice.api.exam.time-prefix=0 sebserver.webservice.api.exam.time-prefix=0
sebserver.webservice.api.exam.time-suffix=0 sebserver.webservice.api.exam.time-suffix=0
sebserver.webservice.api.exam.endpoint=/exam-api sebserver.webservice.api.exam.endpoint=/exam-api
sebserver.webservice.api.exam.endpoint.discovery=${sebserver.webservice.api.exam.endpoint}/discovery 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.endpoint.v1=${sebserver.webservice.api.exam.endpoint}/v1
sebserver.webservice.api.exam.accessTokenValiditySeconds=3600 sebserver.webservice.api.exam.accessTokenValiditySeconds=3600
sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY
sebserver.webservice.api.exam.enable-indicator-cache=true sebserver.webservice.api.exam.enable-indicator-cache=true
sebserver.webservice.api.pagination.maxPageSize=500 sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints # 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.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths= sebserver.webservice.lms.moodle.api.token.request.paths=
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias 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 # 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. # 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 # 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. # 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 # 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 # 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. # 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 # 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. # 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 # 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. # 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 #sebserver.webservice.lms.openedx.seb.restriction.push-count=10
# actuator configuration # actuator configuration
management.server.port=${server.port} management.server.port=${server.port}
management.endpoints.web.base-path=/management management.endpoints.web.base-path=/management
management.endpoints.web.exposure.include=logfile,loggers,jolokia management.endpoints.web.exposure.include=logfile,loggers,jolokia
management.endpoints.web.path-mapping.jolokia=jmx management.endpoints.web.path-mapping.jolokia=jmx