From 16d18a53e60c0965dd99f26afb22900c26843b62 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 11 Jul 2019 17:03:30 +0200 Subject: [PATCH] SEBSERV-62 bug-fixes, improvements --- .../ethz/seb/sebserver/WebSecurityConfig.java | 22 +-- .../ch/ethz/seb/sebserver/gbl/api/API.java | 5 +- .../model/session/ClientConnectionData.java | 11 +- .../sebserver/gui/GuiWebsecurityConfig.java | 4 + .../gui/service/page/impl/PageUtils.java | 5 +- .../OAuth2AuthorizationContextHolder.java | 13 +- .../session/ClientConnectionTable.java | 36 ++-- .../servicelayer/dao/ClientConnectionDAO.java | 23 ++- .../servicelayer/dao/SebClientConfigDAO.java | 47 ++++- .../dao/impl/SebClientConfigDAOImpl.java | 26 ++- .../sebconfig/SebClientConfigService.java | 8 + .../sebconfig/impl/ExamConfigIO.java | 8 + .../impl/SebClientConfigServiceImpl.java | 9 +- .../impl/ClientConnectionDataInternal.java | 3 +- .../session/impl/ClientIndicatorFactory.java | 2 +- .../impl/SebClientConnectionServiceImpl.java | 4 +- .../weblayer/api/ExamAPI_V1_Controller.java | 2 +- .../oauth/AuthorizationServerConfig.java | 2 + .../weblayer/oauth/RevokeTokenEndpoint.java | 3 +- .../oauth/SynchronizedTokenGranter.java | 83 +++++++++ .../config/application-dev-ws.properties | 2 +- src/main/resources/logback-spring.xml | 4 +- src/main/resources/schema-dev.sql | 4 +- .../ch/ethz/seb/sebserver/HTTPClientBot.java | 170 +++++++++--------- .../api/exam/ExamAPIIntegrationTester.java | 2 + src/test/resources/logback.xml | 26 +++ 26 files changed, 365 insertions(+), 159 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SynchronizedTokenGranter.java create mode 100644 src/test/resources/logback.xml diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index 35a2a280..4704eb5a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -36,13 +36,10 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; import org.springframework.util.ResourceUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -109,21 +106,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E .and(); } - @Override - public void configure(final HttpSecurity http) throws Exception { - http - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .antMatcher("/**") - .authorizeRequests() - .anyRequest() - .permitAll() - .and() - .exceptionHandling() - .accessDeniedHandler(new OAuth2AccessDeniedHandler()); - } - @RequestMapping("/error") public void handleError(final HttpServletResponse response) throws IOException { //response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); @@ -147,7 +129,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E log.info("Initialize with insecure ClientHttpRequestFactory for development"); - return new DevClientHttpRequestFactory(); + final DevClientHttpRequestFactory devClientHttpRequestFactory = new DevClientHttpRequestFactory(); + devClientHttpRequestFactory.setOutputStreaming(false); + return devClientHttpRequestFactory; } /** A ClientHttpRequestFactory used in production with TSL SSL configuration. diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 7d6eb0e3..6793bb4d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -28,8 +28,9 @@ public final class API { public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}"; public static final String MODEL_ID_VAR_PATH_SEGMENT = "/{" + PARAM_MODEL_ID + "}"; - public static final String OAUTH_TOKEN_ENDPOINT = "/oauth/token"; // TODO to config properties? - public static final String OAUTH_REVOKE_TOKEN_ENDPOINT = "/oauth/revoke-token"; // TODO to config properties? + public static final String OAUTH_ENDPOINT = "/oauth"; + public static final String OAUTH_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/token"; // TODO to config properties? + public static final String OAUTH_REVOKE_TOKEN_ENDPOINT = OAUTH_ENDPOINT + "/revoke-token"; // TODO to config properties? public static final String CURRENT_USER_ENDPOINT = API.USER_ACCOUNT_ENDPOINT + "/me"; public static final String INFO_ENDPOINT = "/info"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java index 85853694..4460347f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.gbl.model.session; import java.util.Collection; +import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -24,7 +25,15 @@ public class ClientConnectionData { @JsonCreator protected ClientConnectionData( @JsonProperty("clientConnection") final ClientConnection clientConnection, - @JsonProperty("indicatorValues") final Collection indicatorValues) { + @JsonProperty("indicatorValues") final Collection indicatorValues) { + + this.clientConnection = clientConnection; + this.indicatorValues = indicatorValues; + } + + protected ClientConnectionData( + @JsonProperty("clientConnection") final ClientConnection clientConnection, + @JsonProperty("indicatorValues") final List indicatorValues) { this.clientConnection = clientConnection; this.indicatorValues = indicatorValues; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java index 018a7a61..06cc1ead 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java @@ -19,6 +19,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @Configuration @@ -31,6 +32,9 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter { /** Gui-service related public URLS from spring web security perspective */ public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( + // OAuth entry-points + new AntPathRequestMatcher(API.OAUTH_REVOKE_TOKEN_ENDPOINT), + // GUI entry-point new AntPathRequestMatcher("/gui"), // RAP/RWT resources has to be accessible new AntPathRequestMatcher("/rwt-resources/**"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageUtils.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageUtils.java index 063f8351..4cc77731 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageUtils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageUtils.java @@ -51,8 +51,7 @@ public final class PageUtils { CallType.GET_DEPENDENCIES); if (builder == null) { - log.error("No RestCall builder found for entity type: {}", entity.entityType()); - return null; + throw new RuntimeException("No RestCall builder found for entity type: " + entity.entityType()); } final Set dependencies = builder @@ -67,7 +66,7 @@ public final class PageUtils { return new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies"); } } catch (final Exception e) { - log.error("Failed to get dependencyies for Entity: {}", entity, e); + log.warn("Failed to get dependencyies for Entity: {}", entity, e); return new LocTextKey("sebserver.dialog.confirm.deactivation", ""); } }; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java index a7fc62fc..ef7916de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java @@ -35,7 +35,6 @@ import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler; import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; -import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; @@ -114,17 +113,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol public DisposableOAuth2RestTemplate(final OAuth2ProtectedResourceDetails resource) { super( resource, - new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()) { - - private static final long serialVersionUID = 3921115327670719271L; - - @Override - public AccessTokenRequest getAccessTokenRequest() { - final AccessTokenRequest accessTokenRequest = super.getAccessTokenRequest(); - accessTokenRequest.set("Institution", "testInstitution"); - return accessTokenRequest; - } - }); + new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest())); } @Override 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 4a32ddca..b3beda3e 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 @@ -129,17 +129,17 @@ public final class ClientConnectionTable { for (final UpdatableTableItem uti : this.tableMapping.values()) { if (uti.tableItem == null) { createTableItem(uti); - updateIndicatorValues(uti); + updateIndicatorValues(uti, false); updateConnectionStatusColor(uti); } else { - if (!uti.connectionData.clientConnection.status + if (uti.previous_connectionData == null || !uti.connectionData.clientConnection.status .equals(uti.previous_connectionData.clientConnection.status)) { uti.tableItem.setText(0, uti.getConnectionIdentifer()); - uti.tableItem.setText(1, uti.getStatusName()); + uti.tableItem.setText(2, uti.getStatusName()); updateConnectionStatusColor(uti); } if (uti.hasStatus(ConnectionStatus.ESTABLISHED)) { - updateIndicatorValues(uti); + updateIndicatorValues(uti, true); } } uti.tableItem.getDisplay(); @@ -163,20 +163,22 @@ public final class ClientConnectionTable { column.setWidth(columnWidth); } this.table.layout(true, true); - //this.table.pack(); this.tableWidth = area.width; } } - private void updateIndicatorValues(final UpdatableTableItem uti) { - + private void updateIndicatorValues(final UpdatableTableItem uti, final boolean established) { for (final IndicatorValue iv : uti.connectionData.indicatorValues) { final IndicatorData indicatorData = this.indicatorMapping.get(iv.getType()); if (indicatorData != null) { - uti.tableItem.setText(indicatorData.index, String.valueOf(iv.getValue())); - uti.tableItem.setBackground( - indicatorData.index, - this.getColorForValue(indicatorData, iv.getValue())); + if (!established && iv.getType() == IndicatorType.LAST_PING) { + uti.tableItem.setText(indicatorData.index, "--"); + } else { + uti.tableItem.setText(indicatorData.index, String.valueOf(iv.getValue())); + uti.tableItem.setBackground( + indicatorData.index, + this.getColorForValue(indicatorData, iv.getValue())); + } } } } @@ -184,15 +186,15 @@ public final class ClientConnectionTable { private void updateConnectionStatusColor(final UpdatableTableItem uti) { switch (uti.connectionData.clientConnection.status) { case ESTABLISHED: { - uti.tableItem.setBackground(1, this.color1); + uti.tableItem.setBackground(2, this.color1); break; } case ABORTED: { - uti.tableItem.setBackground(1, this.color3); + uti.tableItem.setBackground(2, this.color3); break; } default: { - uti.tableItem.setBackground(1, this.color2); + uti.tableItem.setBackground(2, this.color2); } } } @@ -200,12 +202,12 @@ public final class ClientConnectionTable { private Color getColorForValue(final IndicatorData indicatorData, final double value) { for (int i = 0; i < indicatorData.thresholdColor.length; i++) { - if (value >= indicatorData.thresholdColor[i].value) { + if (value < indicatorData.thresholdColor[i].value) { return indicatorData.thresholdColor[i].color; } } - return this.color1; + return indicatorData.thresholdColor[indicatorData.thresholdColor.length - 1].color; } private static final class UpdatableTableItem { @@ -239,7 +241,7 @@ public final class ClientConnectionTable { return this.connectionData.clientConnection.userSessionId; } - return "- " + this.connectionId + " -"; + return "--"; } public boolean hasStatus(final ConnectionStatus status) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java index 123f8260..fed9b744 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ClientConnectionDAO.java @@ -9,24 +9,39 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import java.util.Collection; +import java.util.Set; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; + +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.util.Result; public interface ClientConnectionDAO extends EntityDAO { + public static final String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE"; + /** Get a list of all connection tokens of all connections (no matter what state) * of an exam. * * @param examId The exam identifier * @return list of all connection tokens of all connections (no matter what state) * of an exam */ + @Cacheable( + cacheNames = CONNECTION_TOKENS_CACHE, + key = "#examId", + unless = "#result.hasError()") Result> getConnectionTokens(Long examId); - /** Get a ClientConnection for a specified token. - * - * @param connectionToken the connection token - * @return Result refer to ClientConnection or refer to a error if happened */ + @Override + @CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true) + Result createNew(ClientConnection data); + + @Override + @CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true) + Result> delete(Set all); + Result byConnectionToken(String connectionToken); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/SebClientConfigDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/SebClientConfigDAO.java index 04c1cc5c..0c535b3e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/SebClientConfigDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/SebClientConfigDAO.java @@ -8,10 +8,18 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; +import java.util.Collection; +import java.util.Set; + +import org.springframework.cache.annotation.CacheEvict; + +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebClientConfigService; /** Concrete EntityDAO interface of SebClientConfig entities */ public interface SebClientConfigDAO extends @@ -20,9 +28,9 @@ public interface SebClientConfigDAO extends /** Get a SebClientConfig by specified client identifier * - * @param clientId the client identifier + * @param clientName the client name * @return Result refer to the SebClientConfig for client or refer to an error if happened */ - Result byClientId(String clientId); + Result byClientName(String clientName); /** Get the configured ClientCredentials for a given SebClientConfig. * The ClientCredentials are still encoded as they are on DB storage @@ -38,4 +46,39 @@ public interface SebClientConfigDAO extends * @return encrypted configuration password */ Result getConfigPasswortCipher(String modelId); + /** Get the stored encrypted configuration password from a specified SEB client configuration. + * The SEB client configuration password is used to encrypt a SEB Client Configuration. + * + * The SEB client configuration must be active otherwise a error is returned + * + * @param clientName the client name + * @return encrypted configuration password */ + Result getConfigPasswortCipherByClientName(String clientName); + + @Override + @CacheEvict( + cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE, + allEntries = true) + Result save(SebClientConfig data); + + @Override + @CacheEvict( + cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE, + allEntries = true) + Result> delete(Set all); + + @Override + @CacheEvict( + cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE, + allEntries = true) + Result> setActive(Set all, boolean active); + + @Override + @CacheEvict( + cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE, + allEntries = true) + default Collection> processBulkAction(final BulkAction bulkAction) { + return BulkActionSupportDAO.super.processBulkAction(bulkAction); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SebClientConfigDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SebClientConfigDAOImpl.java index 6fb0d1b5..b88cc5a9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SebClientConfigDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SebClientConfigDAOImpl.java @@ -133,14 +133,14 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO { } @Override - public Result byClientId(final String clientId) { + public Result byClientName(final String clientName) { return Result.tryCatch(() -> { return this.sebClientConfigRecordMapper .selectByExample() .where( SebClientConfigRecordDynamicSqlSupport.clientName, - isEqualTo(clientId)) + isEqualTo(clientName)) .build() .execute() .stream() @@ -150,6 +150,28 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO { }); } + @Override + @Transactional(readOnly = true) + public Result getConfigPasswortCipherByClientName(final String clientName) { + return Result.tryCatch(() -> { + + final SebClientConfigRecord record = this.sebClientConfigRecordMapper + .selectByExample() + .where( + SebClientConfigRecordDynamicSqlSupport.clientName, + isEqualTo(clientName)) + .and( + SebClientConfigRecordDynamicSqlSupport.active, + isNotEqualTo(0)) + .build() + .execute() + .stream() + .collect(Utils.toSingleton()); + + return record.getClientSecret(); + }); + } + @Override @Transactional(readOnly = true) public boolean isActive(final String modelId) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java index 993ca1e7..fcf09cc0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/SebClientConfigService.java @@ -10,11 +10,15 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig; import java.io.OutputStream; +import org.springframework.cache.annotation.Cacheable; + import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.util.Result; public interface SebClientConfigService { + public static final String CLIENT_CONFIG_BY_CLIENT_ID_CHACHE = "CLIENT_CONFIG_BY_CLIENT_ID_CHACHE"; + static String SEB_CLIENT_CONFIG_EXAMPLE_XML = " \r\n" + " sebMode\r\n" + @@ -73,6 +77,10 @@ public interface SebClientConfigService { * * @param clientId the clientId/clientName * @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */ + @Cacheable( + cacheNames = CLIENT_CONFIG_BY_CLIENT_ID_CHACHE, + key = "#clientId", + unless = "#result.hasError()") Result getEncodedClientSecret(String clientId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java index 7da5e01d..8a09f770 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java @@ -71,6 +71,10 @@ public class ExamConfigIO { final Long institutionId, final Long configurationNodeId) { + if (log.isDebugEnabled()) { + log.debug("Start export SEB plain XML configuration asynconously"); + } + // get all defined root configuration attributes final Map attributes = this.configurationAttributeDAO.getAllRootAttributes() .getOrThrow() @@ -124,6 +128,10 @@ public class ExamConfigIO { out.write(XML_PLIST_END_UTF_8); out.flush(); + if (log.isDebugEnabled()) { + log.debug("Finished export SEB plain XML configuration asynconously"); + } + } catch (final Exception e) { log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e); try { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java index 58224da7..2a290752 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/SebClientConfigServiceImpl.java @@ -130,13 +130,8 @@ public class SebClientConfigServiceImpl implements SebClientConfigService { @Override public Result getEncodedClientSecret(final String clientId) { - return this.sebClientConfigDAO.byClientId(clientId) - .flatMap(this::getEncodedSecret); - } - - private Result getEncodedSecret(final SebClientConfig clientConfig) { - return this.sebClientConfigDAO.getSebClientCredentials(clientConfig.getModelId()) - .map(cc -> this.clientPasswordEncoder.encode(this.clientCredentialService.getPlainClientSecret(cc))); + return this.sebClientConfigDAO.getConfigPasswortCipherByClientName(clientId) + .map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher))); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index cc555f7d..1c896707 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; +import java.util.List; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; @@ -25,7 +26,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData { protected ClientConnectionDataInternal( final ClientConnection clientConnection, - final Collection clientIndicators) { + final List clientIndicators) { super(clientConnection, clientIndicators); 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 9a004755..369a2f6b 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 @@ -49,7 +49,7 @@ public class ClientIndicatorFactory { this.enableCaching = enableCaching; } - public Collection createFor(final ClientConnection clientConnection) { + public List createFor(final ClientConnection clientConnection) { final List result = new ArrayList<>(); if (clientConnection.examId == null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java index 28ff2bbf..5218aaa9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebClientConnectionServiceImpl.java @@ -236,7 +236,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic clientConnection); // Exam integrity - if (clientConnection.examId != null && !examId.equals(clientConnection.examId)) { + if (clientConnection.examId != null && examId != null && !examId.equals(clientConnection.examId)) { log.error("Exam integrity violation with examId: {} on clientConnection: {}", examId, clientConnection); @@ -251,7 +251,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic final ClientConnection establishedClientConnection = new ClientConnection( clientConnection.id, null, - examId, + (examId != null) ? examId : clientConnection.examId, ConnectionStatus.ESTABLISHED, null, userSessionId, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 30fa4ab5..26fc4141 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -327,7 +327,7 @@ public class ExamAPI_V1_Controller { private Long getInstitutionId(final Principal principal) { final String clientId = principal.getName(); - return this.sebClientConfigDAO.byClientId(clientId) + return this.sebClientConfigDAO.byClientName(clientId) .getOrThrow().institutionId; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java index d55feac8..dadaa444 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java @@ -72,7 +72,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap public void configure(final AuthorizationServerEndpointsConfigurer endpoints) { final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setAccessTokenConverter(this.accessTokenConverter); + endpoints + .tokenGranter(new SynchronizedTokenGranter(endpoints.getTokenGranter())) .tokenStore(this.tokenStore) .authenticationManager(this.authenticationManager) .userDetailsService(this.webServiceUserDetails) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/RevokeTokenEndpoint.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/RevokeTokenEndpoint.java index 501ee16a..ea3c0c5a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/RevokeTokenEndpoint.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/RevokeTokenEndpoint.java @@ -12,6 +12,7 @@ import java.util.Collection; import javax.servlet.http.HttpServletRequest; +import org.apache.http.HttpHeaders; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.EventListener; import org.springframework.http.HttpStatus; @@ -48,7 +49,7 @@ public class RevokeTokenEndpoint { @RequestMapping(value = API.OAUTH_REVOKE_TOKEN_ENDPOINT, method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.OK) public void logout(final HttpServletRequest request) { - final String authHeader = request.getHeader("Authorization"); + final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if (authHeader != null) { final String tokenId = authHeader.substring("Bearer".length() + 1); this.tokenServices.revokeToken(tokenId); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SynchronizedTokenGranter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SynchronizedTokenGranter.java new file mode 100644 index 00000000..08382355 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SynchronizedTokenGranter.java @@ -0,0 +1,83 @@ +/* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.TokenRequest; + +/** Just another level of indirection and work-around to solve the problem described here: + * + * https://github.com/spring-projects/spring-security-oauth/issues/276 + * + * The work-around is to add some retry logic within grant if there happens a DuplicateKeyException + * from underling token store. + * This assumes that there is a JDBC Token Store in place and the authentication_id of the oauth_access_token + * table has a unique identifier constraint. */ +public class SynchronizedTokenGranter implements TokenGranter { + + private static final Logger log = LoggerFactory.getLogger(SynchronizedTokenGranter.class); + + private static final int retrymax = 3; + private static final long wait = 200; + + private final TokenGranter delegate; + + public SynchronizedTokenGranter(final TokenGranter delegate) { + this.delegate = delegate; + } + + @Override + public OAuth2AccessToken grant(final String grantType, final TokenRequest tokenRequest) { + try { + + if (log.isDebugEnabled()) { + log.debug("First try, delegate to original TokenGranter"); + } + + return this.delegate.grant(grantType, tokenRequest); + + } catch (final DuplicateKeyException e) { + + log.error("Failed to grant access token on DuplicateKeyException. Start retry..."); + + final int retry = 1; + OAuth2AccessToken grant = null; + while (grant == null && retry <= retrymax) { + try { + Thread.sleep(wait); + } catch (final InterruptedException e1) { + e1.printStackTrace(); + } + + if (log.isDebugEnabled()) { + log.debug("Retry: {}, delegate to original TokenGranter", retry); + } + + try { + grant = this.delegate.grant(grantType, tokenRequest); + } catch (final DuplicateKeyException ee) { + log.error("Retry: {} failed: ", ee); + } + } + + return grant; + + } finally { + + if (log.isDebugEnabled()) { + log.debug("Finised token grant"); + } + } + } + +} diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 5c1b2c44..c6296b39 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -7,7 +7,7 @@ logging.file=log/sebserver.log # spring.datasource.initialize=true spring.datasource.initialization-mode=always -spring.datasource.url=jdbc:mariadb://localhost:6603/SEBServer?useSSL=false +spring.datasource.url=jdbc:mariadb://localhost:6603/SEBServer?useSSL=false&createDatabaseIfNotExist=true spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.datasource.platform=dev spring.datasource.hikari.max-lifetime=600000 diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 98ae07be..766ed919 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -22,7 +22,8 @@ - + + @@ -60,4 +61,5 @@ + \ No newline at end of file diff --git a/src/main/resources/schema-dev.sql b/src/main/resources/schema-dev.sql index 841b9e5a..777938b8 100644 --- a/src/main/resources/schema-dev.sql +++ b/src/main/resources/schema-dev.sql @@ -410,10 +410,12 @@ CREATE TABLE IF NOT EXISTS `oauth_access_token` ( `user_name` VARCHAR(255) NULL, `client_id` VARCHAR(255) NULL, `authentication` BLOB NULL, - `refresh_token` VARCHAR(255) NULL) + `refresh_token` VARCHAR(255) NULL, + UNIQUE INDEX `authentication_id_UNIQUE` (`authentication_id` ASC)) ; + -- ----------------------------------------------------- -- Table `oauth_refresh_token` -- ----------------------------------------------------- diff --git a/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java b/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java index 5829d483..bbfe9ac2 100644 --- a/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java +++ b/src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java @@ -31,6 +31,7 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -47,7 +48,7 @@ public class HTTPClientBot { private static final Logger log = LoggerFactory.getLogger(HTTPClientBot.class); - private final ExecutorService executorService = Executors.newFixedThreadPool(10); + private final ExecutorService executorService = Executors.newFixedThreadPool(20); private final List scopes = Arrays.asList("read", "write"); @@ -68,6 +69,7 @@ public class HTTPClientBot { private final int connectionAttempts; public HTTPClientBot(final Map args) { + this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080"); this.accessTokenEndpoint = args.getOrDefault("accessTokenEndpoint", "/oauth/token"); this.clientId = args.getOrDefault("clientId", "TO_SET"); @@ -76,11 +78,11 @@ public class HTTPClientBot { this.apiVersion = args.getOrDefault("apiVersion", "v1"); this.examId = args.getOrDefault("examId", "2"); this.institutionId = args.getOrDefault("institutionId", "1"); - this.numberOfConnections = Integer.parseInt(args.getOrDefault("numberOfConnections", "1")); + this.numberOfConnections = Integer.parseInt(args.getOrDefault("numberOfConnections", "4")); this.pingInterval = Long.parseLong(args.getOrDefault("pingInterval", "200")); this.errorInterval = Long.parseLong(args.getOrDefault("errorInterval", String.valueOf(TEN_SECONDS))); this.runtime = Long.parseLong(args.getOrDefault("runtime", String.valueOf(ONE_MINUTE))); - this.connectionAttempts = Integer.parseInt(args.getOrDefault("connectionAttempts", "3")); + this.connectionAttempts = Integer.parseInt(args.getOrDefault("connectionAttempts", "1")); for (int i = 0; i < this.numberOfConnections; i++) { this.executorService.execute(new ConnectionBot("connection_" + i)); @@ -107,16 +109,16 @@ public class HTTPClientBot { private final String handshakeURI = HTTPClientBot.this.webserviceAddress + HTTPClientBot.this.apiPath + "/" + - HTTPClientBot.this.apiVersion + "/handshake"; + HTTPClientBot.this.apiVersion + API.EXAM_API_HANDSHAKE_ENDPOINT; private final String configurartionURI = HTTPClientBot.this.webserviceAddress + HTTPClientBot.this.apiPath + "/" + - HTTPClientBot.this.apiVersion + "/configuration"; + HTTPClientBot.this.apiVersion + API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT; private final String pingURI = HTTPClientBot.this.webserviceAddress + HTTPClientBot.this.apiPath + "/" + - HTTPClientBot.this.apiVersion + "/sebping"; + HTTPClientBot.this.apiVersion + API.EXAM_API_PING_ENDPOINT; private final String eventURI = HTTPClientBot.this.webserviceAddress + HTTPClientBot.this.apiPath + "/" + - HTTPClientBot.this.apiVersion + "/seblog"; + HTTPClientBot.this.apiVersion + API.EXAM_API_EVENT_ENDPOINT; private final HttpEntity connectBody; @@ -161,52 +163,16 @@ public class HTTPClientBot { HTTPClientBot.this.errorInterval); int attempt = 0; + String connectionToken = null; - while (attempt < HTTPClientBot.this.connectionAttempts) { + while (connectionToken == null && attempt < HTTPClientBot.this.connectionAttempts) { attempt++; log.info("ConnectionBot {} : Try to request access-token; attempt: {}", this.name, attempt); try { - this.restTemplate.getAccessToken(); - - final String connectionToken = createConnection(); - if (connectionToken != null) { - if (getConfig(connectionToken) && establishConnection(connectionToken)) { - - final PingEntity pingHeader = new PingEntity(connectionToken); - final EventEntity eventHeader = new EventEntity(connectionToken); - - try { - final long startTime = System.currentTimeMillis(); - final long endTime = startTime + HTTPClientBot.this.runtime; - long currentTime = startTime; - long lastPingTime = startTime; - long lastErrorTime = startTime; - - while (currentTime < endTime) { - if (currentTime - lastPingTime >= HTTPClientBot.this.pingInterval) { - pingHeader.next(); - sendPing(pingHeader); - lastPingTime = currentTime; - } - if (currentTime - lastErrorTime >= HTTPClientBot.this.errorInterval) { - eventHeader.next(); - sendErrorEvent(eventHeader); - lastErrorTime = currentTime; - } - try { - Thread.sleep(50); - } catch (final Exception e) { - } - currentTime = System.currentTimeMillis(); - } - } catch (final Throwable t) { - log.error("ConnectionBot {} : Error sending events: ", this.name, t); - } finally { - disconnect(connectionToken); - } - } - } + final OAuth2AccessToken accessToken = this.restTemplate.getAccessToken(); + log.info("ConnectionBot {} : Got access token: {}", this.name, accessToken); + connectionToken = createConnection(); } catch (final Exception e) { log.error("ConnectionBot {} : Failed to request access-token: ", this.name, e); @@ -215,6 +181,51 @@ public class HTTPClientBot { } } } + + final MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + headers.set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + + final MultiValueMap eventHeaders = new LinkedMultiValueMap<>(); + eventHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE); + eventHeaders.set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + + if (connectionToken != null) { + if (getConfig(headers) && establishConnection(headers)) { + + final PingEntity pingHeader = new PingEntity(headers); + final EventEntity eventHeader = new EventEntity(eventHeaders); + + try { + final long startTime = System.currentTimeMillis(); + final long endTime = startTime + HTTPClientBot.this.runtime; + long currentTime = startTime; + long lastPingTime = startTime; + long lastErrorTime = startTime; + while (currentTime < endTime) { + if (currentTime - lastPingTime >= HTTPClientBot.this.pingInterval) { + pingHeader.next(); + sendPing(pingHeader); + lastPingTime = currentTime; + } + if (currentTime - lastErrorTime >= HTTPClientBot.this.errorInterval) { + eventHeader.next(); + sendErrorEvent(eventHeader); + lastErrorTime = currentTime; + } + try { + Thread.sleep(50); + } catch (final Exception e) { + } + currentTime = System.currentTimeMillis(); + } + } catch (final Throwable t) { + log.error("ConnectionBot {} : Error sending events: ", this.name, t); + } finally { + disconnect(connectionToken); + } + } + } } private String createConnection() { @@ -236,22 +247,22 @@ public class HTTPClientBot { final Collection body = exchange.getBody(); final String token = exchange.getHeaders().getFirst(API.EXAM_API_SEB_CONNECTION_TOKEN); - log.info("ConnectionBot {} : successfully created connection, token: {} body: {} ", token, body); + log.info("ConnectionBot {} : successfully created connection, token: {} body: {} ", this.name, token, + body); return token; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed to init connection", e); + log.error("ConnectionBot {} : Failed to init connection", this.name, e); return null; } } - public boolean getConfig(final String connectionToken) { + public boolean getConfig(final MultiValueMap headers) { final HttpEntity configHeader = new HttpEntity<>( API.EXAM_API_PARAM_EXAM_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + - HTTPClientBot.this.examId); - configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + HTTPClientBot.this.examId, + headers); log.info("ConnectionBot {} : get SEB Configuration", this.name); @@ -271,25 +282,25 @@ public class HTTPClientBot { final byte[] config = exchange.getBody(); if (log.isDebugEnabled()) { - log.debug("ConnectionBot {} : successfully requested exam config: " + Utils.toString(config)); + log.debug("ConnectionBot {} : successfully requested exam config: " + Utils.toString(config), + this.name); } else { - log.info("ConnectionBot {} : successfully requested exam config"); + log.info("ConnectionBot {} : successfully requested exam config", this.name); } return true; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed get SEB Configuration", e); + log.error("ConnectionBot {} : Failed get SEB Configuration", this.name, e); return false; } } - public boolean establishConnection(final String connectionToken) { + public boolean establishConnection(final MultiValueMap headers) { final HttpEntity configHeader = new HttpEntity<>( API.EXAM_API_USER_SESSION_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + - this.name); - configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + this.name, + headers); log.info("ConnectionBot {} : Trying to establish SEB client connection", this.name); @@ -307,11 +318,11 @@ public class HTTPClientBot { throw new RuntimeException("Webservice answered with error: " + exchange.getBody()); } - log.info("ConnectionBot {} : successfully established SEB client connection"); + log.info("ConnectionBot {} : successfully established SEB client connection", this.name); return true; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed get established SEB client connection", e); + log.error("ConnectionBot {} : Failed get established SEB client connection", this.name, e); return false; } } @@ -328,7 +339,7 @@ public class HTTPClientBot { return true; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed send ping", e); + log.error("ConnectionBot {} : Failed send ping", this.name, e); return false; } } @@ -345,15 +356,16 @@ public class HTTPClientBot { return true; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed send ping", e); + log.error("ConnectionBot {} : Failed send event", this.name, e); return false; } } public boolean disconnect(final String connectionToken) { - final HttpEntity configHeader = new HttpEntity<>(null); - configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + final MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + headers.set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + final HttpEntity configHeader = new HttpEntity<>(headers); log.info("ConnectionBot {} : Trying to delete SEB client connection", this.name); @@ -371,11 +383,11 @@ public class HTTPClientBot { throw new RuntimeException("Webservice answered with error: " + exchange.getBody()); } - log.info("ConnectionBot {} : successfully deleted SEB client connection"); + log.info("ConnectionBot {} : successfully deleted SEB client connection", this.name); return true; } catch (final Exception e) { - log.error("ConnectionBot {} : Failed get deleted SEB client connection", e); + log.error("ConnectionBot {} : Failed get deleted SEB client connection", this.name, e); return false; } } @@ -401,19 +413,17 @@ public class HTTPClientBot { private static class PingEntity extends HttpEntity { private final String pingBodyTemplate = API.EXAM_API_PING_TIMESTAMP + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + - "{}" + + "%s" + Constants.FORM_URL_ENCODED_SEPARATOR + API.EXAM_API_PING_NUMBER + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + - "{}"; + "%s"; private long timestamp = 0; private int count = 0; - protected PingEntity(final String connectionToken) { - super(); - getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + protected PingEntity(final MultiValueMap headers) { + super(headers); } void next() { @@ -434,14 +444,12 @@ public class HTTPClientBot { private static class EventEntity extends HttpEntity { private final String eventBodyTemplate = - "{ \"type\": \"ERROR_LOG\", \"timestamp\": {}, \"text\": \"some error\" }"; + "{ \"type\": \"ERROR_LOG\", \"timestamp\": %s, \"text\": \"some error\" }"; private long timestamp = 0; - protected EventEntity(final String connectionToken) { - super(); - getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + protected EventEntity(final MultiValueMap headers) { + super(headers); } void next() { diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java index 127db17b..53f92e5b 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/ExamAPIIntegrationTester.java @@ -29,6 +29,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.crypto.password.PasswordEncoder; @@ -61,6 +62,7 @@ import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfigu webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @AutoConfigureMockMvc +@EnableCaching public abstract class ExamAPIIntegrationTester { @Value("${sebserver.webservice.api.exam.endpoint.v1}") diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 00000000..cd46df9a --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,26 @@ + + + + + %d{HH:mm:ss.SSS} %-5level [%thread]:[%logger] %msg%n + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file