SEBSERV-62 bug-fixes, improvements
This commit is contained in:
parent
f26b1945ad
commit
16d18a53e6
26 changed files with 365 additions and 159 deletions
|
@ -36,13 +36,10 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
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.builders.WebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
|
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
@ -109,21 +106,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
|
||||||
.and();
|
.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")
|
@RequestMapping("/error")
|
||||||
public void handleError(final HttpServletResponse response) throws IOException {
|
public void handleError(final HttpServletResponse response) throws IOException {
|
||||||
//response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
|
//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");
|
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.
|
/** A ClientHttpRequestFactory used in production with TSL SSL configuration.
|
||||||
|
|
|
@ -28,8 +28,9 @@ public final class API {
|
||||||
public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}";
|
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 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_ENDPOINT = "/oauth";
|
||||||
public static final String OAUTH_REVOKE_TOKEN_ENDPOINT = "/oauth/revoke-token"; // TODO to config properties?
|
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 CURRENT_USER_ENDPOINT = API.USER_ACCOUNT_ENDPOINT + "/me";
|
||||||
|
|
||||||
public static final String INFO_ENDPOINT = "/info";
|
public static final String INFO_ENDPOINT = "/info";
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
package ch.ethz.seb.sebserver.gbl.model.session;
|
package ch.ethz.seb.sebserver.gbl.model.session;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -24,7 +25,15 @@ public class ClientConnectionData {
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
protected ClientConnectionData(
|
protected ClientConnectionData(
|
||||||
@JsonProperty("clientConnection") final ClientConnection clientConnection,
|
@JsonProperty("clientConnection") final ClientConnection clientConnection,
|
||||||
@JsonProperty("indicatorValues") final Collection<? extends IndicatorValue> indicatorValues) {
|
@JsonProperty("indicatorValues") final Collection<? extends SimpleIndicatorValue> indicatorValues) {
|
||||||
|
|
||||||
|
this.clientConnection = clientConnection;
|
||||||
|
this.indicatorValues = indicatorValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ClientConnectionData(
|
||||||
|
@JsonProperty("clientConnection") final ClientConnection clientConnection,
|
||||||
|
@JsonProperty("indicatorValues") final List<? extends IndicatorValue> indicatorValues) {
|
||||||
|
|
||||||
this.clientConnection = clientConnection;
|
this.clientConnection = clientConnection;
|
||||||
this.indicatorValues = indicatorValues;
|
this.indicatorValues = indicatorValues;
|
||||||
|
|
|
@ -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.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -31,6 +32,9 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
/** Gui-service related public URLS from spring web security perspective */
|
/** Gui-service related public URLS from spring web security perspective */
|
||||||
public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
|
public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
|
||||||
|
// OAuth entry-points
|
||||||
|
new AntPathRequestMatcher(API.OAUTH_REVOKE_TOKEN_ENDPOINT),
|
||||||
|
// GUI entry-point
|
||||||
new AntPathRequestMatcher("/gui"),
|
new AntPathRequestMatcher("/gui"),
|
||||||
// RAP/RWT resources has to be accessible
|
// RAP/RWT resources has to be accessible
|
||||||
new AntPathRequestMatcher("/rwt-resources/**"),
|
new AntPathRequestMatcher("/rwt-resources/**"),
|
||||||
|
|
|
@ -51,8 +51,7 @@ public final class PageUtils {
|
||||||
CallType.GET_DEPENDENCIES);
|
CallType.GET_DEPENDENCIES);
|
||||||
|
|
||||||
if (builder == null) {
|
if (builder == null) {
|
||||||
log.error("No RestCall builder found for entity type: {}", entity.entityType());
|
throw new RuntimeException("No RestCall builder found for entity type: " + entity.entityType());
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<EntityKey> dependencies = builder
|
final Set<EntityKey> dependencies = builder
|
||||||
|
@ -67,7 +66,7 @@ public final class PageUtils {
|
||||||
return new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
|
return new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} 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", "");
|
return new LocTextKey("sebserver.dialog.confirm.deactivation", "");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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.http.OAuth2ErrorHandler;
|
||||||
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
|
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
|
||||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
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.DefaultAccessTokenRequest;
|
||||||
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
|
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
|
@ -114,17 +113,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
public DisposableOAuth2RestTemplate(final OAuth2ProtectedResourceDetails resource) {
|
public DisposableOAuth2RestTemplate(final OAuth2ProtectedResourceDetails resource) {
|
||||||
super(
|
super(
|
||||||
resource,
|
resource,
|
||||||
new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()) {
|
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -129,17 +129,17 @@ public final class ClientConnectionTable {
|
||||||
for (final UpdatableTableItem uti : this.tableMapping.values()) {
|
for (final UpdatableTableItem uti : this.tableMapping.values()) {
|
||||||
if (uti.tableItem == null) {
|
if (uti.tableItem == null) {
|
||||||
createTableItem(uti);
|
createTableItem(uti);
|
||||||
updateIndicatorValues(uti);
|
updateIndicatorValues(uti, false);
|
||||||
updateConnectionStatusColor(uti);
|
updateConnectionStatusColor(uti);
|
||||||
} else {
|
} else {
|
||||||
if (!uti.connectionData.clientConnection.status
|
if (uti.previous_connectionData == null || !uti.connectionData.clientConnection.status
|
||||||
.equals(uti.previous_connectionData.clientConnection.status)) {
|
.equals(uti.previous_connectionData.clientConnection.status)) {
|
||||||
uti.tableItem.setText(0, uti.getConnectionIdentifer());
|
uti.tableItem.setText(0, uti.getConnectionIdentifer());
|
||||||
uti.tableItem.setText(1, uti.getStatusName());
|
uti.tableItem.setText(2, uti.getStatusName());
|
||||||
updateConnectionStatusColor(uti);
|
updateConnectionStatusColor(uti);
|
||||||
}
|
}
|
||||||
if (uti.hasStatus(ConnectionStatus.ESTABLISHED)) {
|
if (uti.hasStatus(ConnectionStatus.ESTABLISHED)) {
|
||||||
updateIndicatorValues(uti);
|
updateIndicatorValues(uti, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uti.tableItem.getDisplay();
|
uti.tableItem.getDisplay();
|
||||||
|
@ -163,16 +163,17 @@ public final class ClientConnectionTable {
|
||||||
column.setWidth(columnWidth);
|
column.setWidth(columnWidth);
|
||||||
}
|
}
|
||||||
this.table.layout(true, true);
|
this.table.layout(true, true);
|
||||||
//this.table.pack();
|
|
||||||
this.tableWidth = area.width;
|
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) {
|
for (final IndicatorValue iv : uti.connectionData.indicatorValues) {
|
||||||
final IndicatorData indicatorData = this.indicatorMapping.get(iv.getType());
|
final IndicatorData indicatorData = this.indicatorMapping.get(iv.getType());
|
||||||
if (indicatorData != null) {
|
if (indicatorData != null) {
|
||||||
|
if (!established && iv.getType() == IndicatorType.LAST_PING) {
|
||||||
|
uti.tableItem.setText(indicatorData.index, "--");
|
||||||
|
} else {
|
||||||
uti.tableItem.setText(indicatorData.index, String.valueOf(iv.getValue()));
|
uti.tableItem.setText(indicatorData.index, String.valueOf(iv.getValue()));
|
||||||
uti.tableItem.setBackground(
|
uti.tableItem.setBackground(
|
||||||
indicatorData.index,
|
indicatorData.index,
|
||||||
|
@ -180,19 +181,20 @@ public final class ClientConnectionTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateConnectionStatusColor(final UpdatableTableItem uti) {
|
private void updateConnectionStatusColor(final UpdatableTableItem uti) {
|
||||||
switch (uti.connectionData.clientConnection.status) {
|
switch (uti.connectionData.clientConnection.status) {
|
||||||
case ESTABLISHED: {
|
case ESTABLISHED: {
|
||||||
uti.tableItem.setBackground(1, this.color1);
|
uti.tableItem.setBackground(2, this.color1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ABORTED: {
|
case ABORTED: {
|
||||||
uti.tableItem.setBackground(1, this.color3);
|
uti.tableItem.setBackground(2, this.color3);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
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) {
|
private Color getColorForValue(final IndicatorData indicatorData, final double value) {
|
||||||
|
|
||||||
for (int i = 0; i < indicatorData.thresholdColor.length; i++) {
|
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 indicatorData.thresholdColor[i].color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.color1;
|
return indicatorData.thresholdColor[indicatorData.thresholdColor.length - 1].color;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class UpdatableTableItem {
|
private static final class UpdatableTableItem {
|
||||||
|
@ -239,7 +241,7 @@ public final class ClientConnectionTable {
|
||||||
return this.connectionData.clientConnection.userSessionId;
|
return this.connectionData.clientConnection.userSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "- " + this.connectionId + " -";
|
return "--";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasStatus(final ConnectionStatus status) {
|
public boolean hasStatus(final ConnectionStatus status) {
|
||||||
|
|
|
@ -9,24 +9,39 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||||
|
|
||||||
import java.util.Collection;
|
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.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
public interface ClientConnectionDAO extends EntityDAO<ClientConnection, ClientConnection> {
|
public interface ClientConnectionDAO extends EntityDAO<ClientConnection, ClientConnection> {
|
||||||
|
|
||||||
|
public static final String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE";
|
||||||
|
|
||||||
/** Get a list of all connection tokens of all connections (no matter what state)
|
/** Get a list of all connection tokens of all connections (no matter what state)
|
||||||
* of an exam.
|
* of an exam.
|
||||||
*
|
*
|
||||||
* @param examId The exam identifier
|
* @param examId The exam identifier
|
||||||
* @return list of all connection tokens of all connections (no matter what state)
|
* @return list of all connection tokens of all connections (no matter what state)
|
||||||
* of an exam */
|
* of an exam */
|
||||||
|
@Cacheable(
|
||||||
|
cacheNames = CONNECTION_TOKENS_CACHE,
|
||||||
|
key = "#examId",
|
||||||
|
unless = "#result.hasError()")
|
||||||
Result<Collection<String>> getConnectionTokens(Long examId);
|
Result<Collection<String>> getConnectionTokens(Long examId);
|
||||||
|
|
||||||
/** Get a ClientConnection for a specified token.
|
@Override
|
||||||
*
|
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
|
||||||
* @param connectionToken the connection token
|
Result<ClientConnection> createNew(ClientConnection data);
|
||||||
* @return Result refer to ClientConnection or refer to a error if happened */
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
|
||||||
|
Result<Collection<EntityKey>> delete(Set<EntityKey> all);
|
||||||
|
|
||||||
Result<ClientConnection> byConnectionToken(String connectionToken);
|
Result<ClientConnection> byConnectionToken(String connectionToken);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,18 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
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.model.sebconfig.SebClientConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.bulkaction.BulkActionSupportDAO;
|
||||||
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.sebconfig.SebClientConfigService;
|
||||||
|
|
||||||
/** Concrete EntityDAO interface of SebClientConfig entities */
|
/** Concrete EntityDAO interface of SebClientConfig entities */
|
||||||
public interface SebClientConfigDAO extends
|
public interface SebClientConfigDAO extends
|
||||||
|
@ -20,9 +28,9 @@ public interface SebClientConfigDAO extends
|
||||||
|
|
||||||
/** Get a SebClientConfig by specified client identifier
|
/** 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 */
|
* @return Result refer to the SebClientConfig for client or refer to an error if happened */
|
||||||
Result<SebClientConfig> byClientId(String clientId);
|
Result<SebClientConfig> byClientName(String clientName);
|
||||||
|
|
||||||
/** Get the configured ClientCredentials for a given SebClientConfig.
|
/** Get the configured ClientCredentials for a given SebClientConfig.
|
||||||
* The ClientCredentials are still encoded as they are on DB storage
|
* The ClientCredentials are still encoded as they are on DB storage
|
||||||
|
@ -38,4 +46,39 @@ public interface SebClientConfigDAO extends
|
||||||
* @return encrypted configuration password */
|
* @return encrypted configuration password */
|
||||||
Result<CharSequence> getConfigPasswortCipher(String modelId);
|
Result<CharSequence> 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<CharSequence> getConfigPasswortCipherByClientName(String clientName);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(
|
||||||
|
cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE,
|
||||||
|
allEntries = true)
|
||||||
|
Result<SebClientConfig> save(SebClientConfig data);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(
|
||||||
|
cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE,
|
||||||
|
allEntries = true)
|
||||||
|
Result<Collection<EntityKey>> delete(Set<EntityKey> all);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(
|
||||||
|
cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE,
|
||||||
|
allEntries = true)
|
||||||
|
Result<Collection<EntityKey>> setActive(Set<EntityKey> all, boolean active);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(
|
||||||
|
cacheNames = SebClientConfigService.CLIENT_CONFIG_BY_CLIENT_ID_CHACHE,
|
||||||
|
allEntries = true)
|
||||||
|
default Collection<Result<EntityKey>> processBulkAction(final BulkAction bulkAction) {
|
||||||
|
return BulkActionSupportDAO.super.processBulkAction(bulkAction);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,14 +133,14 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<SebClientConfig> byClientId(final String clientId) {
|
public Result<SebClientConfig> byClientName(final String clientName) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
return this.sebClientConfigRecordMapper
|
return this.sebClientConfigRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
.where(
|
.where(
|
||||||
SebClientConfigRecordDynamicSqlSupport.clientName,
|
SebClientConfigRecordDynamicSqlSupport.clientName,
|
||||||
isEqualTo(clientId))
|
isEqualTo(clientName))
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -150,6 +150,28 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<CharSequence> 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
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public boolean isActive(final String modelId) {
|
public boolean isActive(final String modelId) {
|
||||||
|
|
|
@ -10,11 +10,15 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
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.model.sebconfig.SebClientConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
public interface SebClientConfigService {
|
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 =
|
static String SEB_CLIENT_CONFIG_EXAMPLE_XML =
|
||||||
" <dict>\r\n" +
|
" <dict>\r\n" +
|
||||||
" <key>sebMode</key>\r\n" +
|
" <key>sebMode</key>\r\n" +
|
||||||
|
@ -73,6 +77,10 @@ public interface SebClientConfigService {
|
||||||
*
|
*
|
||||||
* @param clientId the clientId/clientName
|
* @param clientId the clientId/clientName
|
||||||
* @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */
|
* @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<CharSequence> getEncodedClientSecret(String clientId);
|
Result<CharSequence> getEncodedClientSecret(String clientId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,10 @@ public class ExamConfigIO {
|
||||||
final Long institutionId,
|
final Long institutionId,
|
||||||
final Long configurationNodeId) {
|
final Long configurationNodeId) {
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Start export SEB plain XML configuration asynconously");
|
||||||
|
}
|
||||||
|
|
||||||
// get all defined root configuration attributes
|
// get all defined root configuration attributes
|
||||||
final Map<Long, ConfigurationAttribute> attributes = this.configurationAttributeDAO.getAllRootAttributes()
|
final Map<Long, ConfigurationAttribute> attributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
|
@ -124,6 +128,10 @@ public class ExamConfigIO {
|
||||||
out.write(XML_PLIST_END_UTF_8);
|
out.write(XML_PLIST_END_UTF_8);
|
||||||
out.flush();
|
out.flush();
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Finished export SEB plain XML configuration asynconously");
|
||||||
|
}
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
|
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -130,13 +130,8 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<CharSequence> getEncodedClientSecret(final String clientId) {
|
public Result<CharSequence> getEncodedClientSecret(final String clientId) {
|
||||||
return this.sebClientConfigDAO.byClientId(clientId)
|
return this.sebClientConfigDAO.getConfigPasswortCipherByClientName(clientId)
|
||||||
.flatMap(this::getEncodedSecret);
|
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher)));
|
||||||
}
|
|
||||||
|
|
||||||
private Result<CharSequence> getEncodedSecret(final SebClientConfig clientConfig) {
|
|
||||||
return this.sebClientConfigDAO.getSebClientCredentials(clientConfig.getModelId())
|
|
||||||
.map(cc -> this.clientPasswordEncoder.encode(this.clientCredentialService.getPlainClientSecret(cc)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
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.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
|
@ -25,7 +26,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
|
||||||
|
|
||||||
protected ClientConnectionDataInternal(
|
protected ClientConnectionDataInternal(
|
||||||
final ClientConnection clientConnection,
|
final ClientConnection clientConnection,
|
||||||
final Collection<ClientIndicator> clientIndicators) {
|
final List<ClientIndicator> clientIndicators) {
|
||||||
|
|
||||||
super(clientConnection, clientIndicators);
|
super(clientConnection, clientIndicators);
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class ClientIndicatorFactory {
|
||||||
this.enableCaching = enableCaching;
|
this.enableCaching = enableCaching;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ClientIndicator> createFor(final ClientConnection clientConnection) {
|
public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
|
||||||
final List<ClientIndicator> result = new ArrayList<>();
|
final List<ClientIndicator> result = new ArrayList<>();
|
||||||
|
|
||||||
if (clientConnection.examId == null) {
|
if (clientConnection.examId == null) {
|
||||||
|
|
|
@ -236,7 +236,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
|
||||||
clientConnection);
|
clientConnection);
|
||||||
|
|
||||||
// Exam integrity
|
// 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: {}",
|
log.error("Exam integrity violation with examId: {} on clientConnection: {}",
|
||||||
examId,
|
examId,
|
||||||
clientConnection);
|
clientConnection);
|
||||||
|
@ -251,7 +251,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
|
||||||
final ClientConnection establishedClientConnection = new ClientConnection(
|
final ClientConnection establishedClientConnection = new ClientConnection(
|
||||||
clientConnection.id,
|
clientConnection.id,
|
||||||
null,
|
null,
|
||||||
examId,
|
(examId != null) ? examId : clientConnection.examId,
|
||||||
ConnectionStatus.ESTABLISHED,
|
ConnectionStatus.ESTABLISHED,
|
||||||
null,
|
null,
|
||||||
userSessionId,
|
userSessionId,
|
||||||
|
|
|
@ -327,7 +327,7 @@ public class ExamAPI_V1_Controller {
|
||||||
|
|
||||||
private Long getInstitutionId(final Principal principal) {
|
private Long getInstitutionId(final Principal principal) {
|
||||||
final String clientId = principal.getName();
|
final String clientId = principal.getName();
|
||||||
return this.sebClientConfigDAO.byClientId(clientId)
|
return this.sebClientConfigDAO.byClientName(clientId)
|
||||||
.getOrThrow().institutionId;
|
.getOrThrow().institutionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||||
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
|
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
|
||||||
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
|
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
|
||||||
jwtAccessTokenConverter.setAccessTokenConverter(this.accessTokenConverter);
|
jwtAccessTokenConverter.setAccessTokenConverter(this.accessTokenConverter);
|
||||||
|
|
||||||
endpoints
|
endpoints
|
||||||
|
.tokenGranter(new SynchronizedTokenGranter(endpoints.getTokenGranter()))
|
||||||
.tokenStore(this.tokenStore)
|
.tokenStore(this.tokenStore)
|
||||||
.authenticationManager(this.authenticationManager)
|
.authenticationManager(this.authenticationManager)
|
||||||
.userDetailsService(this.webServiceUserDetails)
|
.userDetailsService(this.webServiceUserDetails)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.Collection;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
@ -48,7 +49,7 @@ public class RevokeTokenEndpoint {
|
||||||
@RequestMapping(value = API.OAUTH_REVOKE_TOKEN_ENDPOINT, method = RequestMethod.DELETE)
|
@RequestMapping(value = API.OAUTH_REVOKE_TOKEN_ENDPOINT, method = RequestMethod.DELETE)
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public void logout(final HttpServletRequest request) {
|
public void logout(final HttpServletRequest request) {
|
||||||
final String authHeader = request.getHeader("Authorization");
|
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
if (authHeader != null) {
|
if (authHeader != null) {
|
||||||
final String tokenId = authHeader.substring("Bearer".length() + 1);
|
final String tokenId = authHeader.substring("Bearer".length() + 1);
|
||||||
this.tokenServices.revokeToken(tokenId);
|
this.tokenServices.revokeToken(tokenId);
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ logging.file=log/sebserver.log
|
||||||
#
|
#
|
||||||
spring.datasource.initialize=true
|
spring.datasource.initialize=true
|
||||||
spring.datasource.initialization-mode=always
|
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.driver-class-name=org.mariadb.jdbc.Driver
|
||||||
spring.datasource.platform=dev
|
spring.datasource.platform=dev
|
||||||
spring.datasource.hikari.max-lifetime=600000
|
spring.datasource.hikari.max-lifetime=600000
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
<appender-ref ref="FILE" />
|
<appender-ref ref="FILE" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<Logger name="ch.ethz.seb.sebserver" level="DEBUG" additivity="true" />
|
<Logger name="ch.ethz.seb.sebserver.gui" level="INFO" additivity="true" />
|
||||||
|
<Logger name="ch.ethz.seb.sebserver.webservice" level="DEBUG" additivity="true" />
|
||||||
<Logger name="org.apache.ibatis.datasource" level="TRACE" additivity="true" />
|
<Logger name="org.apache.ibatis.datasource" level="TRACE" additivity="true" />
|
||||||
<Logger name="org.mybatis.generator" level="INFO" additivity="true" />
|
<Logger name="org.mybatis.generator" level="INFO" additivity="true" />
|
||||||
<Logger name="org.springframework.boot" level="INFO" additivity="true" />
|
<Logger name="org.springframework.boot" level="INFO" additivity="true" />
|
||||||
|
@ -60,4 +61,5 @@
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</springProfile>
|
</springProfile>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
|
@ -410,10 +410,12 @@ CREATE TABLE IF NOT EXISTS `oauth_access_token` (
|
||||||
`user_name` VARCHAR(255) NULL,
|
`user_name` VARCHAR(255) NULL,
|
||||||
`client_id` VARCHAR(255) NULL,
|
`client_id` VARCHAR(255) NULL,
|
||||||
`authentication` BLOB 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`
|
-- Table `oauth_refresh_token`
|
||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
|
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
|
||||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
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.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ public class HTTPClientBot {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(HTTPClientBot.class);
|
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<String> scopes = Arrays.asList("read", "write");
|
private final List<String> scopes = Arrays.asList("read", "write");
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ public class HTTPClientBot {
|
||||||
private final int connectionAttempts;
|
private final int connectionAttempts;
|
||||||
|
|
||||||
public HTTPClientBot(final Map<String, String> args) {
|
public HTTPClientBot(final Map<String, String> args) {
|
||||||
|
|
||||||
this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080");
|
this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080");
|
||||||
this.accessTokenEndpoint = args.getOrDefault("accessTokenEndpoint", "/oauth/token");
|
this.accessTokenEndpoint = args.getOrDefault("accessTokenEndpoint", "/oauth/token");
|
||||||
this.clientId = args.getOrDefault("clientId", "TO_SET");
|
this.clientId = args.getOrDefault("clientId", "TO_SET");
|
||||||
|
@ -76,11 +78,11 @@ public class HTTPClientBot {
|
||||||
this.apiVersion = args.getOrDefault("apiVersion", "v1");
|
this.apiVersion = args.getOrDefault("apiVersion", "v1");
|
||||||
this.examId = args.getOrDefault("examId", "2");
|
this.examId = args.getOrDefault("examId", "2");
|
||||||
this.institutionId = args.getOrDefault("institutionId", "1");
|
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.pingInterval = Long.parseLong(args.getOrDefault("pingInterval", "200"));
|
||||||
this.errorInterval = Long.parseLong(args.getOrDefault("errorInterval", String.valueOf(TEN_SECONDS)));
|
this.errorInterval = Long.parseLong(args.getOrDefault("errorInterval", String.valueOf(TEN_SECONDS)));
|
||||||
this.runtime = Long.parseLong(args.getOrDefault("runtime", String.valueOf(ONE_MINUTE)));
|
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++) {
|
for (int i = 0; i < this.numberOfConnections; i++) {
|
||||||
this.executorService.execute(new ConnectionBot("connection_" + i));
|
this.executorService.execute(new ConnectionBot("connection_" + i));
|
||||||
|
@ -107,16 +109,16 @@ public class HTTPClientBot {
|
||||||
|
|
||||||
private final String handshakeURI = HTTPClientBot.this.webserviceAddress +
|
private final String handshakeURI = HTTPClientBot.this.webserviceAddress +
|
||||||
HTTPClientBot.this.apiPath + "/" +
|
HTTPClientBot.this.apiPath + "/" +
|
||||||
HTTPClientBot.this.apiVersion + "/handshake";
|
HTTPClientBot.this.apiVersion + API.EXAM_API_HANDSHAKE_ENDPOINT;
|
||||||
private final String configurartionURI = HTTPClientBot.this.webserviceAddress +
|
private final String configurartionURI = HTTPClientBot.this.webserviceAddress +
|
||||||
HTTPClientBot.this.apiPath + "/" +
|
HTTPClientBot.this.apiPath + "/" +
|
||||||
HTTPClientBot.this.apiVersion + "/configuration";
|
HTTPClientBot.this.apiVersion + API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT;
|
||||||
private final String pingURI = HTTPClientBot.this.webserviceAddress +
|
private final String pingURI = HTTPClientBot.this.webserviceAddress +
|
||||||
HTTPClientBot.this.apiPath + "/" +
|
HTTPClientBot.this.apiPath + "/" +
|
||||||
HTTPClientBot.this.apiVersion + "/sebping";
|
HTTPClientBot.this.apiVersion + API.EXAM_API_PING_ENDPOINT;
|
||||||
private final String eventURI = HTTPClientBot.this.webserviceAddress +
|
private final String eventURI = HTTPClientBot.this.webserviceAddress +
|
||||||
HTTPClientBot.this.apiPath + "/" +
|
HTTPClientBot.this.apiPath + "/" +
|
||||||
HTTPClientBot.this.apiVersion + "/seblog";
|
HTTPClientBot.this.apiVersion + API.EXAM_API_EVENT_ENDPOINT;
|
||||||
|
|
||||||
private final HttpEntity<?> connectBody;
|
private final HttpEntity<?> connectBody;
|
||||||
|
|
||||||
|
@ -161,20 +163,38 @@ public class HTTPClientBot {
|
||||||
HTTPClientBot.this.errorInterval);
|
HTTPClientBot.this.errorInterval);
|
||||||
|
|
||||||
int attempt = 0;
|
int attempt = 0;
|
||||||
|
String connectionToken = null;
|
||||||
|
|
||||||
while (attempt < HTTPClientBot.this.connectionAttempts) {
|
while (connectionToken == null && attempt < HTTPClientBot.this.connectionAttempts) {
|
||||||
attempt++;
|
attempt++;
|
||||||
log.info("ConnectionBot {} : Try to request access-token; attempt: {}", this.name, attempt);
|
log.info("ConnectionBot {} : Try to request access-token; attempt: {}", this.name, attempt);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
this.restTemplate.getAccessToken();
|
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);
|
||||||
|
if (attempt >= HTTPClientBot.this.connectionAttempts) {
|
||||||
|
log.error("ConnectionBot {} : Gave up afer {} connection attempts: ", this.name, attempt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||||
|
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
|
headers.set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||||
|
|
||||||
|
final MultiValueMap<String, String> eventHeaders = new LinkedMultiValueMap<>();
|
||||||
|
eventHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||||
|
eventHeaders.set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||||
|
|
||||||
final String connectionToken = createConnection();
|
|
||||||
if (connectionToken != null) {
|
if (connectionToken != null) {
|
||||||
if (getConfig(connectionToken) && establishConnection(connectionToken)) {
|
if (getConfig(headers) && establishConnection(headers)) {
|
||||||
|
|
||||||
final PingEntity pingHeader = new PingEntity(connectionToken);
|
final PingEntity pingHeader = new PingEntity(headers);
|
||||||
final EventEntity eventHeader = new EventEntity(connectionToken);
|
final EventEntity eventHeader = new EventEntity(eventHeaders);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
|
@ -182,7 +202,6 @@ public class HTTPClientBot {
|
||||||
long currentTime = startTime;
|
long currentTime = startTime;
|
||||||
long lastPingTime = startTime;
|
long lastPingTime = startTime;
|
||||||
long lastErrorTime = startTime;
|
long lastErrorTime = startTime;
|
||||||
|
|
||||||
while (currentTime < endTime) {
|
while (currentTime < endTime) {
|
||||||
if (currentTime - lastPingTime >= HTTPClientBot.this.pingInterval) {
|
if (currentTime - lastPingTime >= HTTPClientBot.this.pingInterval) {
|
||||||
pingHeader.next();
|
pingHeader.next();
|
||||||
|
@ -207,14 +226,6 @@ public class HTTPClientBot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("ConnectionBot {} : Failed to request access-token: ", this.name, e);
|
|
||||||
if (attempt >= HTTPClientBot.this.connectionAttempts) {
|
|
||||||
log.error("ConnectionBot {} : Gave up afer {} connection attempts: ", this.name, attempt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createConnection() {
|
private String createConnection() {
|
||||||
|
@ -236,22 +247,22 @@ public class HTTPClientBot {
|
||||||
final Collection<RunningExam> body = exchange.getBody();
|
final Collection<RunningExam> body = exchange.getBody();
|
||||||
final String token = exchange.getHeaders().getFirst(API.EXAM_API_SEB_CONNECTION_TOKEN);
|
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;
|
return token;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("ConnectionBot {} : Failed to init connection", e);
|
log.error("ConnectionBot {} : Failed to init connection", this.name, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getConfig(final String connectionToken) {
|
public boolean getConfig(final MultiValueMap<String, String> headers) {
|
||||||
final HttpEntity<?> configHeader = new HttpEntity<>(
|
final HttpEntity<?> configHeader = new HttpEntity<>(
|
||||||
API.EXAM_API_PARAM_EXAM_ID +
|
API.EXAM_API_PARAM_EXAM_ID +
|
||||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||||
HTTPClientBot.this.examId);
|
HTTPClientBot.this.examId,
|
||||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
headers);
|
||||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
|
||||||
|
|
||||||
log.info("ConnectionBot {} : get SEB Configuration", this.name);
|
log.info("ConnectionBot {} : get SEB Configuration", this.name);
|
||||||
|
|
||||||
|
@ -271,25 +282,25 @@ public class HTTPClientBot {
|
||||||
final byte[] config = exchange.getBody();
|
final byte[] config = exchange.getBody();
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
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 {
|
} else {
|
||||||
log.info("ConnectionBot {} : successfully requested exam config");
|
log.info("ConnectionBot {} : successfully requested exam config", this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("ConnectionBot {} : Failed get SEB Configuration", e);
|
log.error("ConnectionBot {} : Failed get SEB Configuration", this.name, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean establishConnection(final String connectionToken) {
|
public boolean establishConnection(final MultiValueMap<String, String> headers) {
|
||||||
final HttpEntity<?> configHeader = new HttpEntity<>(
|
final HttpEntity<?> configHeader = new HttpEntity<>(
|
||||||
API.EXAM_API_USER_SESSION_ID +
|
API.EXAM_API_USER_SESSION_ID +
|
||||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||||
this.name);
|
this.name,
|
||||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
headers);
|
||||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
|
||||||
|
|
||||||
log.info("ConnectionBot {} : Trying to establish SEB client connection", this.name);
|
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());
|
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;
|
return true;
|
||||||
} catch (final Exception e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +339,7 @@ public class HTTPClientBot {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("ConnectionBot {} : Failed send ping", e);
|
log.error("ConnectionBot {} : Failed send ping", this.name, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,15 +356,16 @@ public class HTTPClientBot {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("ConnectionBot {} : Failed send ping", e);
|
log.error("ConnectionBot {} : Failed send event", this.name, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean disconnect(final String connectionToken) {
|
public boolean disconnect(final String connectionToken) {
|
||||||
final HttpEntity<?> configHeader = new HttpEntity<>(null);
|
final MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
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);
|
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());
|
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;
|
return true;
|
||||||
} catch (final Exception e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -401,19 +413,17 @@ public class HTTPClientBot {
|
||||||
private static class PingEntity extends HttpEntity<String> {
|
private static class PingEntity extends HttpEntity<String> {
|
||||||
private final String pingBodyTemplate = API.EXAM_API_PING_TIMESTAMP +
|
private final String pingBodyTemplate = API.EXAM_API_PING_TIMESTAMP +
|
||||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||||
"{}" +
|
"%s" +
|
||||||
Constants.FORM_URL_ENCODED_SEPARATOR +
|
Constants.FORM_URL_ENCODED_SEPARATOR +
|
||||||
API.EXAM_API_PING_NUMBER +
|
API.EXAM_API_PING_NUMBER +
|
||||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||||
"{}";
|
"%s";
|
||||||
|
|
||||||
private long timestamp = 0;
|
private long timestamp = 0;
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
|
|
||||||
protected PingEntity(final String connectionToken) {
|
protected PingEntity(final MultiValueMap<String, String> headers) {
|
||||||
super();
|
super(headers);
|
||||||
getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
|
||||||
getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void next() {
|
void next() {
|
||||||
|
@ -434,14 +444,12 @@ public class HTTPClientBot {
|
||||||
|
|
||||||
private static class EventEntity extends HttpEntity<String> {
|
private static class EventEntity extends HttpEntity<String> {
|
||||||
private final String eventBodyTemplate =
|
private final String eventBodyTemplate =
|
||||||
"{ \"type\": \"ERROR_LOG\", \"timestamp\": {}, \"text\": \"some error\" }";
|
"{ \"type\": \"ERROR_LOG\", \"timestamp\": %s, \"text\": \"some error\" }";
|
||||||
|
|
||||||
private long timestamp = 0;
|
private long timestamp = 0;
|
||||||
|
|
||||||
protected EventEntity(final String connectionToken) {
|
protected EventEntity(final MultiValueMap<String, String> headers) {
|
||||||
super();
|
super(headers);
|
||||||
getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
|
||||||
getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void next() {
|
void next() {
|
||||||
|
|
|
@ -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.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
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)
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
|
@EnableCaching
|
||||||
public abstract class ExamAPIIntegrationTester {
|
public abstract class ExamAPIIntegrationTester {
|
||||||
|
|
||||||
@Value("${sebserver.webservice.api.exam.endpoint.v1}")
|
@Value("${sebserver.webservice.api.exam.endpoint.v1}")
|
||||||
|
|
26
src/test/resources/logback.xml
Normal file
26
src/test/resources/logback.xml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration debug="false" scan="false">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread]:[%logger] %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<Logger name="org.eth.demo.sebserver" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.mybatis.generator" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.eth.demo.sebserver.batis" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.springframework.security" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.springframework.security.oauth2" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.springframework.web.socket.messaging" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.springframework.messaging" level="INFO" additivity="true" />
|
||||||
|
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="INFO" additivity="true" />
|
||||||
|
<Logger name="org.springframework.web" level="INFO" additivity="true" />
|
||||||
|
|
||||||
|
<Logger name="org.eth.demo.sebserver.batis.gen.mapper.ExamRecordMapper" level="INFO" additivity="true" />
|
||||||
|
|
||||||
|
<root level="INFO" additivity="true">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
Loading…
Add table
Reference in a new issue