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

@ -57,13 +57,6 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****"); 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("----> ");
SEBServerInit.INIT_LOGGER.info("----> Intitialize Services..."); SEBServerInit.INIT_LOGGER.info("----> Intitialize Services...");
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");

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) {
try { if(!config.isActive()) {
final BulkAction bulkAction = event.getBulkAction(); return false;
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) {
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
.forEach(this::flushClientConfigData);
} }
} catch (final Exception e) { try {
log.error("Unexpected error while trying to flush ClientConfig data ", e); RestTemplate restTemplate = new RestTemplate();
String externalServerURL = webserviceInfo.getExternalServerURL() +
API.OAUTH_TOKEN_ENDPOINT;
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());
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 (Exception e) {
log.warn("Failed to check access for SebClientConfig: {} cause: {}", config, e.getMessage());
return false;
} }
} }

View file

@ -9,6 +9,7 @@
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.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -93,6 +94,17 @@ public class ClientIndicatorFactory {
final PingIntervalClientIndicator pingIndicator = this.applicationContext final PingIntervalClientIndicator pingIndicator = this.applicationContext
.getBean(PingIntervalClientIndicator.class); .getBean(PingIntervalClientIndicator.class);
pingIndicator.hidden = true; 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); result.add(pingIndicator);
} }

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

@ -17,6 +17,7 @@ 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;
// TODO check if we can apply some caching here to get better performance for SEB client connection attempts
public class DefaultTokenServicesFallback extends DefaultTokenServices { public class DefaultTokenServicesFallback extends DefaultTokenServices {
private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class); private static final Logger log = LoggerFactory.getLogger(DefaultTokenServicesFallback.class);
@ -29,22 +30,22 @@ public class DefaultTokenServicesFallback extends DefaultTokenServices {
return super.createAccessToken(authentication); return super.createAccessToken(authentication);
} catch (final DuplicateKeyException e) { } catch (final DuplicateKeyException e) {
log.info( log.warn(
"Catched DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time"); "Caught DuplicateKeyException, try to handle it by trying to get the already stored access token after waited some time");
final String clientName = authentication.getName(); final String clientName = authentication.getName();
if (StringUtils.isNotBlank(clientName)) { if (StringUtils.isNotBlank(clientName)) {
// wait a second... // wait some time...
try { try {
Thread.sleep(1000); Thread.sleep(500);
} catch (final InterruptedException e1) { } catch (final InterruptedException e1) {
log.warn("Failed to sleep: {}", e1.getMessage()); log.warn("Failed to sleep: {}", e1.getMessage());
} }
final OAuth2AccessToken accessToken = this.getAccessToken(authentication); final OAuth2AccessToken accessToken = this.getAccessToken(authentication);
if (accessToken != null) { if (accessToken != null) {
log.info("Found original accees token for client: {} token: {}", clientName, accessToken); log.debug("Found original access token for client: {} ", clientName);
return accessToken; return accessToken;
} }
} }

View file

@ -25,7 +25,7 @@ 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