diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 1806b13d..7f1a6a4b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java index eebfb41a..0e55f57c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -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 { - - 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 { + + 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()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java index 1a3f1a61..dda1369f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java @@ -60,10 +60,10 @@ public interface ClientConfigService { unless = "#result.hasError()") Result 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); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java index 7be036c9..f851615f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java @@ -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 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 entity = new HttpEntity<>( + "grant_type=client_credentials&scope=read write", + headers); + + ResponseEntity 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; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java index 122fef6d..3b5d71fc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java @@ -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 createFor(final ClientConnection clientConnection) { - final List result = new ArrayList<>(); - - if (clientConnection.examId == null) { - return result; - } - - try { - - final Collection 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 createFor(final ClientConnection clientConnection) { + final List result = new ArrayList<>(); + + if (clientConnection.examId == null) { + return result; + } + + try { + + final Collection 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); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingIntervalClientIndicator.java index 648e4354..1a584210 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingIntervalClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/PingIntervalClientIndicator.java @@ -38,7 +38,6 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { long pingErrorThreshold; boolean missingPing = false; - boolean hidden = false; public PingIntervalClientIndicator(final ClientEventExtensionMapper clientEventExtensionMapper) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SebClientConfigController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SebClientConfigController.java index bd73fe1b..129085e8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SebClientConfigController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SebClientConfigController.java @@ -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 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 errors = new ArrayList<>(); if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/DefaultTokenServicesFallback.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/DefaultTokenServicesFallback.java index 66b93c00..4165faeb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/DefaultTokenServicesFallback.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/DefaultTokenServicesFallback.java @@ -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; + } + } + +} diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index ebe238e2..5ec2c44f 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -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 \ No newline at end of file