diff --git a/examples/hawkbit-mgmt-api-client/.gitignore b/examples/hawkbit-mgmt-api-client/.gitignore index 0f630157f..4b9b73073 100644 --- a/examples/hawkbit-mgmt-api-client/.gitignore +++ b/examples/hawkbit-mgmt-api-client/.gitignore @@ -1,2 +1,3 @@ /target/ /bin/ +/.apt_generated/ diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index b27e76a28..24e9667c2 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -29,6 +29,7 @@ import org.eclipse.hawkbit.im.authentication.TenantUserPasswordAuthenticationTok import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.rest.resource.RestConstants; import org.eclipse.hawkbit.security.ControllerTenantAwareAuthenticationDetailsSource; import org.eclipse.hawkbit.security.DdiSecurityProperties; @@ -39,6 +40,7 @@ import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurit import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter; import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,33 +113,36 @@ public class SecurityManagedConfiguration { @Autowired private ControllerManagement controllerManagement; @Autowired - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; @Autowired private TenantAware tenantAware; @Autowired private DdiSecurityProperties ddiSecurityConfiguration; @Autowired private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties; + @Autowired + private SystemSecurityContext systemSecurityContext; @Override protected void configure(final HttpSecurity http) throws Exception { final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), - systemManagement, tenantAware); + ddiSecurityConfiguration.getRp().getCnHeader(), + ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, + tenantAware, systemSecurityContext); securityHeaderFilter.setAuthenticationManager(authenticationManager()); securityHeaderFilter.setCheckForPrincipalChanges(true); securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - systemManagement, tenantAware, controllerManagement); + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); securityTokenFilter.setAuthenticationManager(authenticationManager()); securityTokenFilter.setCheckForPrincipalChanges(true); securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager()); gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); @@ -169,19 +174,10 @@ public class SecurityManagedConfiguration { } } - /* - * (non-Javadoc) - * - * @see - * org.springframework.security.config.annotation.web.configuration. - * WebSecurityConfigurerAdapter - * #configure(org.springframework.security.config.annotation. - * authentication.builders. AuthenticationManagerBuilder) - */ @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider( - new PreAuthTokenSourceTrustAuthenticationProvider(ddiSecurityConfiguration.getRp().getTrustedIPs())); + auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); } } @@ -466,8 +462,8 @@ public class SecurityManagedConfiguration { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider( - new PreAuthTokenSourceTrustAuthenticationProvider(ddiSecurityConfiguration.getRp().getTrustedIPs())); + auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); } } diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties index dcb3e8fb4..198fce255 100644 --- a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties +++ b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties @@ -36,6 +36,10 @@ hawkbit.threadpool.queuesize=20000 # Defines the polling time for the controllers in HH:MM:SS notation hawkbit.controller.pollingTime=00:05:00 hawkbit.controller.pollingOverdueTime=00:05:00 +hawkbit.controller.maxPollingTime=23:59:59 +hawkbit.controller.minPollingTime=00:00:30 +# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window + # Configuration for RabbitMQ integration hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter diff --git a/hawkbit-core/.gitignore b/hawkbit-core/.gitignore new file mode 100644 index 000000000..c71ea97ab --- /dev/null +++ b/hawkbit-core/.gitignore @@ -0,0 +1 @@ +/.apt_generated/ diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java index 4e176d258..c923963d2 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java @@ -8,11 +8,19 @@ */ package org.eclipse.hawkbit; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.EnvironmentAware; import org.springframework.stereotype.Component; /** - * Defines the polling time for the controllers in HH:MM:SS notation. + * Defines global configuration for the controllers/clients on the provisioning + * targets/devices. + * + * + * Note: many of the controller related properties can be overridden on tenant + * level. As a result they are not defined here but in + * {@link TenantConfigurationKey} and injected using {@link EnvironmentAware}. * */ @Component @@ -20,31 +28,31 @@ import org.springframework.stereotype.Component; public class ControllerPollProperties { /** - * Recommended target polling time for DDI API. Final choice is up to the - * target. + * Maximum polling time that can be configured by a tenant in HH:MM:SS + * notation. */ - private String pollingTime = "00:05:00"; + private String maxPollingTime = "23:59:00"; /** - * Assumed time frame where the target is considered overdue when no DDI - * polling has been registered by the update server. + * Minimum polling time that can be configured by a tenant in HH:MM:SS + * notation. */ - private String pollingOverdueTime = "00:05:00"; + private String minPollingTime = "00:00:30"; - public String getPollingTime() { - return pollingTime; + public String getMaxPollingTime() { + return maxPollingTime; } - public void setPollingTime(final String pollingTime) { - this.pollingTime = pollingTime; + public void setMaxPollingTime(final String maxPollingTime) { + this.maxPollingTime = maxPollingTime; } - public String getPollingOverdueTime() { - return pollingOverdueTime; + public String getMinPollingTime() { + return minPollingTime; } - public void setPollingOverdueTime(final String pollingOverdue) { - this.pollingOverdueTime = pollingOverdue; + public void setMinPollingTime(final String minPollingTime) { + this.minPollingTime = minPollingTime; } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index cdf91f879..003b31cd7 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -65,6 +65,7 @@ public enum SpServerError { */ SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", "The given sort parameter direction does not exist"), + /** * */ @@ -176,6 +177,16 @@ public enum SpServerError { SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", "The given entity is read only and the change cannot be completed."), + /** + * + */ + SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", + "The given configuration value is invalid."), + /** + * + */ + SP_CONFIGURATION_KEY_INVALID("hawkbit.server.error.configKeyInvalid", "The given configuration key is invalid."), + /** * */ diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java index a605f7745..ba691bb5f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java @@ -74,6 +74,9 @@ public abstract class SpServerRtException extends RuntimeException { this.error = error; } + /** + * @return the SpServerError which is wrapped by this exception + */ public SpServerError getError() { return error; } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java new file mode 100644 index 000000000..52e616edc --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; + +/** + * This class is a helper for converting a duration into a string and for the + * other way. The string is in the format expected in configuration and database + * {@link #DURATION_FORMAT}. + * + */ +public class DurationHelper { + + /** + * Format of the String expected in configuration file and in the database. + */ + public static final String DURATION_FORMAT = "HH:mm:ss"; + + /** + * Converts a Duration into a formatted String + * + * @param duration + * duration, which will be converted into a formatted String + * @return String in the duration format, specified at + * {@link #DURATION_FORMAT} + */ + public String durationToFormattedString(final Duration duration) { + if (duration == null) { + return null; + } + + return LocalTime.ofNanoOfDay(duration.toNanos()).format(DateTimeFormatter.ofPattern(DURATION_FORMAT)); + } + + /** + * Converts a formatted String into a Duration object. + * + * @param formattedDuration + * String in {@link #DURATION_FORMAT} + * @return duration + * @throws DateTimeParseException + * when String is in wrong format + */ + public Duration formattedStringToDuration(final String formattedDuration) throws DateTimeParseException { + if (formattedDuration == null) { + return null; + } + + final TemporalAccessor ta = DateTimeFormatter.ofPattern(DURATION_FORMAT).parse(formattedDuration.trim()); + return Duration.between(LocalTime.MIDNIGHT, LocalTime.from(ta)); + } + + /** + * converts values of time constants to a Duration object.. + * + * @param hours + * count of hours + * @param minutes + * count of minutes + * @param seconds + * count of seconds + * @return duration + */ + public Duration getDurationByTimeValues(final long hours, final long minutes, final long seconds) { + return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds); + } + + public DurationRangeValidator durationRangeValidator(final Duration min, final Duration max) { + return new DurationRangeValidator(min, max); + } + + public static class DurationRangeValidator { + final Duration min; + final Duration max; + + private DurationRangeValidator(final Duration min, final Duration max) { + this.min = min; + this.max = max; + } + + public boolean isWithinRange(final Duration duration) { + return duration.compareTo(min) > 0 && duration.compareTo(max) < 0; + } + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java new file mode 100644 index 000000000..dcc42065c --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * The {@link #InvalidTenantConfigurationKeyException} is thrown when an invalid + * configuration key is used. + * + */ +public class InvalidTenantConfigurationKeyException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_CONFIGURATION_KEY_INVALID; + + /** + * Default constructor. + */ + public InvalidTenantConfigurationKeyException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public InvalidTenantConfigurationKeyException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public InvalidTenantConfigurationKeyException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public InvalidTenantConfigurationKeyException(final String message) { + super(message, THIS_ERROR); + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java index ec5c5ec40..c83f24dc2 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java @@ -8,12 +8,21 @@ */ package org.eclipse.hawkbit.tenancy.configuration; +import java.util.Arrays; +import java.util.Optional; + +import org.eclipse.hawkbit.ControllerPollProperties; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationStringValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.springframework.context.ApplicationContext; + /** * An enum which defines the tenant specific configurations which can be - * configured for each tenant seperately. - * - * - * + * configured for each tenant separately. The non overridable properties are + * configured in {@link ControllerPollProperties} instead. * */ public enum TenantConfigurationKey { @@ -21,51 +30,68 @@ public enum TenantConfigurationKey { /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_HEADER_ENABLED("authentication.header.enabled", - "hawkbit.server.ddi.security.authentication.header.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_HEADER_ENABLED("authentication.header.enabled", "hawkbit.server.ddi.security.authentication.header.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * */ - AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME("authentication.header.authority", - "hawkbit.server.ddi.security.authentication.header.authority", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME("authentication.header.authority", "hawkbit.server.ddi.security.authentication.header.authority", String.class, Boolean.FALSE.toString(), TenantConfigurationStringValidator.class), /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED("authentication.targettoken.enabled", - "hawkbit.server.ddi.security.authentication.targettoken.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED("authentication.targettoken.enabled", "hawkbit.server.ddi.security.authentication.targettoken.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED("authentication.gatewaytoken.enabled", - "hawkbit.server.ddi.security.authentication.gatewaytoken.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED("authentication.gatewaytoken.enabled", "hawkbit.server.ddi.security.authentication.gatewaytoken.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * string value which holds the name of the security token key. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME("authentication.gatewaytoken.name", - "hawkbit.server.ddi.security.authentication.gatewaytoken.name", null), + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME("authentication.gatewaytoken.name", "hawkbit.server.ddi.security.authentication.gatewaytoken.name", String.class, null, TenantConfigurationStringValidator.class), /** * string value which holds the actual security-key of the gateway security * token. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY("authentication.gatewaytoken.key", - "hawkbit.server.ddi.security.authentication.gatewaytoken.key", null); + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY("authentication.gatewaytoken.key", "hawkbit.server.ddi.security.authentication.gatewaytoken.key", String.class, null, TenantConfigurationStringValidator.class), + + /** + * string value which holds the polling time interval in the format HH:mm:ss + */ + POLLING_TIME_INTERVAL("pollingOverdueTime", "hawkbit.controller.pollingOverdueTime", String.class, null, TenantConfigurationPollingDurationValidator.class), + + /** + * string value which holds the polling time interval in the format HH:mm:ss + */ + POLLING_OVERDUE_TIME_INTERVAL("pollingTime", "hawkbit.controller.pollingTime", String.class, null, TenantConfigurationPollingDurationValidator.class); private final String keyName; private final String defaultKeyName; + private final Class dataType; private final String defaultValue; + private final Class validator; /** + * * @param key * the property key name - * @param allowedValues + * @param defaultKeyName * the allowed values for this specific key + * @param dataType + * the class of the property + * @param defaultValue + * value which should be returned, in case there is no value in + * the database + * @param validator + * Validator which validates, that property is of correct format */ - private TenantConfigurationKey(final String key, final String defaultKeyName, final String defaultValue) { + private TenantConfigurationKey(final String key, final String defaultKeyName, final Class dataType, + final String defaultValue, final Class validator) { this.keyName = key; + this.dataType = dataType; this.defaultKeyName = defaultKeyName; this.defaultValue = defaultValue; + this.validator = validator; } /** @@ -88,4 +114,49 @@ public enum TenantConfigurationKey { public String getDefaultValue() { return defaultValue; } + + /** + * + * @return the data type of the tenant configuration value. (e.g. + * Integer.class, String.class) + */ + public Class getDataType() { + return dataType; + } + + /** + * validates if a object matches the allowed data format of the + * corresponding key + * + * @param context + * application context + * @param value + * which will be validated + * @throws TenantConfigurationValidatorException + * is thrown, when object is invalid + */ + public void validate(final ApplicationContext context, final Object value) { + final TenantConfigurationValidator createdBean = context.getAutowireCapableBeanFactory().createBean(validator); + try { + createdBean.validate(value); + } finally { + context.getAutowireCapableBeanFactory().destroyBean(createdBean); + } + } + + /** + * @param keyName + * name of the TenantConfigurationKey + * @return the TenantConfigurationKey with the name keyName + */ + public static TenantConfigurationKey fromKeyName(final String keyName) { + + final Optional optKey = Arrays.stream(TenantConfigurationKey.values()) + .filter(conf -> conf.getKeyName().equals(keyName)).findFirst(); + + if (optKey.isPresent()) { + return optKey.get(); + } + throw new InvalidTenantConfigurationKeyException("The given configuration key name does not exist."); + } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java new file mode 100644 index 000000000..8836d8578 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * specific tenant configuration validator, which validates that the given value + * is a booleans. + */ +public class TenantConfigurationBooleanValidator implements TenantConfigurationValidator { + + @Override + public Class validateToClass() { + return Boolean.class; + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java new file mode 100644 index 000000000..9696ac34c --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +import java.time.Duration; +import java.time.format.DateTimeParseException; + +import org.eclipse.hawkbit.ControllerPollProperties; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class is used to validate, that the property is a String and that it is + * in the correct duration format. + * + */ +public class TenantConfigurationPollingDurationValidator implements TenantConfigurationValidator { + + private final DurationHelper durationHelper = new DurationHelper(); + + private final Duration minDuration; + + private final Duration maxDuration; + + /** + * Constructor. + * + * @param properties + * property accessor for poll configuration + */ + @Autowired + public TenantConfigurationPollingDurationValidator(final ControllerPollProperties properties) { + minDuration = durationHelper.formattedStringToDuration(properties.getMinPollingTime()); + maxDuration = durationHelper.formattedStringToDuration(properties.getMaxPollingTime()); + } + + @Override + public void validate(final Object tenantConfigurationObject) { + TenantConfigurationValidator.super.validate(tenantConfigurationObject); + final String tenantConfigurationString = (String) tenantConfigurationObject; + + final Duration tenantConfigurationValue; + try { + tenantConfigurationValue = durationHelper.formattedStringToDuration(tenantConfigurationString); + } catch (final DateTimeParseException ex) { + throw new TenantConfigurationValidatorException( + String.format("The given configuration value is expected as a string in the format %s.", + DurationHelper.DURATION_FORMAT)); + } + + if (!durationHelper.durationRangeValidator(minDuration, maxDuration).isWithinRange(tenantConfigurationValue)) { + throw new TenantConfigurationValidatorException( + String.format("The given configuration value is not in the allowed range from %s to %s.", + durationHelper.durationToFormattedString(minDuration), + durationHelper.durationToFormattedString(maxDuration))); + } + } + + @Override + public Class validateToClass() { + return String.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java new file mode 100644 index 000000000..fc427f4ac --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * specific tenant configuration validator, which validates Strings. + */ +public class TenantConfigurationStringValidator implements TenantConfigurationValidator { + + @Override + public Class validateToClass() { + return String.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java new file mode 100644 index 000000000..dccf515e6 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * base interface for clases which can validate tenant configuration values. + * + */ +public interface TenantConfigurationValidator { + + /** + * validates the tenant configuration value + * + * @param tenantConfigurationValue + * value which will be validated. + * @throws TenantConfigurationValidatorException + * is thrown, when parameter is invalid. + */ + default void validate(final Object tenantConfigurationValue) throws TenantConfigurationValidatorException { + if (tenantConfigurationValue != null + && validateToClass().isAssignableFrom(tenantConfigurationValue.getClass())) { + return; + } + throw new TenantConfigurationValidatorException( + "The given configuration value is expected as a " + validateToClass().getSimpleName()); + } + + /** + * Return the generic class to check the object + * + * @return the class to check the value + */ + default Class validateToClass() { + return Object.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java new file mode 100644 index 000000000..dffc13dcb --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * Exception which is thrown, when the validation of the configuration value has + * not been successful. + * + */ +public class TenantConfigurationValidatorException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_CONFIGURATION_VALUE_INVALID; + + /** + * Default constructor. + */ + public TenantConfigurationValidatorException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public TenantConfigurationValidatorException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public TenantConfigurationValidatorException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public TenantConfigurationValidatorException(final String message) { + super(message, THIS_ERROR); + } + +} diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java index 58ad38c92..8f19d9f02 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java @@ -16,7 +16,7 @@ import javax.annotation.PostConstruct; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.security.CoapAnonymousPreAuthenticatedFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter; @@ -24,6 +24,7 @@ import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilt import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; import org.eclipse.hawkbit.security.PreAuthenficationFilter; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +50,7 @@ public class AmqpControllerAuthentfication { private ControllerManagement controllerManagement; @Autowired - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; @Autowired private TenantAware tenantAware; @@ -57,6 +58,9 @@ public class AmqpControllerAuthentfication { @Autowired private DdiSecurityProperties ddiSecruityProperties; + @Autowired + private SystemSecurityContext systemSecurityContext; + /** * Constructor. */ @@ -74,16 +78,16 @@ public class AmqpControllerAuthentfication { private void addFilter() { final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter( - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); filterChain.add(gatewaySecurityTokenFilter); final ControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new ControllerPreAuthenticatedSecurityHeaderFilter( ddiSecruityProperties.getRp().getCnHeader(), ddiSecruityProperties.getRp().getSslIssuerHashHeader(), - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); filterChain.add(securityHeaderFilter); final ControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new ControllerPreAuthenticateSecurityTokenFilter( - systemManagement, controllerManagement, tenantAware); + tenantConfigurationManagement, controllerManagement, tenantAware, systemSecurityContext); filterChain.add(securityTokenFilter); filterChain.add(new CoapAnonymousPreAuthenticatedFilter()); @@ -138,12 +142,15 @@ public class AmqpControllerAuthentfication { this.ddiSecruityProperties = secruityProperties; } - public void setSystemManagement(final SystemManagement systemManagement) { - this.systemManagement = systemManagement; + public void setTenantConfigurationManagement(final TenantConfigurationManagement tenantConfigurationManagement) { + this.tenantConfigurationManagement = tenantConfigurationManagement; } public void setTenantAware(final TenantAware tenantAware) { this.tenantAware = tenantAware; } + void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) { + this.systemSecurityContext = systemSecurityContext; + } } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index 501fddf81..66527727a 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -22,10 +22,12 @@ import org.eclipse.hawkbit.dmf.json.model.DownloadResponse; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp; import org.eclipse.hawkbit.security.SecurityContextTenantAware; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.junit.Before; import org.junit.Test; @@ -56,9 +58,15 @@ public class AmqpControllerAuthenticationTest { private static String CONTROLLLER_ID = "123"; private AmqpMessageHandlerService amqpMessageHandlerService; private MessageConverter messageConverter; - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; private AmqpControllerAuthentfication authenticationManager; + private static final TenantConfigurationValue CONFIG_VALUE_FALSE = TenantConfigurationValue + . builder().value(Boolean.FALSE).build(); + + private static final TenantConfigurationValue CONFIG_VALUE_TRUE = TenantConfigurationValue + . builder().value(Boolean.TRUE).build(); + @Before public void before() throws Exception { messageConverter = new Jackson2JsonMessageConverter(); @@ -74,16 +82,22 @@ public class AmqpControllerAuthenticationTest { when(secruityProperties.getRp()).thenReturn(rp); when(rp.getSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d"); authenticationManager.setSecruityProperties(secruityProperties); - systemManagement = mock(SystemManagement.class); - authenticationManager.setSystemManagement(systemManagement); - when(systemManagement.getConfigurationValue(any(), any())).thenReturn(Boolean.FALSE); + + tenantConfigurationManagement = mock(TenantConfigurationManagement.class); + authenticationManager.setTenantConfigurationManagement(tenantConfigurationManagement); + + when(tenantConfigurationManagement.getConfigurationValue(any(), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_FALSE); final ControllerManagement controllerManagement = mock(ControllerManagement.class); when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID); authenticationManager.setControllerManagement(controllerManagement); amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class)); - authenticationManager.setTenantAware(new SecurityContextTenantAware()); + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); + authenticationManager.setTenantAware(tenantAware); + final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); + authenticationManager.setSystemSecurityContext(systemSecurityContext); authenticationManager.postConstruct(); amqpMessageHandlerService.setAuthenticationManager(authenticationManager); } @@ -105,9 +119,9 @@ public class AmqpControllerAuthenticationTest { @Description("Tests authentication manager without wrong credential") public void testAuthenticationBadCredantialsWithWrongCredential() { final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); try { authenticationManager.doAuthenticate(securityToken); @@ -122,9 +136,9 @@ public class AmqpControllerAuthenticationTest { @Description("Tests authentication successfull") public void testSuccessfullAuthentication() { final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); final Authentication authentication = authenticationManager.doAuthenticate(securityToken); assertThat(authentication).isNotNull(); @@ -154,9 +168,9 @@ public class AmqpControllerAuthenticationTest { public void testAuthenticationMessageBadCredantialsWithWrongCredential() { final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); @@ -176,9 +190,9 @@ public class AmqpControllerAuthenticationTest { public void testSuccessfullMessageAuthentication() { final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java index 5782a48db..adb2858b9 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java @@ -18,7 +18,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,8 +55,9 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac private static final String CONTROLLER_DL_REQUEST_ANT_PATTERN = "/{" + TENANT_PLACE_HOLDER + "}/controller/artifacts/v1/**"; - protected SystemManagement systemManagement; + protected TenantConfigurationManagement tenantConfigurationManagement; protected TenantAware tenantAware; + protected SystemSecurityContext systemSecurityContext; private final AntPathMatcher pathExtractor; @@ -70,10 +71,11 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac * @param tenantAware * the tenant aware service */ - public AbstractHttpControllerAuthenticationFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - this.systemManagement = systemManagement; + public AbstractHttpControllerAuthenticationFilter(final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; pathExtractor = new AntPathMatcher(); } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java index 91a0cdfa6..1eb155b9a 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -35,7 +35,7 @@ public class HttpControllerPreAuthenticateSecurityTokenFilter extends AbstractHt /** * Constructor. * - * @param systemManagement + * @param tenantConfigurationManagement * the system management service to retrieve configuration * properties * @param tenantAware @@ -44,16 +44,20 @@ public class HttpControllerPreAuthenticateSecurityTokenFilter extends AbstractHt * @param controllerManagement * the controller management to retrieve the specific target * security token to verify + * @param systemSecurityContext + * the system security context */ - public HttpControllerPreAuthenticateSecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware, final ControllerManagement controllerManagement) { - super(systemManagement, tenantAware); + public HttpControllerPreAuthenticateSecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final ControllerManagement controllerManagement, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; } @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticateSecurityTokenFilter(systemManagement, controllerManagement, tenantAware); + return new ControllerPreAuthenticateSecurityTokenFilter(tenantConfigurationManagement, controllerManagement, + tenantAware, systemSecurityContext); } } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java index 2b1a4d78f..930c66dca 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.security; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -27,21 +27,25 @@ public class HttpControllerPreAuthenticatedGatewaySecurityTokenFilter /** * Constructor. * - * @param systemManagement + * @param tenantConfigurationManagement * the system management service to retrieve configuration * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * * @param systemSecurityContext the system security context */ - public HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); } @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedGatewaySecurityTokenFilter(systemManagement, tenantAware); + return new ControllerPreAuthenticatedGatewaySecurityTokenFilter(tenantConfigurationManagement, tenantAware, + systemSecurityContext); } } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java index 3d6b43657..162e2688d 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.security; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -35,18 +35,20 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract * @param caAuthorityNameHeader * the http-header which holds the ca-authority name of the * certificate - * @param systemManagement - * the system management service to retrieve configuration - * properties to check if the header authentication is enabled - * for this tenant + * @param tenantConfigurationManagement + * the tenant configuration management service to retrieve + * configuration properties to check if the header authentication + * is enabled for this tenant * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context */ public HttpControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.caCommonNameHeader = caCommonNameHeader; this.caAuthorityNameHeader = caAuthorityNameHeader; } @@ -54,7 +56,7 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { return new ControllerPreAuthenticatedSecurityHeaderFilter(caCommonNameHeader, caAuthorityNameHeader, - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index 84d1d9b60..bf805f3dd 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -14,17 +14,15 @@ import java.util.Map; import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder; -import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper; import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -51,21 +49,13 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** - * @return simple default {@link AsyncUncaughtExceptionHandler} - * implementation - * @Bean public SimpleAsyncUncaughtExceptionHandler - * simpleAsyncUncaughtExceptionHandler() { return new - * SimpleAsyncUncaughtExceptionHandler(); } - * - * /** - * @return the {@link PollConfigurationHelper} singleton bean which holds - * the polling and polling overdue configuration and make it - * accessible in beans which cannot not be autowired or retrieve - * environment variables due {@link EnvironmentAware} interface. + * @return the {@link TenantConfigurationManagement} singleton bean which + * make it accessible in beans which cannot access the service + * directly, e.g. JPA entities. */ @Bean - public PollConfigurationHelper pollConfigurationHelper() { - return PollConfigurationHelper.getInstance(); + public TenantConfigurationManagement tenantConfigurationManagement() { + return TenantConfigurationManagement.getInstance(); } /** @@ -181,5 +171,4 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { public PlatformTransactionManager transactionManager() { return new MultiTenantJpaTransactionManager(); } - } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index b6fbb6010..7131bbe0a 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -33,7 +33,9 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.Target_; +import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,6 +88,33 @@ public class ControllerManagement { @Autowired private HawkbitSecurityProperties securityProperties; + @Autowired + private TenantConfigurationRepository tenantConfigurationRepository; + + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + + /** + * Retrieves all {@link SoftwareModule}s which are assigned to the given + * {@link DistributionSet}. + * + * @param distributionSet + * the distribution set which should be assigned to the returned + * {@link SoftwareModule}s + * @return a list of {@link SoftwareModule}s assigned to given + * {@code distributionSet} + */ + @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) + public String findPollingTime() { + final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final Class propertyType = String.class; + tenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType); + final TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + return tenantConfigurationManagement + .buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue(); + } + /** * Refreshes the time of the last time the controller has been connected to * the server. diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index aac5db7f5..77b48a3cd 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -22,10 +22,8 @@ import org.eclipse.hawkbit.report.model.SystemUsageReport; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -33,12 +31,7 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.support.ConfigurableConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -53,7 +46,7 @@ import org.springframework.validation.annotation.Validated; @Transactional(readOnly = true) @Validated @Service -public class SystemManagement implements EnvironmentAware { +public class SystemManagement { @Autowired private EntityManager entityManager; @@ -113,10 +106,6 @@ public class SystemManagement implements EnvironmentAware { private final ThreadLocal createInitialTenant = new ThreadLocal<>(); - private final ConfigurableConversionService conversionService = new DefaultConversionService(); - - private Environment environment; - /** * Calculated system usage statistics, both overall for the entire system * and per tenant; @@ -320,88 +309,6 @@ public class SystemManagement implements EnvironmentAware { return tenantMetaDataRepository.save(metaData); } - /** - * Retrieves a configuration value from the e.g. tenant overwritten - * configuration values or in case the tenant does not a have a specific - * configuration the global default value hold in the {@link Environment}. - * - * @param configurationKey - * the key of the configuration - * @param propertyType - * the type of the configuration value, e.g. {@code String.class} - * , {@code Integer.class}, etc - * @return the converted configuration value either from the tenant specific - * configuration stored or from the fallback default values or - * {@code null} in case key has not been configured and not default - * value exists - * @throws ConversionFailedException - * if the property cannot be converted to the given - * {@code propertyType} - */ - @Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") - public T getConfigurationValue(final TenantConfigurationKey configurationKey, final Class propertyType) { - final TenantConfiguration tenantConfiguration = tenantConfigurationRepository - .findByKey(configurationKey.getKeyName()); - if (tenantConfiguration != null) { - return conversionService.convert(tenantConfiguration.getValue(), propertyType); - } else if (configurationKey.getDefaultKeyName() != null) { - final T defaultKeyNameValue = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType); - return defaultKeyNameValue != null ? defaultKeyNameValue - : conversionService.convert(configurationKey.getDefaultValue(), propertyType); - } - return null; - } - - /** - * Adds or updates a specific configuration for a specific tenant. - * - * @param tenantConf - * the tenant configuration object which contains the key and - * value of the specific configuration to update - * @return the added or updated TenantConfiguration - */ - @CacheEvict(value = "tenantConfiguration", key = "#tenantConf.getKey()") - @Transactional - @Modifying - public TenantConfiguration addOrUpdateConfiguration(final TenantConfiguration tenantConf) { - TenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(tenantConf.getKey()); - if (tenantConfiguration != null) { - tenantConfiguration.setValue(tenantConf.getValue()); - } else { - tenantConfiguration = new TenantConfiguration(tenantConf.getKey(), tenantConf.getValue()); - } - return tenantConfigurationRepository.save(tenantConfiguration); - } - - /** - * Deletes a specific configuration for the current tenant. - * - * @param configurationKey - * the configuration key to be deleted - */ - @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") - @Transactional - @Modifying - public void deleteConfiguration(final TenantConfigurationKey configurationKey) { - tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName()); - } - - @Transactional - public List getTenantConfigurations() { - return tenantConfigurationRepository.findAll(); - } - - /* - * (non-Javadoc) - * - * @see org.springframework.context.EnvironmentAware#setEnvironment(org. - * springframework.core.env. Environment) - */ - @Override - public void setEnvironment(final Environment environment) { - this.environment = environment; - } - private DistributionSetType createStandardSoftwareDataSetup() { // Edge Controller Linux standard setup @@ -458,7 +365,5 @@ public class SystemManagement implements EnvironmentAware { return SimpleKeyGenerator.generateKey(initialTenantCreation.toUpperCase(), initialTenantCreation.toUpperCase()); } - } - } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java new file mode 100644 index 000000000..f6b6afcfc --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationContext; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +/** + * Central tenant configuration management operations of the SP server. + */ +@Transactional(readOnly = true) +@Validated +public class TenantConfigurationManagement implements EnvironmentAware { + + private static final TenantConfigurationManagement INSTANCE = new TenantConfigurationManagement(); + + @Autowired + private TenantConfigurationRepository tenantConfigurationRepository; + + @Autowired + private ApplicationContext applicationContext; + + private final ConfigurableConversionService conversionService = new DefaultConversionService(); + + private Environment environment; + + /** + * Get Singleton instance, needed for classes which are not managed in + * Spring context + * + * @return singleton instance of TenantConfigurationManagement + */ + public static TenantConfigurationManagement getInstance() { + return INSTANCE; + } + + /** + * Retrieves a configuration value from the e.g. tenant overwritten + * configuration values or in case the tenant does not a have a specific + * configuration the global default value hold in the {@link Environment}. + * + * @param + * the type of the configuration value + * @param configurationKey + * the key of the configuration + * @param propertyType + * the type of the configuration value, e.g. {@code String.class} + * , {@code Integer.class}, etc + * @return the converted configuration value either from the tenant specific + * configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default + * value exists + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + + @Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public TenantConfigurationValue getConfigurationValue(final TenantConfigurationKey configurationKey, + final Class propertyType) { + validateTenantConfigurationDataType(configurationKey, propertyType); + + final TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + + return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration); + } + + /** + * Validates the data type of the tenant configuration. If it is possible to + * cast to the given data type. + * + * @param configurationKey + * the key + * @param propertyType + * the class + */ + protected void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, + final Class propertyType) { + if (!configurationKey.getDataType().isAssignableFrom(propertyType)) { + throw new TenantConfigurationValidatorException( + String.format("Cannot parse the database value of type %s into the type %s.", + configurationKey.getDataType(), propertyType)); + } + } + + /** + * Build the tenant configuration by the given key + * + * @param configurationKey + * the key + * @param propertyType + * the property type + * @param tenantConfiguration + * the configuration + * @return if no default value is set and no database value available + * or returns the tenant configuration value + */ + protected TenantConfigurationValue buildTenantConfigurationValueByKey( + final TenantConfigurationKey configurationKey, final Class propertyType, + final TenantConfiguration tenantConfiguration) { + if (tenantConfiguration != null) { + return TenantConfigurationValue. builder().isGlobal(false).createdBy(tenantConfiguration.getCreatedBy()) + .createdAt(tenantConfiguration.getCreatedAt()) + .lastModifiedAt(tenantConfiguration.getLastModifiedAt()) + .lastModifiedBy(tenantConfiguration.getLastModifiedBy()) + .value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build(); + + } else if (configurationKey.getDefaultKeyName() != null) { + + return TenantConfigurationValue. builder().isGlobal(true).createdBy(null).createdAt(null) + .lastModifiedAt(null).lastModifiedBy(null) + .value(getGlobalConfigurationValue(configurationKey, propertyType)).build(); + } + return null; + } + + /** + * Retrieves a configuration value from the e.g. tenant overwritten + * configuration values or in case the tenant does not a have a specific + * configuration the global default value hold in the {@link Environment}. + * + * @param configurationKey + * the key of the configuration + * @return the converted configuration value either from the tenant specific + * configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default + * value exists + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public TenantConfigurationValue getConfigurationValue(final TenantConfigurationKey configurationKey) { + return getConfigurationValue(configurationKey, configurationKey.getDataType()); + } + + /** + * returns the global configuration property either defined in the property + * file or an default value otherwise. + * + * @param + * the type of the configuration value + * @param configurationKey + * the key of the configuration + * @param propertyType + * the type of the configuration value, e.g. {@code String.class} + * , {@code Integer.class}, etc + * @return the global configured value + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in the property + * file or the default value does not match the expected type + * and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey, + final Class propertyType) { + + if (!configurationKey.getDataType().isAssignableFrom(propertyType)) { + throw new TenantConfigurationValidatorException( + String.format("Cannot parse the database value of type %s into the type %s.", + configurationKey.getDataType(), propertyType)); + } + + final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType); + + if (valueInProperties == null) { + return conversionService.convert(configurationKey.getDefaultValue(), propertyType); + } + + return valueInProperties; + } + + /** + * Adds or updates a specific configuration for a specific tenant. + * + * + * @param configurationKey + * the key of the configuration + * @param value + * the configuration value which will be written into the + * database. + * @return the configuration value which was just written into the database. + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + */ + @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @Transactional + @Modifying + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public TenantConfigurationValue addOrUpdateConfiguration(final TenantConfigurationKey configurationKey, + final T value) { + + if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) { + throw new TenantConfigurationValidatorException(String.format( + "Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value, + value.getClass(), configurationKey.getDataType())); + } + + configurationKey.validate(applicationContext, value); + + TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + + if (tenantConfiguration == null) { + tenantConfiguration = new TenantConfiguration(configurationKey.getKeyName(), value.toString()); + } else { + tenantConfiguration.setValue(value.toString()); + } + + final TenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository.save(tenantConfiguration); + + final Class clazzT = (Class) value.getClass(); + + return TenantConfigurationValue. builder().isGlobal(false) + .createdBy(updatedTenantConfiguration.getCreatedBy()) + .createdAt(updatedTenantConfiguration.getCreatedAt()) + .lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt()) + .lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy()) + .value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build(); + } + + /** + * Deletes a specific configuration for the current tenant. Does nothing in + * case there is no tenant specific configuration value. + * + * @param configurationKey + * the configuration key to be deleted + */ + @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @Transactional + @Modifying + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public void deleteConfiguration(final TenantConfigurationKey configurationKey) { + tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName()); + } + + @Override + public void setEnvironment(final Environment environment) { + this.environment = environment; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java index e487ee85a..0c4a7631c 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java @@ -38,7 +38,9 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; -import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,7 +112,7 @@ public class TargetInfo implements Persistable, Serializable { @CollectionTable(name = "sp_target_attributes", joinColumns = { @JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) // use deprecated annotation until HHH-8862 is fixed - @SuppressWarnings("deprecation") + // @org.hibernate.annotations.ForeignKey( name = "fk_targ_attrib_target" ) private final Map controllerAttributes = Collections.synchronizedMap(new HashMap()); @@ -120,6 +122,9 @@ public class TargetInfo implements Persistable, Serializable { @Column(name = "request_controller_attributes", nullable = false) private boolean requestControllerAttributes = true; + @Transient + private final DurationHelper durationHelper = new DurationHelper(); + /** * Constructor for {@link TargetStatus}. * @@ -181,7 +186,7 @@ public class TargetInfo implements Persistable, Serializable { } /** - * @param ipAddress + * @param address * the ipAddress to set * * @throws IllegalArgumentException @@ -315,17 +320,21 @@ public class TargetInfo implements Persistable, Serializable { * before this method returns {@code null} */ public PollStatus getPollStatus() { - if (lastTargetQuery != null) { - final Duration pollTime = PollConfigurationHelper.getInstance().getPollTimeInterval(); - final Duration overdueTime = PollConfigurationHelper.getInstance().getOverduePollTimeInterval(); - final LocalDateTime currentDate = LocalDateTime.now(); - final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), - ZoneId.systemDefault()); - final LocalDateTime nextPollDate = lastPollDate.plus(pollTime); - final LocalDateTime overdueDate = nextPollDate.plus(overdueTime); - return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate); + if (lastTargetQuery == null) { + return null; } - return null; + + final Duration pollTime = durationHelper.formattedStringToDuration(TenantConfigurationManagement.getInstance() + .getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue()); + final Duration overdueTime = durationHelper.formattedStringToDuration(TenantConfigurationManagement + .getInstance().getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class) + .getValue()); + final LocalDateTime currentDate = LocalDateTime.now(); + final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), + ZoneId.systemDefault()); + final LocalDateTime nextPollDate = lastPollDate.plus(pollTime); + final LocalDateTime overdueDate = nextPollDate.plus(overdueTime); + return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate); } /** @@ -368,16 +377,10 @@ public class TargetInfo implements Persistable, Serializable { return lastPollDate; } - /** - * @return the nextPollDate - */ public LocalDateTime getNextPollDate() { return nextPollDate; } - /** - * @return the overdueDate - */ public LocalDateTime getOverdueDate() { return overdueDate; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java new file mode 100644 index 000000000..d8483e12f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +/** + * represents a tenant configuration value including some meta data + * + * @param + * type of the configuration value + */ +public class TenantConfigurationValue { + + private T value; + private Long lastModifiedAt; + private String lastModifiedBy; + private Long createdAt; + private String createdBy; + private boolean isGlobal = true; + + private TenantConfigurationValue() { + } + + /** + * Gets the value. + * + * @return the value + */ + public T getValue() { + return value; + } + + /** + * Checks if is global. + * + * @return true, if is global + */ + public boolean isGlobal() { + return isGlobal; + } + + /** + * Gets the last modified at. + * + * @return the last modified at + */ + public Long getLastModifiedAt() { + return lastModifiedAt; + } + + /** + * Gets the last modified by. + * + * @return the last modified by + */ + public String getLastModifiedBy() { + return lastModifiedBy; + } + + /** + * Gets the created at. + * + * @return the created at + */ + public Long getCreatedAt() { + return createdAt; + } + + /** + * Gets the created by. + * + * @return the created by + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * Builder. + * + * @param + * the key type + * @return the tenant configuration value builder + */ + public static TenantConfigurationValueBuilder builder() { + + return new TenantConfigurationValueBuilder(); + } + + /** + * builds the tenant configuration value including some meta data + * + * @param + * type of the configuration value + */ + public static class TenantConfigurationValueBuilder { + + private final TenantConfigurationValue configuration = new TenantConfigurationValue<>(); + + /** + * Builds the. + * + * @return the tenant configuration value + */ + public TenantConfigurationValue build() { + return configuration; + } + + /** + * sets the configuration value itself + * + * @param value + * the value + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder value(final T value) { + this.configuration.value = value; + return this; + } + + /** + * set the is global attribute. + * + * @param isGlobal + * true when there is no tenant specific value, false + * otherwise + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder isGlobal(final boolean isGlobal) { + this.configuration.isGlobal = isGlobal; + return this; + } + + /** + * Sets the last modified at attribute + * + * @param lastModifiedAt + * timestamp of last modification + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder lastModifiedAt(final Long lastModifiedAt) { + this.configuration.lastModifiedAt = lastModifiedAt; + return this; + } + + /** + * sets the last modified by attribute + * + * @param lastModifiedBy + * the last modified by + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder lastModifiedBy(final String lastModifiedBy) { + this.configuration.lastModifiedBy = lastModifiedBy; + return this; + } + + /** + * sets the created at attribute + * + * @param createdAt + * defined when the configuration has been created + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder createdAt(final Long createdAt) { + this.configuration.createdAt = createdAt; + return this; + } + + /** + * sets the created by attribute + * + * @param createdBy + * defines by whom the configuration has been created + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder createdBy(final String createdBy) { + this.configuration.createdBy = createdBy; + return this; + } + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java deleted file mode 100644 index 7f5978a2d..000000000 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.repository.model.helper; - -import java.time.Duration; - -import javax.annotation.PostConstruct; - -import org.eclipse.hawkbit.ControllerPollProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.EnvironmentAware; - -/** - * A singleton bean which holds configuration of the poll time - * {@code hawkbit.server.controller.polling} and - * {@code hawkbit.server.controller.polling.overdue} to have access to the - * configuration in beans not instatinated by spring e.g. JPA entities which - * cannot implement the {@link EnvironmentAware} interface to retrieve - * environment variables. - * - * - * - * - */ -public final class PollConfigurationHelper { - - private static final Logger LOG = LoggerFactory.getLogger(PollConfigurationHelper.class); - private static final PollConfigurationHelper INSTANCE = new PollConfigurationHelper(); - - private static final int DEFAULT_OVERDUE_HOUR = 0; - private static final int DEFAULT_OVERDUE_MINUTE = 5; - private static final int DEFAULT_OVERDUE_SECOND = 0; - - private static final int DEFAULT_POLL_HOUR = 0; - private static final int DEFAULT_POLL_MINUTE = 5; - private static final int DEFAULT_POLL_SECOND = 0; - - @Autowired - private ControllerPollProperties controllerPollProperties; - - private Duration controllerPollTimeDuration; - private Duration controllerOverduePollTimeDuration; - - private PollConfigurationHelper() { - } - - /** - * Bean post construct to calcualte the poll time and poll overdue time only - * once. - */ - @PostConstruct - public void postConstruct() { - final long[] controllerPollTimeSplit = splitInterval(controllerPollProperties.getPollingTime(), - DEFAULT_POLL_HOUR, DEFAULT_POLL_MINUTE, DEFAULT_POLL_SECOND); - this.controllerPollTimeDuration = Duration.ZERO.plusHours(controllerPollTimeSplit[0]) - .plusMinutes(controllerPollTimeSplit[1]).plusSeconds(controllerPollTimeSplit[2]); - - final long[] controllerOverduePollTimeSplit = splitInterval(controllerPollProperties.getPollingOverdueTime(), - DEFAULT_OVERDUE_HOUR, DEFAULT_OVERDUE_MINUTE, DEFAULT_OVERDUE_SECOND); - this.controllerOverduePollTimeDuration = Duration.ZERO.plusHours(controllerOverduePollTimeSplit[0]) - .plusMinutes(controllerOverduePollTimeSplit[1]).plusSeconds(controllerOverduePollTimeSplit[2]); - - } - - /** - * @return the poll time interval configured in the configuration - * {@code hawkbit.server.controller.polling} or the default value - * which is {@code 00:05:00} never {@code null}. - */ - public Duration getPollTimeInterval() { - return controllerPollTimeDuration; - } - - /** - * @return the overdue poll threshold configured in the configuration - * {@code hawkbit.server.controller.polling.overdue} or the default - * value which is {@code 00:05:00} never {@code null}. - */ - public Duration getOverduePollTimeInterval() { - return controllerOverduePollTimeDuration; - } - - /** - * @return a singleton instance of the environment helper. - */ - public static PollConfigurationHelper getInstance() { - return INSTANCE; - } - - private long[] splitInterval(final String interval, final long defaultHour, final long defaultMinute, - final long defaultSecond) { - if (interval != null) { - final String[] split = interval.split(":"); - if (split.length == 3) { - try { - return new long[] { Long.parseLong(split[0]), Long.parseLong(split[1]), Long.parseLong(split[2]) }; - } catch (final NumberFormatException e) { - LOG.warn("Cannot parse the given poll configuration {}", interval); - } - } - } - LOG.warn("Using default configuration hour:{} min:{}, second:{}", defaultHour, defaultMinute, defaultSecond); - return new long[] { defaultHour, defaultMinute, defaultSecond }; - } -} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java index 18f9bca06..b7091edf8 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java @@ -42,6 +42,7 @@ import org.eclipse.hawkbit.repository.TargetInfoRepository; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetRepository; import org.eclipse.hawkbit.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantMetaDataRepository; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; @@ -180,6 +181,10 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { @Autowired protected TenantAwareCacheManager cacheManager; + + @Autowired + protected TenantConfigurationManagement tenantConfigurationManagement; + @Autowired protected RolloutManagement rolloutManagement; diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java index 706cb4479..70372fac2 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java @@ -47,7 +47,7 @@ import com.mongodb.MongoClientOptions; */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ, proxyTargetClass = true, securedEnabled = true) -@EnableConfigurationProperties({ DdiSecurityProperties.class, ControllerPollProperties.class }) +@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class }) @Profile("test") public class TestConfiguration implements AsyncConfigurer { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java index cdb76fd8d..1a1632d32 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java @@ -21,10 +21,7 @@ import org.eclipse.hawkbit.report.model.TenantUsage; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.junit.Test; -import org.springframework.core.convert.ConversionFailedException; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -151,91 +148,4 @@ public class SystemManagementTest extends AbstractIntegrationTestWithMongoDB { }); } - @Test - @Description("Tests that tenant specific configuration can be persisted and in case the tenant does not have specific configuration the default from environment is used instead.") - public void storeTenantSpecificConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String envPropertyDefault = environment.getProperty(configKey.getDefaultKeyName()); - assertThat(envPropertyDefault).isNotNull(); - - // get the configuration from the system management - final String defaultConfigValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(envPropertyDefault).isEqualTo(defaultConfigValue); - - // update the tenant specific configuration - final String newConfigurationValue = "thisIsAnotherValueForPolling"; - assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), newConfigurationValue)); - - // verify that new configuration value is used - final String updatedConfigurationValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); - assertThat(systemManagement.getTenantConfigurations()).hasSize(1); - } - - @Test - @Description("Tests that the tenant specific configuration can be updated") - public void updateTenantSpecifcConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String value1 = "firstValue"; - final String value2 = "secondValue"; - - // add value first - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), value1)); - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isEqualTo(value1); - - // update to value second - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), value2)); - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isEqualTo(value2); - } - - @Test - @Description("Tests that the configuration value can be converted from String to Integer automatically") - public void tenantConfigurationValueConversion() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final Integer value1 = 123; - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), String.valueOf(value1))); - assertThat(systemManagement.getConfigurationValue(configKey, Integer.class)).isEqualTo(value1); - } - - @Test(expected = ConversionFailedException.class) - @Description("Tests that the get configuration throws exception in case the value cannot be automatically converted from String to Integer") - public void wrongTenantConfigurationValueConversionThrowsException() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String value1 = "thisIsNotANumber"; - // add value as String - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), String.valueOf(value1))); - // try to get it as Integer - systemManagement.getConfigurationValue(configKey, Integer.class); - } - - @Test - @Description("Tests that a deletion of a tenant specific configuration deletes it from the database.") - public void deleteConfigurationReturnNullConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; - - // gateway token does not have default value so no configuration value - // is should be available - final String defaultConfigValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(defaultConfigValue).isNull(); - - // update the tenant specific configuration - final String newConfigurationValue = "thisIsAnotherValueForPolling"; - assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), newConfigurationValue)); - - // verify that new configuration value is used - final String updatedConfigurationValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); - - // delete the tenant specific configuration - systemManagement.deleteConfiguration(configKey); - // ensure that now gateway token is set again, because is deleted and - // must be null now - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isNull(); - } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java new file mode 100644 index 000000000..1b6d2942f --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java @@ -0,0 +1,226 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.junit.Assert; +import org.junit.Test; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Repository") +@Stories("Tenant Configuration Management") +public class TenantConfigurationManagementTest extends AbstractIntegrationTestWithMongoDB { + + final DurationHelper durationHelper = new DurationHelper(); + + @Test + @Description("Tests that tenant specific configuration can be persisted and in case the tenant does not have specific configuration the default from environment is used instead.") + public void storeTenantSpecificConfigurationAsString() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final String envPropertyDefault = environment.getProperty(configKey.getDefaultKeyName()); + assertThat(envPropertyDefault).isNotNull(); + + // get the configuration from the system management + final TenantConfigurationValue defaultConfigValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class); + + assertThat(defaultConfigValue.isGlobal()).isEqualTo(true); + assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault); + + // update the tenant specific configuration + final String newConfigurationValue = "thisIsAnotherTokenName"; + assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue()); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue); + + // verify that new configuration value is used + final TenantConfigurationValue updatedConfigurationValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class); + + assertThat(updatedConfigurationValue.isGlobal()).isEqualTo(false); + assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue); + // assertThat(tenantConfigurationManagement.getTenantConfigurations()).hasSize(1); + } + + @Test + @Description("Tests that the tenant specific configuration can be updated") + public void updateTenantSpecifcConfiguration() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final String value1 = "firstValue"; + final String value2 = "secondValue"; + + // add value first + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) + .isEqualTo(value1); + + // update to value second + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) + .isEqualTo(value2); + } + + @Test + @Description("Tests that the configuration value can be converted from String to Integer automatically") + public void storeAndUpdateTenantSpecificConfigurationAsBoolean() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; + final Boolean value1 = true; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) + .isEqualTo(value1); + final Boolean value2 = false; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) + .isEqualTo(value2); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Tests that the get configuration throws exception in case the value cannot be automatically converted from String to Boolean") + public void wrongTenantConfigurationValueTypeThrowsException() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; + final String value1 = "thisIsNotABoolean"; + // add value as String + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + } + + @Test + @Description("Tests that a deletion of a tenant specific configuration deletes it from the database.") + public void deleteConfigurationReturnNullConfiguration() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; + + // gateway token does not have default value so no configuration value + // is should be available + final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class) + .getValue(); + assertThat(defaultConfigValue).isNull(); + + // update the tenant specific configuration + final String newConfigurationValue = "thisIsAnotherValueForPolling"; + assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue); + + // verify that new configuration value is used + final String updatedConfigurationValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class).getValue(); + assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); + + // delete the tenant specific configuration + tenantConfigurationManagement.deleteConfiguration(configKey); + // ensure that now gateway token is set again, because is deleted and + // must be null now + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isNull(); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored but a string expected.") + public void storesIntegerWhenStringIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored but a boolean expected.") + public void storesIntegerWhenBooleanIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored as PollingTime.") + public void storesIntegerWhenPollingIntervalIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.") + public void storesWrongFormattedStringAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final String wrongFormatted = "wrongFormatted"; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.") + public void storesTooSmallDurationAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + final String tooSmallDuration = durationHelper + .durationToFormattedString(durationHelper.getDurationByTimeValues(0, 0, 1)); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tooSmallDuration); + } + + @Test + @Description("Stores a correct formatted PollignTime and reads it again.") + public void storesCorrectDurationAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + final Duration duration = durationHelper.getDurationByTimeValues(1, 2, 0); + assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2)); + + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, + durationHelper.durationToFormattedString(duration)); + + final String storedDurationString = tenantConfigurationManagement.getConfigurationValue(configKey, String.class) + .getValue(); + assertThat(duration).isEqualTo(durationHelper.formattedStringToDuration(storedDurationString)); + } + + @Test + @Description("Request a config value in a wrong Value") + public void requestConfigValueWithWrongType() { + try { + tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, + Object.class); + Assert.fail(""); + } catch (final TenantConfigurationValidatorException e) { + + } + } + + @Test + @Description("Verifies that every TenenatConfiguraationKeyName exists only once") + public void verifyThatAllKeysAreDifferent() { + final Map keynames = new HashMap(); + + Arrays.stream(TenantConfigurationKey.values()).forEach(key -> { + + if (keynames.containsKey(key.getKeyName())) { + throw new IllegalStateException("The key names are not unique"); + } + + keynames.put(key.getKeyName(), null); + }); + } + + @Test + @Description("Get TenantConfigurationKeyByName") + public void getTenantConfigurationKeyByName() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + assertThat(TenantConfigurationKey.fromKeyName(configKey.getKeyName())).isEqualTo(configKey); + } + +} diff --git a/hawkbit-repository/src/test/resources/application-test.properties b/hawkbit-repository/src/test/resources/application-test.properties index dc7549fcb..b1904f911 100644 --- a/hawkbit-repository/src/test/resources/application-test.properties +++ b/hawkbit-repository/src/test/resources/application-test.properties @@ -11,6 +11,7 @@ spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value} spring.data.mongodb.port=28017 hawkbit.server.ddi.security.authentication.header.enabled=true +hawkbit.server.ddi.security.authentication.gatewaytoken.name=TestToken hawkbit.server.artifact.repo.upload.maxFileSize=5MB diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java index 706584a64..bde96f7a8 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java @@ -116,6 +116,8 @@ public final class RestConstants { * The target URL mapping rest resource. */ public static final String TARGET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targets"; + + public static final String SYSTEM_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/system"; /** * The software module URL mapping rest resource. */ diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java similarity index 59% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java index 2d31d9f61..b0ae6e546 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java @@ -11,45 +11,33 @@ package org.eclipse.hawkbit.rest.resource.model.system; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; /** - * Response body for tenant configuration requests. - * + * A json annotated rest model for System Configuration for PUT. */ @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public class TenantConfigurationRest { +public class TenantConfigurationValueRequest { - private String key; - private String value; + @JsonProperty(required = true) + private Object value; /** - * @return the key + * + * @return the value of the TenantConfigurationValueRequest */ - public String getKey() { - return key; - } - - /** - * @param key - * the key to set - */ - public void setKey(final String key) { - this.key = key; - } - - /** - * @return the value - */ - public String getValue() { + public Object getValue() { return value; } /** + * Sets the TenantConfigurationValueRequest + * * @param value - * the value to set */ - public void setValue(final String value) { + public void setValue(final Object value) { this.value = value; } + } diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java new file mode 100644 index 000000000..47dcfebe0 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.hawkbit.rest.resource.model.system; + +import org.springframework.hateoas.ResourceSupport; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * A json annotated rest model for a tenant configuration value to RESTful API + * representation. + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class TenantConfigurationValueRest extends ResourceSupport { + + @JsonInclude(Include.ALWAYS) + private Object value; + + @JsonInclude(Include.ALWAYS) + private boolean isGlobal = true; + + private Long lastModifiedAt; + private String lastModifiedBy; + private Long createdAt; + private String createdBy; + + public Object getValue() { + return value; + } + + public void setValue(final Object value) { + this.value = value; + } + + public boolean isGlobal() { + return isGlobal; + } + + public void setGlobal(final boolean isGlobal) { + this.isGlobal = isGlobal; + } + + public Long getLastModifiedAt() { + return lastModifiedAt; + } + + public void setLastModifiedAt(final Long lastModifiedAt) { + this.lastModifiedAt = lastModifiedAt; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(final String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(final Long createdAt) { + this.createdAt = createdAt; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(final String createdBy) { + this.createdBy = createdBy; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java similarity index 95% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java index e430cba1c..35d35c0b1 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import java.util.Collection; diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java similarity index 97% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java index 84ce69948..c69201d69 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import java.util.List; diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java similarity index 96% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java index b300d94de..bc8653b36 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java index 6928ee7b5..ff1e26f8b 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import org.eclipse.hawkbit.ControllerPollProperties; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.cache.CacheWriteNotify; import org.eclipse.hawkbit.controller.model.ActionFeedback; @@ -77,8 +76,6 @@ public class RootController { private static final Logger LOG = LoggerFactory.getLogger(RootController.class); private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({})."; - private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server."; - @Autowired private ControllerManagement controllerManagement; @@ -88,9 +85,6 @@ public class RootController { @Autowired private ArtifactManagement artifactManagement; - @Autowired - private ControllerPollProperties controllerPollProperties; - @Autowired private CacheWriteNotify cacheWriteNotify; @@ -156,7 +150,7 @@ public class RootController { return new ResponseEntity<>( DataConversionHelper.fromTarget(target, controllerManagement.findActionByTargetAndActive(target), - controllerPollProperties.getPollingTime(), tenantAware), + controllerManagement.findPollingTime(), tenantAware), HttpStatus.OK); } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java index f054e4bc5..25ddc7c4c 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java @@ -65,6 +65,8 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_INVALID, HttpStatus.BAD_REQUEST); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_KEY_INVALID, HttpStatus.BAD_REQUEST); } private static HttpStatus getStatusOrDefault(final SpServerError error) { diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java index 7213eae6d..b3a461915 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java @@ -17,11 +17,10 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.report.model.SystemUsageReport; import org.eclipse.hawkbit.report.model.TenantUsage; import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; -import org.eclipse.hawkbit.rest.resource.model.system.CacheRest; -import org.eclipse.hawkbit.rest.resource.model.system.SystemStatisticsRest; -import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationRest; -import org.eclipse.hawkbit.rest.resource.model.system.TenantSystemUsageRest; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.CacheRest; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.SystemStatisticsRest; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.TenantSystemUsageRest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,7 +31,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -53,6 +51,9 @@ public class SystemManagementResource { @Autowired private SystemManagement systemManagement; + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + @Autowired private CacheManager cacheManager; @@ -129,24 +130,6 @@ public class SystemManagementResource { return ResponseEntity.ok(cacheNames); } - /** - * Adds or updates a configuration for a specific tenant to the tenant - * configuration. - * - * @param configuration - * the configuration value to add or update - * @param key - * the key of the configuration to add or update - * @return the response entity with status OK. - */ - @RequestMapping(method = RequestMethod.PUT, value = "/conf/{key}") - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) - public ResponseEntity addUpdateConfig(@RequestBody final TenantConfigurationRest configuration, - @PathVariable final String key) { - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(key, configuration.getValue())); - return ResponseEntity.ok().build(); - } - private CacheRest cacheRest(final Cache cache) { final Object nativeCache = cache.getNativeCache(); if (nativeCache instanceof com.google.common.cache.Cache) { diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java new file mode 100644 index 000000000..0f8119b53 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRest; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + */ +public class SystemMapper { + + private SystemMapper() { + // Utility class + } + + /** + * @param tenantConfigurationManagement + * instance of TenantConfigurationManagement + * @return a map of all existing configuration values + */ + public static Map toResponse( + final TenantConfigurationManagement tenantConfigurationManagement) { + + final Map configurationMap = new HashMap<>(); + + for (final TenantConfigurationKey key : TenantConfigurationKey.values()) { + configurationMap.put(key.getKeyName(), + toResponse(key.getKeyName(), tenantConfigurationManagement.getConfigurationValue(key))); + } + + return configurationMap; + } + + /** + * maps a TenantConfigurationValue from the repository model to a + * TenantConfigurationValueRest, the RESTful model. + * + * @param repoConfValue + * configuration value as repository model + * @return configuration value as RESTful model + */ + public static TenantConfigurationValueRest toResponse(final String key, + final TenantConfigurationValue repoConfValue) { + final TenantConfigurationValueRest restConfValue = new TenantConfigurationValueRest(); + + restConfValue.setValue(repoConfValue.getValue()); + restConfValue.setGlobal(repoConfValue.isGlobal()); + restConfValue.setCreatedAt(repoConfValue.getCreatedAt()); + restConfValue.setCreatedBy(repoConfValue.getCreatedBy()); + restConfValue.setLastModifiedAt(repoConfValue.getLastModifiedAt()); + restConfValue.setLastModifiedBy(repoConfValue.getLastModifiedBy()); + + restConfValue.add(linkTo(methodOn(SystemResource.class).getConfigurationValue(key)).withRel("self")); + + return restConfValue; + } +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java new file mode 100644 index 000000000..9f2201868 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRequest; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRest; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling tenant specific configuration operations. + * + * + * + * + */ +@RestController +@RequestMapping(RestConstants.SYSTEM_V1_REQUEST_MAPPING) +public class SystemResource { + + private static final Logger LOG = LoggerFactory.getLogger(SystemResource.class); + + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getSystem() { + final ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(linkTo(methodOn(SystemResource.class).getSystemConfiguration()).withRel("configs")); + return ResponseEntity.ok(resourceSupport); + } + + /** + * @return a Map of all configuration values. + */ + @RequestMapping(method = RequestMethod.GET, value = "/configs", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity> getSystemConfiguration() { + return new ResponseEntity<>(SystemMapper.toResponse(tenantConfigurationManagement), HttpStatus.OK); + } + + /** + * Handles the DELETE request of deleting a tenant specific configuration + * value within SP. + * + * @param keyName + * the Name of the configuration key + * @return If the given configuration value exists and could be deleted Http + * OK. In any failure the JsonResponseExceptionHandler is handling + * the response. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/configs/{keyName}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity deleteConfigurationValue(@PathVariable final String keyName) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + tenantConfigurationManagement.deleteConfiguration(configKey); + + LOG.debug("{} config value deleted, return status {}", keyName, HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + /** + * Handles the GET request of deleting a tenant specific configuration value + * within SP. + * + * @param keyName + * the Name of the configuration key + * @return If the given configuration value exists and could be get Http OK. + * In any failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/configs/{keyName}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getConfigurationValue(@PathVariable final String keyName) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + LOG.debug("{} config value getted, return status {}", keyName, HttpStatus.OK); + return new ResponseEntity<>(SystemMapper.toResponse(configKey.getKeyName(), + tenantConfigurationManagement.getConfigurationValue(configKey)), HttpStatus.OK); + } + + /** + * Handles the GET request of deleting a tenant specific configuration value + * within SP. + * + * @param keyName + * the Name of the configuration key + * @param configurationValueRest + * the new value for the configuration + * @return If the given configuration value exists and could be get Http OK. + * In any failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.PUT, value = "/configs/{keyName}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateConfigurationValue(@PathVariable final String keyName, + @RequestBody final TenantConfigurationValueRequest configurationValueRest) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + final TenantConfigurationValue updatedValue = tenantConfigurationManagement + + .addOrUpdateConfiguration(configKey, configurationValueRest.getValue()); + return new ResponseEntity<>(SystemMapper.toResponse(keyName, updatedValue), HttpStatus.OK); + } + +} \ No newline at end of file diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java index 45bf5755b..a6e457f3a 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java @@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.rest.resource.JsonBuilder; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.util.IpUtil; import org.junit.Test; import org.springframework.hateoas.MediaTypes; @@ -73,7 +74,7 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { @Test @Description("Ensures that target poll request does not change audit data on the entity.") @WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET, - SpPermission.CREATE_TARGET }) + SpPermission.CREATE_TARGET }, allSpPermissions = false) public void targetPollDoesNotModifyAuditData() throws Exception { // create target first with "knownPrincipal" user and audit data final String knownTargetControllerId = "target1"; @@ -133,6 +134,28 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { .andExpect(status().isMethodNotAllowed()); } + @Test + @Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.") + @WithUser(principal = "knownpricipal", allSpPermissions = false) + public void pollWithModifiedGloablPollingTime() throws Exception { + securityRule.runAs( + WithSpringAuthorityRule.withUser("tenantadmin", SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION), + () -> { + tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL, + "00:02:00"); + return null; + }); + + securityRule.runAs( + WithSpringAuthorityRule.withUser("controller", SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS), () -> { + mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON)) + .andExpect(jsonPath("$config.polling.sleep", equalTo("00:02:00"))); + return null; + }); + } + @Test @Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.") public void rootRsNotModified() throws Exception { diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java new file mode 100644 index 000000000..39e571a2c --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.MockMvcResultPrinter; +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Management RESTful API") +@Stories("ConfigurationResource") +public class ConfigurationResourceTest extends AbstractIntegrationTest { + + private static String BASE_JSON_REQUEST_STRING = "{\"value\":\"%s\"}"; + + @Test + @Description("perform a GET request on all existing configurations.") + public void getConfigurationValues() throws Exception { + + final ResultActions resultActions = mvc.perform(get(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.*", hasSize(TenantConfigurationKey.values().length))); + + for (final TenantConfigurationKey key : TenantConfigurationKey.values()) { + + final TenantConfigurationValue confValue = tenantConfigurationManagement.getConfigurationValue(key); + resultActions.andExpect(jsonPath("$.['" + key.getKeyName() + "'].value", equalTo(confValue.getValue()))) + .andExpect(jsonPath("$.['" + key.getKeyName() + "'].global", equalTo(confValue.isGlobal()))); + } + } + + @Test + @Description("perform a GET request on a existing configuration key.") + public void getConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String notGlobalValue = "notTheGlobalHeaderAuthoryName"; + + tenantConfigurationManagement.addOrUpdateConfiguration(key, notGlobalValue); + + mvc.perform(get(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("value", equalTo(notGlobalValue))).andExpect(jsonPath("global", equalTo(false))) + .andExpect(jsonPath("createdAt", notNullValue())).andExpect(jsonPath("createdBy", notNullValue())); + } + + @Test + @Description("perform a PUT request on a existing configuration key with a valid value.") + public void putConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String testValue = "12:12:12"; + + mvc.perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName()) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)).contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isEqualTo(testValue); + } + + @Test + @Description("perform a DELETE request on a existing configuration key.") + public void deleteConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String notGlobalValue = "notTheGlobalHeaderAuthoryName"; + + tenantConfigurationManagement.addOrUpdateConfiguration(key, notGlobalValue); + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).isGlobal()).isEqualTo(false); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isEqualTo(notGlobalValue); + + mvc.perform(delete(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isNoContent()); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).isGlobal()).isEqualTo(true); + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isNotEqualTo(notGlobalValue); + } + + @Test + @Description("perform a (put) request on a not existing configuration key.") + public void putInvalidConfigurationKey() throws Exception { + + final String notExistingKey = "notExistingKey"; + final String testValue = "12:12:12"; + + final MvcResult mvcResult = mvc + .perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}", notExistingKey) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_CONFIGURATION_KEY_INVALID.getKey()); + } + + @Test + @Description("perform a put request with a not matching configuration value.") + public void putInvalidConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final String testValue = "invalidFormattedDuration"; + + final MvcResult mvcResult = mvc + .perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}", key.getKeyName()) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_CONFIGURATION_VALUE_INVALID.getKey()); + } +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index d105f8d70..5d4ab9283 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -308,6 +308,13 @@ public final class SpPermission { public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#TENANT_CONFIGURATION} + */ + public static final String HAS_AUTH_TENANT_CONFIGURATION = HAS_AUTH_PREFIX + TENANT_CONFIGURATION + + HAS_AUTH_SUFFIX; + private SpringEvalExpressions() { // utility class } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java index c1125667e..b22b54e39 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -36,8 +36,18 @@ public class SystemSecurityContext { private static final Logger logger = LoggerFactory.getLogger(SystemSecurityContext.class); + private final TenantAware tenantAware; + + /** + * Autowired constructor. + * + * @param tenantAware + * the tenant aware bean to retrieve the current tenant + */ @Autowired - private TenantAware tenantAware; + public SystemSecurityContext(final TenantAware tenantAware) { + this.tenantAware = tenantAware; + } public T runAsSystem(final Callable callable) { final SecurityContext oldContext = SecurityContextHolder.getContext(); diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java index bbffc8748..d22c432cf 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -26,14 +26,16 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe private static final Logger LOGGER = LoggerFactory.getLogger(AbstractControllerAuthenticationFilter.class); - protected final SystemManagement systemManagement; + protected final TenantConfigurationManagement tenantConfigurationManagement; protected final TenantAware tenantAware; private final SecurityConfigurationKeyTenantRunner configurationKeyTenantRunner; + protected final SystemSecurityContext systemSecurityContext; - protected AbstractControllerAuthenticationFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - this.systemManagement = systemManagement; + protected AbstractControllerAuthenticationFilter(final TenantConfigurationManagement systemManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + this.tenantConfigurationManagement = systemManagement; this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; this.configurationKeyTenantRunner = new SecurityConfigurationKeyTenantRunner(); } @@ -53,8 +55,10 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner { @Override public Boolean run() { + LOGGER.trace("retrieving configuration value for configuration key {}", getTenantConfigurationKey()); - return systemManagement.getConfigurationValue(getTenantConfigurationKey(), Boolean.class); + return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement + .getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue()); } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java index 3cf09cff8..c30f60711 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java @@ -12,7 +12,7 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -45,8 +45,8 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro /** * Constructor. * - * @param systemManagement - * the system management service to retrieve configuration + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration * properties * @param controllerManagement * the controller management to retrieve the specific target @@ -54,10 +54,15 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ - public ControllerPreAuthenticateSecurityTokenFilter(final SystemManagement systemManagement, - final ControllerManagement controllerManagement, final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public ControllerPreAuthenticateSecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, + final ControllerManagement controllerManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java index 7e8848ed8..765589df4 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -39,16 +39,20 @@ public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends Abstra /** * Constructor. * - * @param systemManagement - * the system management service to retrieve configuration + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ - public ControllerPreAuthenticatedGatewaySecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public ControllerPreAuthenticatedGatewaySecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); } @Override @@ -84,8 +88,12 @@ public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends Abstra public String run() { LOGGER.trace("retrieving configuration value for configuration key {}", TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY); - return systemManagement.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class); + + return systemSecurityContext + .runAsSystem(() -> tenantConfigurationManagement + .getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class) + .getValue()); } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java index 066efca53..c74d9182a 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -56,18 +56,20 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont * @param caAuthorityNameHeader * the http-header which holds the ca-authority name of the * certificate - * @param systemManagement - * the system management service to retrieve configuration - * properties to check if the header authentication is enabled - * for this tenant + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration + * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ public ControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.caCommonNameHeader = caCommonNameHeader; this.sslIssuerHashBasicHeader = caAuthorityNameHeader; } @@ -142,8 +144,8 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont private final class GetSecurityAuthorityNameTenantRunner implements TenantAware.TenantRunner { @Override public String run() { - return systemManagement.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class); + return tenantConfigurationManagement.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue(); } } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/AuthenticationConfigurationView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/AuthenticationConfigurationView.java index c7fa7c72b..94e852813 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/AuthenticationConfigurationView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/AuthenticationConfigurationView.java @@ -8,16 +8,13 @@ */ package org.eclipse.hawkbit.ui.tenantconfiguration; -import java.util.ArrayList; -import java.util.List; - import javax.annotation.PostConstruct; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.tenantconfiguration.authentication.AuthenticationConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.authentication.CertificateAuthenticationConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.authentication.GatewaySecurityTokenAuthenticationConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.authentication.TargetSecurityTokenAuthenticationConfigurationItem; -import org.eclipse.hawkbit.ui.tenantconfiguration.authentication.TenantConfigurationItem; import org.eclipse.hawkbit.ui.utils.I18N; import org.springframework.beans.factory.annotation.Autowired; @@ -26,7 +23,6 @@ import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.CheckBox; -import com.vaadin.ui.CustomComponent; import com.vaadin.ui.GridLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; @@ -40,17 +36,11 @@ import com.vaadin.ui.VerticalLayout; */ @SpringComponent @ViewScope -public class AuthenticationConfigurationView extends CustomComponent - implements ConfigurationGroup, TenantConfigurationItem.TenantConfigurationChangeListener { +public class AuthenticationConfigurationView extends BaseConfigurationView + implements ConfigurationGroup, ConfigurationItem.ConfigurationItemChangeListener, ValueChangeListener { - /** - * - */ private static final String DIST_CHECKBOX_STYLE = "dist-checkbox-style"; - /** - * - */ private static final long serialVersionUID = 1L; @Autowired @@ -65,8 +55,6 @@ public class AuthenticationConfigurationView extends CustomComponent @Autowired private GatewaySecurityTokenAuthenticationConfigurationItem gatewaySecurityTokenAuthenticationConfigurationItem; - private final List configurationChangeListeners = new ArrayList<>(); - private CheckBox gatewaySecTokenCheckBox; private CheckBox targetSecTokenCheckBox; @@ -100,42 +88,31 @@ public class AuthenticationConfigurationView extends CustomComponent certificateAuthCheckbox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, ""); certificateAuthCheckbox.setValue(certificateAuthenticationConfigurationItem.isConfigEnabled()); - certificateAuthCheckbox.addValueChangeListener(new AuthenticationTenantConfigurationItemChangeListener( - certificateAuthCheckbox, certificateAuthenticationConfigurationItem)); - certificateAuthenticationConfigurationItem.addConfigurationChangeListener(this); + certificateAuthCheckbox.addValueChangeListener(this); + certificateAuthenticationConfigurationItem.addChangeListener(this); gridLayout.addComponent(certificateAuthCheckbox, 0, 0); gridLayout.addComponent(certificateAuthenticationConfigurationItem, 1, 0); targetSecTokenCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, ""); targetSecTokenCheckBox.setValue(targetSecurityTokenAuthenticationConfigurationItem.isConfigEnabled()); - targetSecTokenCheckBox.addValueChangeListener(new AuthenticationTenantConfigurationItemChangeListener( - targetSecTokenCheckBox, targetSecurityTokenAuthenticationConfigurationItem)); - targetSecurityTokenAuthenticationConfigurationItem.addConfigurationChangeListener(this); + targetSecTokenCheckBox.addValueChangeListener(this); + targetSecurityTokenAuthenticationConfigurationItem.addChangeListener(this); gridLayout.addComponent(targetSecTokenCheckBox, 0, 1); gridLayout.addComponent(targetSecurityTokenAuthenticationConfigurationItem, 1, 1); gatewaySecTokenCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, ""); gatewaySecTokenCheckBox.setId("gatewaysecuritycheckbox"); gatewaySecTokenCheckBox.setValue(gatewaySecurityTokenAuthenticationConfigurationItem.isConfigEnabled()); - gatewaySecTokenCheckBox.addValueChangeListener(new AuthenticationTenantConfigurationItemChangeListener( - gatewaySecTokenCheckBox, gatewaySecurityTokenAuthenticationConfigurationItem)); - gatewaySecurityTokenAuthenticationConfigurationItem.addConfigurationChangeListener(this); + gatewaySecTokenCheckBox.addValueChangeListener(this); + gatewaySecurityTokenAuthenticationConfigurationItem.addChangeListener(this); gridLayout.addComponent(gatewaySecTokenCheckBox, 0, 2); gridLayout.addComponent(gatewaySecurityTokenAuthenticationConfigurationItem, 1, 2); vLayout.addComponent(gridLayout); rootPanel.setContent(vLayout); setCompositionRoot(rootPanel); - } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.tenantconfiguration.ConfigurationGroup#save - * () - */ @Override public void save() { certificateAuthenticationConfigurationItem.save(); @@ -143,13 +120,6 @@ public class AuthenticationConfigurationView extends CustomComponent gatewaySecurityTokenAuthenticationConfigurationItem.save(); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.tenantconfiguration.ConfigurationGroup#undo - * () - */ @Override public void undo() { certificateAuthenticationConfigurationItem.undo(); @@ -160,47 +130,37 @@ public class AuthenticationConfigurationView extends CustomComponent gatewaySecTokenCheckBox.setValue(gatewaySecurityTokenAuthenticationConfigurationItem.isConfigEnabled()); } - private void notifyConfigurationChanged() { - configurationChangeListeners.forEach(listener -> listener.configurationChanged()); - } - - @Override - public void addChangeListener(final ConfigurationGroupChangeListener listener) { - configurationChangeListeners.add(listener); - } - @Override public void configurationHasChanged() { notifyConfigurationChanged(); } - private final class AuthenticationTenantConfigurationItemChangeListener implements ValueChangeListener { + @Override + public void valueChange(final ValueChangeEvent event) { - private static final long serialVersionUID = 1L; - private final CheckBox checkBox; - private final TenantConfigurationItem configurationItem; - - private AuthenticationTenantConfigurationItemChangeListener(final CheckBox checkBox, - final TenantConfigurationItem configurationItem) { - this.checkBox = checkBox; - this.configurationItem = configurationItem; + if (!(event.getProperty() instanceof CheckBox)) { + return; } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.data.Property.ValueChangeListener#valueChange(com.vaadin. - * data.Property. ValueChangeEvent) - */ - @Override - public void valueChange(final ValueChangeEvent event) { - notifyConfigurationChanged(); - if (checkBox.getValue()) { - configurationItem.configEnable(); - } else { - configurationItem.configDisable(); - } + notifyConfigurationChanged(); + + final CheckBox checkBox = (CheckBox) event.getProperty(); + AuthenticationConfigurationItem configurationItem; + + if (checkBox == gatewaySecTokenCheckBox) { + configurationItem = gatewaySecurityTokenAuthenticationConfigurationItem; + } else if (checkBox == targetSecTokenCheckBox) { + configurationItem = targetSecurityTokenAuthenticationConfigurationItem; + } else if (checkBox == certificateAuthCheckbox) { + configurationItem = certificateAuthenticationConfigurationItem; + } else { + return; + } + + if (checkBox.getValue()) { + configurationItem.configEnable(); + } else { + configurationItem.configDisable(); } } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/BaseConfigurationView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/BaseConfigurationView.java new file mode 100644 index 000000000..559eeae02 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/BaseConfigurationView.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.tenantconfiguration; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.ui.CustomComponent; + +/** + * base class for all configuration views. This class implements the logic for + * the handling of the + * + */ +public abstract class BaseConfigurationView extends CustomComponent implements ConfigurationGroup { + + private static final long serialVersionUID = 1L; + + private final List configurationChangeListeners = new ArrayList<>(); + + protected void notifyConfigurationChanged() { + configurationChangeListeners.forEach(listener -> listener.configurationHasChanged()); + } + + @Override + public void addChangeListener(final ConfigurationItemChangeListener listener) { + configurationChangeListeners.add(listener); + } + + @Override + public boolean isUserInputValid() { + // default return value is true, because often user can only choose from + // different valid options. + return true; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationGroup.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationGroup.java index cfe85cae3..90a38dd56 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationGroup.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationGroup.java @@ -8,14 +8,14 @@ */ package org.eclipse.hawkbit.ui.tenantconfiguration; -import java.io.Serializable; +import com.vaadin.ui.Component; /** * * * */ -public interface ConfigurationGroup { +public interface ConfigurationGroup extends Component, ConfigurationItem { /** * called to store any configuration changes. @@ -26,29 +26,4 @@ public interface ConfigurationGroup { * called to rollback any configuration changes. */ void undo(); - - /** - * Adds a configuration change listener to notify about configuration - * changes. - * - * @param listener - * the listener to be notified in case the item changes some - * configuration - */ - void addChangeListener(ConfigurationGroupChangeListener listener); - - /** - * Configuration Change Listener to be notified about configuration changes - * in configuration group. - * - * - * - */ - interface ConfigurationGroupChangeListener extends Serializable { - /** - * called to notify about configuration has been changed. - */ - void configurationChanged(); - } - } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationItem.java new file mode 100644 index 000000000..142de0abb --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/ConfigurationItem.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.tenantconfiguration; + +import java.io.Serializable; + +/** + * represents an configurationItem, which can be modified by the user + */ +public interface ConfigurationItem { + + /** + * called to verify that the Input done by the user is valid + * + * @return true when the data is valid, false otherwise + */ + boolean isUserInputValid(); + + /** + * Adds a configuration change listener to notify about configuration + * changes. + * + * @param listener + * the listener to be notified in case the item changes some + * configuration + */ + void addChangeListener(final ConfigurationItemChangeListener listener); + + /** + * Configuration Change Listener to be notified about configuration changes + * in configuration group. + * + */ + @FunctionalInterface + interface ConfigurationItemChangeListener extends Serializable { + /** + * called to notify about configuration has been changed. + */ + void configurationHasChanged(); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/DefaultDistributionSetTypeLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/DefaultDistributionSetTypeLayout.java index 173ca5f9e..ec0e643de 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/DefaultDistributionSetTypeLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/DefaultDistributionSetTypeLayout.java @@ -8,9 +8,6 @@ */ package org.eclipse.hawkbit.ui.tenantconfiguration; -import java.util.ArrayList; -import java.util.List; - import javax.annotation.PostConstruct; import org.eclipse.hawkbit.repository.DistributionSetManagement; @@ -29,7 +26,6 @@ import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.Alignment; import com.vaadin.ui.ComboBox; -import com.vaadin.ui.CustomComponent; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; @@ -43,7 +39,7 @@ import com.vaadin.ui.VerticalLayout; */ @SpringComponent @ViewScope -public class DefaultDistributionSetTypeLayout extends CustomComponent implements ConfigurationGroup { +public class DefaultDistributionSetTypeLayout extends BaseConfigurationView implements ConfigurationGroup { private static final long serialVersionUID = 17896542758L; @@ -66,8 +62,6 @@ public class DefaultDistributionSetTypeLayout extends CustomComponent implements private Label changeIcon; - private final List configurationChangeListeners = new ArrayList<>(); - /** * Initialize Default Distribution Set layout. */ @@ -163,13 +157,4 @@ public class DefaultDistributionSetTypeLayout extends CustomComponent implements changeIcon.setVisible(false); } } - - private void notifyConfigurationChanged() { - configurationChangeListeners.forEach(listener -> listener.configurationChanged()); - } - - @Override - public void addChangeListener(final ConfigurationGroupChangeListener listener) { - configurationChangeListeners.add(listener); - } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/PollingConfigurationView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/PollingConfigurationView.java new file mode 100644 index 000000000..f956abe10 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/PollingConfigurationView.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.tenantconfiguration; + +import java.time.Duration; + +import javax.annotation.PostConstruct; + +import org.eclipse.hawkbit.ControllerPollProperties; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.eclipse.hawkbit.ui.tenantconfiguration.polling.DurationConfigField; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; +import com.vaadin.ui.VerticalLayout; + +/** + * View to configure the polling interval and the overdue time. + * + * + */ +@SpringComponent +@ViewScope +public class PollingConfigurationView extends BaseConfigurationView + implements ConfigurationGroup, ConfigurationItem.ConfigurationItemChangeListener { + + private static final long serialVersionUID = 1L; + + @Autowired + private I18N i18n; + + @Autowired + private ControllerPollProperties controllerPollProperties; + + @Autowired + private transient TenantConfigurationManagement tenantConfigurationManagement; + + private DurationConfigField fieldPollTime = null; + private DurationConfigField fieldPollingOverdueTime = null; + + private Duration minDuration; + private Duration maxDuration; + private Duration globalPollTime; + private Duration globalOverdueTime; + + private Duration tenantPollTime = null; + private Duration tenantOverdueTime = null; + + private final DurationHelper durationHelper = new DurationHelper(); + + /** + * Initialize Authentication Configuration layout. + */ + @PostConstruct + public void init() { + + minDuration = durationHelper.formattedStringToDuration(controllerPollProperties.getMinPollingTime()); + maxDuration = durationHelper.formattedStringToDuration(controllerPollProperties.getMaxPollingTime()); + globalPollTime = durationHelper.formattedStringToDuration(tenantConfigurationManagement + .getGlobalConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class)); + globalOverdueTime = durationHelper.formattedStringToDuration(tenantConfigurationManagement + .getGlobalConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class)); + + final TenantConfigurationValue pollTimeConfValue = tenantConfigurationManagement + .getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class); + if (!pollTimeConfValue.isGlobal()) { + tenantPollTime = durationHelper.formattedStringToDuration(pollTimeConfValue.getValue()); + } + + final TenantConfigurationValue overdueTimeConfValue = tenantConfigurationManagement + .getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class); + if (!overdueTimeConfValue.isGlobal()) { + tenantOverdueTime = durationHelper.formattedStringToDuration(overdueTimeConfValue.getValue()); + } + + final Panel rootPanel = new Panel(); + rootPanel.setSizeFull(); + rootPanel.addStyleName("config-panel"); + + final VerticalLayout vLayout = new VerticalLayout(); + vLayout.setMargin(true); + + final Label headerDisSetType = new Label(i18n.get("configuration.polling.title")); + headerDisSetType.addStyleName("config-panel-header"); + vLayout.addComponent(headerDisSetType); + + fieldPollTime = DurationConfigField.builder().caption(i18n.get("configuration.polling.time")) + .checkBoxTooltip(i18n.get("configuration.polling.custom.value")).range(minDuration, maxDuration) + .globalDuration(globalPollTime).tenantDuration(tenantPollTime).build(); + fieldPollTime.addChangeListener(this); + vLayout.addComponent(fieldPollTime); + + fieldPollingOverdueTime = DurationConfigField.builder().caption(i18n.get("configuration.polling.overduetime")) + .checkBoxTooltip(i18n.get("configuration.polling.custom.value")).range(minDuration, maxDuration) + .globalDuration(globalOverdueTime).tenantDuration(tenantOverdueTime).build(); + fieldPollingOverdueTime.addChangeListener(this); + vLayout.addComponent(fieldPollingOverdueTime); + + rootPanel.setContent(vLayout); + setCompositionRoot(rootPanel); + } + + @Override + public void save() { + // make sure values are only saved, when the value has been changed + + if (!compareDurations(tenantPollTime, fieldPollTime.getValue())) { + tenantPollTime = fieldPollTime.getValue(); + saveDurationConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, tenantPollTime); + } + + if (!compareDurations(tenantOverdueTime, fieldPollingOverdueTime.getValue())) { + tenantOverdueTime = fieldPollingOverdueTime.getValue(); + saveDurationConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, tenantOverdueTime); + } + } + + private void saveDurationConfigurationValue(final TenantConfigurationKey key, final Duration duration) { + if (duration == null) { + tenantConfigurationManagement.deleteConfiguration(key); + } else { + tenantConfigurationManagement.addOrUpdateConfiguration(key, + durationHelper.durationToFormattedString(duration)); + } + } + + @Override + public void undo() { + fieldPollTime.setValue(tenantPollTime); + fieldPollingOverdueTime.setValue(tenantOverdueTime); + } + + @Override + public boolean isUserInputValid() { + return fieldPollTime.isUserInputValid() && fieldPollingOverdueTime.isUserInputValid(); + } + + @Override + public void configurationHasChanged() { + notifyConfigurationChanged(); + } + + private boolean compareDurations(final Duration d1, final Duration d2) { + if (d1 == null && d2 == null) { + return true; + } + + if (d1 != null) { + return d1.equals(d2); + } + + // d1 == null, d2 != null + return false; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/TenantConfigurationDashboardView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/TenantConfigurationDashboardView.java index 1376e17d5..8a22064ab 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/TenantConfigurationDashboardView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/TenantConfigurationDashboardView.java @@ -8,11 +8,16 @@ */ package org.eclipse.hawkbit.ui.tenantconfiguration; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; + import org.eclipse.hawkbit.ui.HawkbitUI; import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; -import org.eclipse.hawkbit.ui.tenantconfiguration.ConfigurationGroup.ConfigurationGroupChangeListener; +import org.eclipse.hawkbit.ui.tenantconfiguration.ConfigurationItem.ConfigurationItemChangeListener; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; @@ -33,14 +38,13 @@ import com.vaadin.ui.VerticalLayout; /** * Main UI for the system configuration view. - * + * * * */ @SpringView(name = TenantConfigurationDashboardView.VIEW_NAME, ui = HawkbitUI.class) @ViewScope -public class TenantConfigurationDashboardView extends CustomComponent - implements View, ConfigurationGroupChangeListener { +public class TenantConfigurationDashboardView extends CustomComponent implements View, ConfigurationItemChangeListener { public static final String VIEW_NAME = "spSystemConfig"; private static final long serialVersionUID = 1L; @@ -51,6 +55,9 @@ public class TenantConfigurationDashboardView extends CustomComponent @Autowired private AuthenticationConfigurationView authenticationConfigurationView; + @Autowired + private PollingConfigurationView pollingConfigurationView; + @Autowired private I18N i18n; @@ -63,6 +70,18 @@ public class TenantConfigurationDashboardView extends CustomComponent private Button saveConfigurationBtn; private Button undoConfigurationBtn; + private final List configurationViews = new ArrayList<>(); + + /** + * init method adds all Configuration Views to the list of Views. + */ + @PostConstruct + public void init() { + configurationViews.add(defaultDistributionSetTypeLayout); + configurationViews.add(authenticationConfigurationView); + configurationViews.add(pollingConfigurationView); + } + @Override public void enter(final ViewChangeEvent event) { @@ -73,17 +92,16 @@ public class TenantConfigurationDashboardView extends CustomComponent rootLayout.setSizeFull(); rootLayout.setMargin(true); rootLayout.setSpacing(true); - rootLayout.addComponent(defaultDistributionSetTypeLayout); - rootLayout.addComponent(authenticationConfigurationView); + + configurationViews.forEach(view -> rootLayout.addComponent(view)); + final HorizontalLayout buttonContent = saveConfigurationButtonsLayout(); rootLayout.addComponent(buttonContent); rootLayout.setComponentAlignment(buttonContent, Alignment.BOTTOM_LEFT); rootPanel.setContent(rootLayout); setCompositionRoot(rootPanel); - authenticationConfigurationView.addChangeListener(this); - defaultDistributionSetTypeLayout.addChangeListener(this); - + configurationViews.forEach(view -> view.addChangeListener(this)); } private HorizontalLayout saveConfigurationButtonsLayout() { @@ -112,19 +130,25 @@ public class TenantConfigurationDashboardView extends CustomComponent } private void saveConfiguration() { - defaultDistributionSetTypeLayout.save(); - authenticationConfigurationView.save(); + + final boolean isUserInputValid = configurationViews.stream().allMatch(confView -> confView.isUserInputValid()); + + if (!isUserInputValid) { + uINotification.displayValidationError(i18n.get("notification.configuration.save.notpossible")); + return; + } + configurationViews.forEach(confView -> confView.save()); // More methods saveConfigurationBtn.setEnabled(false); undoConfigurationBtn.setEnabled(false); - uINotification.displaySuccess(i18n.get("notification.configuration.save")); + uINotification.displaySuccess(i18n.get("notification.configuration.save.successful")); } private void undoConfiguration() { - defaultDistributionSetTypeLayout.undo(); - authenticationConfigurationView.undo(); - + configurationViews.forEach(confView -> { + confView.undo(); + }); // More methods saveConfigurationBtn.setEnabled(false); undoConfigurationBtn.setEnabled(false); @@ -132,13 +156,13 @@ public class TenantConfigurationDashboardView extends CustomComponent /* * (non-Javadoc) - * + * * @see * org.eclipse.hawkbit.server.ui.tenantconfiguration.ConfigurationGroup. * ConfigurationGroupChangeListener #configurationChanged() */ @Override - public void configurationChanged() { + public void configurationHasChanged() { saveConfigurationBtn.setEnabled(true); undoConfigurationBtn.setEnabled(true); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AbstractAuthenticationTenantConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AbstractAuthenticationTenantConfigurationItem.java index 05d581267..0907ebf9e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AbstractAuthenticationTenantConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AbstractAuthenticationTenantConfigurationItem.java @@ -11,7 +11,7 @@ package org.eclipse.hawkbit.ui.tenantconfiguration.authentication; import java.util.ArrayList; import java.util.List; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; @@ -19,34 +19,33 @@ import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import com.vaadin.ui.VerticalLayout; /** - * Ab abstract authentication configuration item. + * abstract authentication configuration item. * * * * */ -abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLayout implements TenantConfigurationItem { +abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLayout + implements AuthenticationConfigurationItem { - /** - * - */ private static final long serialVersionUID = 1L; private final TenantConfigurationKey configurationKey; - private final transient SystemManagement systemManagement; + private final transient TenantConfigurationManagement tenantConfigurationManagement; - private final List configurationChangeListeners = new ArrayList<>(); + private final List configurationChangeListeners = new ArrayList<>(); /** * @param configurationKey * the key for this configuration - * @param systemManagement - * the system management to retrive the configuration value + * @param tenantConfigurationManagement + * the tenant configuration management to retrieve the + * configuration value */ public AbstractAuthenticationTenantConfigurationItem(final TenantConfigurationKey configurationKey, - final SystemManagement systemManagement) { + final TenantConfigurationManagement tenantConfigurationManagement) { this.configurationKey = configurationKey; - this.systemManagement = systemManagement; + this.tenantConfigurationManagement = tenantConfigurationManagement; } /** @@ -57,22 +56,16 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay addComponent(SPUIComponentProvider.getLabel(labelText, SPUILabelDefinitions.SP_LABEL_SIMPLE)); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.tenantconfiguration. - * TenantConfigurationItem# isConfigEnabled() - */ @Override public boolean isConfigEnabled() { - return systemManagement.getConfigurationValue(configurationKey, Boolean.class); + return tenantConfigurationManagement.getConfigurationValue(configurationKey, Boolean.class).getValue(); } /** * @return the systemManagement */ - protected SystemManagement getSystemManagement() { - return systemManagement; + protected TenantConfigurationManagement getTenantConfigurationManagement() { + return tenantConfigurationManagement; } /** @@ -86,16 +79,13 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay configurationChangeListeners.forEach(listener -> listener.configurationHasChanged()); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.tenantconfiguration. - * TenantConfigurationItem# addConfigurationChangeListener - * (hawkbit.server.ui.tenantconfiguration.TenantConfigurationItem. - * TenantConfigurationChangeListener) - */ @Override - public void addConfigurationChangeListener(final TenantConfigurationChangeListener listener) { + public void addChangeListener(final ConfigurationItemChangeListener listener) { configurationChangeListeners.add(listener); } + + @Override + public boolean isUserInputValid() { + return true; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TenantConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AuthenticationConfigurationItem.java similarity index 55% rename from hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TenantConfigurationItem.java rename to hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AuthenticationConfigurationItem.java index 911520e99..b6b045e1b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TenantConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/AuthenticationConfigurationItem.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.ui.tenantconfiguration.authentication; -import java.io.Serializable; +import org.eclipse.hawkbit.ui.tenantconfiguration.ConfigurationItem; import com.vaadin.ui.Component; @@ -20,7 +20,7 @@ import com.vaadin.ui.Component; * * */ -public interface TenantConfigurationItem extends Component { +public interface AuthenticationConfigurationItem extends Component, ConfigurationItem { /** * @return {@code true} if configuration is enabled, otherwise {@code false} @@ -47,28 +47,4 @@ public interface TenantConfigurationItem extends Component { */ void undo(); - /** - * Adds a configuration change listener to notify about configuration - * changes. - * - * @param listener - * the listener to be notified in case the item changes some - * configuration - */ - void addConfigurationChangeListener(TenantConfigurationChangeListener listener); - - /** - * Configuration Change Listener to be notified about configuration changes - * in configuration item. - * - * - * - */ - @FunctionalInterface - interface TenantConfigurationChangeListener extends Serializable { - /** - * called to notify about configuration has been changed. - */ - void configurationHasChanged(); - } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java index 331fc976e..cf2744718 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java @@ -10,8 +10,7 @@ package org.eclipse.hawkbit.ui.tenantconfiguration.authentication; import javax.annotation.PostConstruct; -import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.utils.I18N; @@ -54,8 +53,9 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti * the system management to retrie the configuration */ @Autowired - public CertificateAuthenticationConfigurationItem(final SystemManagement systemManagement) { - super(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, systemManagement); + public CertificateAuthenticationConfigurationItem( + final TenantConfigurationManagement tenantConfigurationManagement) { + super(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, tenantConfigurationManagement); } /** @@ -133,13 +133,12 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti @Override public void save() { if (configurationEnabledChange) { - getSystemManagement().addOrUpdateConfiguration( - new TenantConfiguration(getConfigurationKey().getKeyName(), String.valueOf(configurationEnabled))); + getTenantConfigurationManagement().addOrUpdateConfiguration(getConfigurationKey(), configurationEnabled); } if (configurationCaRootAuthorityChanged) { final String value = caRootAuthorityTextField.getValue() != null ? caRootAuthorityTextField.getValue() : ""; - getSystemManagement().addOrUpdateConfiguration(new TenantConfiguration( - TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME.getKeyName(), value)); + getTenantConfigurationManagement() + .addOrUpdateConfiguration(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, value); } } @@ -153,13 +152,15 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti configurationEnabledChange = false; configurationCaRootAuthorityChanged = false; - configurationEnabled = getSystemManagement().getConfigurationValue(getConfigurationKey(), Boolean.class); + configurationEnabled = getTenantConfigurationManagement() + .getConfigurationValue(getConfigurationKey(), Boolean.class).getValue(); caRootAuthorityTextField.setValue(getCaRootAuthorityValue()); } private String getCaRootAuthorityValue() { - return getSystemManagement() - .getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class); + return getTenantConfigurationManagement() + .getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class) + .getValue(); } private void setDetailVisible(final boolean visible) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/GatewaySecurityTokenAuthenticationConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/GatewaySecurityTokenAuthenticationConfigurationItem.java index 39196f95e..fa1ca9271 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/GatewaySecurityTokenAuthenticationConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/GatewaySecurityTokenAuthenticationConfigurationItem.java @@ -10,8 +10,7 @@ package org.eclipse.hawkbit.ui.tenantconfiguration.authentication; import javax.annotation.PostConstruct; -import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; @@ -62,12 +61,12 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac private VerticalLayout detailLayout; /** - * @param configurationKey - * @param systemManagement + * @param tenantConfigurationManagement */ @Autowired - public GatewaySecurityTokenAuthenticationConfigurationItem(final SystemManagement systemManagement) { - super(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, systemManagement); + public GatewaySecurityTokenAuthenticationConfigurationItem( + final TenantConfigurationManagement tenantConfigurationManagement) { + super(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, tenantConfigurationManagement); } /** @@ -163,13 +162,13 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac } private String getSecurityTokenName() { - return getSystemManagement().getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME, String.class); + return getTenantConfigurationManagement().getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME, String.class).getValue(); } private String getSecurityTokenKey() { - return getSystemManagement().getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class); + return getTenantConfigurationManagement().getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue(); } @Override @@ -184,20 +183,19 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac @Override public void save() { if (configurationEnabledChange) { - getSystemManagement().addOrUpdateConfiguration(new TenantConfiguration( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED.getKeyName(), - String.valueOf(configurationEnabled))); + getTenantConfigurationManagement().addOrUpdateConfiguration( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, configurationEnabled); } if (keyNameChanged) { - getSystemManagement().addOrUpdateConfiguration(new TenantConfiguration( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME.getKeyName(), - gatewayTokenNameTextField.getValue())); + getTenantConfigurationManagement().addOrUpdateConfiguration( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME, + gatewayTokenNameTextField.getValue()); } if (keyChanged) { - getSystemManagement().addOrUpdateConfiguration(new TenantConfiguration( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY.getKeyName(), - gatewayTokenkeyLabel.getValue())); + getTenantConfigurationManagement().addOrUpdateConfiguration( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, + gatewayTokenkeyLabel.getValue()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TargetSecurityTokenAuthenticationConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TargetSecurityTokenAuthenticationConfigurationItem.java index 683e1cd69..42e50da2c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TargetSecurityTokenAuthenticationConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/TargetSecurityTokenAuthenticationConfigurationItem.java @@ -10,8 +10,7 @@ package org.eclipse.hawkbit.ui.tenantconfiguration.authentication; import javax.annotation.PostConstruct; -import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.ui.utils.I18N; import org.springframework.beans.factory.annotation.Autowired; @@ -44,8 +43,9 @@ public class TargetSecurityTokenAuthenticationConfigurationItem extends Abstract * the system management to retrie the configuration */ @Autowired - public TargetSecurityTokenAuthenticationConfigurationItem(final SystemManagement systemManagement) { - super(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, systemManagement); + public TargetSecurityTokenAuthenticationConfigurationItem( + final TenantConfigurationManagement tenantConfigurationManagement) { + super(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, tenantConfigurationManagement); } /** @@ -87,15 +87,16 @@ public class TargetSecurityTokenAuthenticationConfigurationItem extends Abstract @Override public void save() { - if (configurationEnabledChange) { - getSystemManagement().addOrUpdateConfiguration( - new TenantConfiguration(getConfigurationKey().getKeyName(), String.valueOf(configurationEnabled))); + if (!configurationEnabledChange) { + return; } + getTenantConfigurationManagement().addOrUpdateConfiguration(getConfigurationKey(), configurationEnabled); } @Override public void undo() { configurationEnabledChange = false; - configurationEnabled = getSystemManagement().getConfigurationValue(getConfigurationKey(), Boolean.class); + configurationEnabled = getTenantConfigurationManagement() + .getConfigurationValue(getConfigurationKey(), Boolean.class).getValue(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationConfigField.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationConfigField.java new file mode 100644 index 000000000..8cefc15fa --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationConfigField.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.tenantconfiguration.polling; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.ui.tenantconfiguration.ConfigurationItem; + +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.GridLayout; + +/** + * The DurationConfigField consists of three vaadin fields. A {@link #Label} + * {@link #DurationField} and a {@link #CheckBox}. The user can then enter a + * duration in the DurationField or he can configure using the global duration + * by changing the CheckBox. + */ +public class DurationConfigField extends GridLayout implements ConfigurationItem { + + private static final long serialVersionUID = 1L; + + private final List configurationChangeListeners = new ArrayList<>(); + + private final CheckBox checkBox = new CheckBox(); + private final DurationField durationField = new DurationField(); + private Duration globalDuration; + + private DurationConfigField() { + super(2, 2); + + this.addStyleName("duration-config-field"); + this.setSpacing(true); + this.setImmediate(true); + this.setColumnExpandRatio(1, 1.0F); + + this.addComponent(checkBox, 0, 0); + this.setComponentAlignment(checkBox, Alignment.MIDDLE_LEFT); + + this.addComponent(durationField, 1, 0); + this.setComponentAlignment(durationField, Alignment.MIDDLE_LEFT); + + checkBox.addValueChangeListener(event->checkBoxChange()); + durationField.addValueChangeListener(event->notifyConfigurationChanged()); + } + + private void checkBoxChange() { + durationField.setEnabled(checkBox.getValue()); + + if (!checkBox.getValue()) { + durationField.setDuration(globalDuration); + } + + notifyConfigurationChanged(); + } + + /** + * has to be called before using, see Builder Implementation. + * + * @param tenantDuration + * tenant specific duration value + * @param globalDuration + * duration value which is stored in the global configuration + */ + private void init(final Duration globalDuration, final Duration tenantDuration) { + this.globalDuration = globalDuration; + this.setValue(tenantDuration); + } + + private void setCheckBoxTooltip(final String label) { + checkBox.setDescription(label); + } + + private void setAllowedRange(final Duration minimumDuration, final Duration maximumDuration) { + durationField.setMinimumDuration(minimumDuration); + durationField.setMaximumDuration(maximumDuration); + } + + /** + * Set the value of the duration field + * + * @param tenantDuration + * duration which will be set in to the duration field, when + * {@code null} the global configuration will be used. + */ + public void setValue(final Duration tenantDuration) { + if (tenantDuration == null) { + // no tenant specific configuration + checkBox.setValue(false); + durationField.setDuration(globalDuration); + durationField.setEnabled(false); + return; + } + + checkBox.setValue(true); + durationField.setDuration(tenantDuration); + durationField.setEnabled(true); + } + + /** + * @return the duration of the duration field or null, when the user has + * configured to use the global value. + */ + public Duration getValue() { + if (checkBox.getValue()) { + return durationField.getDuration(); + } + return null; + } + + @Override + public boolean isUserInputValid() { + return !checkBox.getValue() || (durationField.isValid() && durationField.getValue() != null); + } + + private void notifyConfigurationChanged() { + configurationChangeListeners.forEach(listener -> listener.configurationHasChanged()); + } + + @Override + public void addChangeListener(final ConfigurationItemChangeListener listener) { + configurationChangeListeners.add(listener); + } + + public static DurationConfigFieldBuilder builder() { + return new DurationConfigFieldBuilder(); + } + + public static class DurationConfigFieldBuilder { + private final DurationConfigField field; + + private Duration globalDuration = null; + private Duration tenantDuration = null; + + private DurationConfigFieldBuilder() { + field = new DurationConfigField(); + }; + + public DurationConfigFieldBuilder checkBoxTooltip(final String label) { + field.setCheckBoxTooltip(label); + return this; + } + + public DurationConfigFieldBuilder globalDuration(final Duration globalDuration) { + this.globalDuration = globalDuration; + return this; + } + + public DurationConfigFieldBuilder caption(final String caption) { + field.setCaption(caption); + return this; + } + + public DurationConfigFieldBuilder range(final Duration minDuration, final Duration maxDuration) { + field.setAllowedRange(minDuration, maxDuration); + return this; + } + + public DurationConfigFieldBuilder tenantDuration(final Duration tenantDuration) { + this.tenantDuration = tenantDuration; + return this; + } + + public DurationConfigField build() { + if (globalDuration == null) { + throw new IllegalStateException( + "Cannot build DurationConfigField without a value for global duration."); + } + + field.init(globalDuration, tenantDuration); + return field; + } + }; +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationField.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationField.java new file mode 100644 index 000000000..d511e275e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/polling/DurationField.java @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.tenantconfiguration.polling; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.validation.constraints.NotNull; + +import com.vaadin.data.Property; +import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.converter.Converter.ConversionException; +import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.ui.DateField; +import com.vaadin.ui.themes.ValoTheme; + +/** + * This class represents a Field which is optimized to enter a time duration in + * form HH:mm:ss (see {@link #DURATION_FORMAT_STIRNG}). It uses the vaadin + * DateField as a basic element, but the format is optimized for the duration + * input. For a correct view of the popup it is recommended not to display the + * css-class "v-datefield-calendarpanel-header" and + * "v-datefield-calendarpanel-body" (see systemconfig.scss} + */ +public class DurationField extends DateField { + + private static final long serialVersionUID = 1L; + + private static final String CSS_STYLE_NAME = "durationfield"; + + private static final String ADDITIONAL_DURATION_STRING = "HHmmss"; + private static final String DURATION_FORMAT_STIRNG = "HH:mm:ss"; + + private static final ZoneId ZONEID_UTC = ZoneId.of("+0"); + + private static final Duration MAXIMUM_DURATION = Duration.ofHours(23).plusMinutes(59).plusSeconds(59); + + private final SimpleDateFormat durationFormat = new SimpleDateFormat(DURATION_FORMAT_STIRNG); + private final SimpleDateFormat additionalFormat = new SimpleDateFormat(ADDITIONAL_DURATION_STRING); + + private Date minimumDuration; + private Date maximumDuration; + + /** + * Creates a DurationField + */ + protected DurationField() { + super(); + + this.setTimeZone(TimeZone.getTimeZone(ZONEID_UTC)); + durationFormat.setTimeZone(TimeZone.getTimeZone(ZONEID_UTC)); + additionalFormat.setTimeZone(TimeZone.getTimeZone(ZONEID_UTC)); + durationFormat.setLenient(false); + additionalFormat.setLenient(false); + + this.setResolution(Resolution.SECOND); + this.setDateFormat(DURATION_FORMAT_STIRNG); + this.addStyleName(CSS_STYLE_NAME); + this.addStyleName(ValoTheme.TEXTFIELD_TINY); + this.setWidth("100px"); + + // needed that popup shows a 24h clock + this.setLocale(Locale.GERMANY); + + this.addValueChangeListener(this); + } + + /** + * This method is called to handle a non-empty date string from the client + * if the client could not parse it as a Date. In the current case two + * different parsing schemas are tried. If parsing is not possible a + * ConversionException is thrown which marks the DurationField as invalid. + */ + @Override + protected Date handleUnparsableDateString(final String value) throws ConversionException { + + try { + return durationFormat.parse(value); + + } catch (final ParseException e1) { + try { + + return additionalFormat.parse("000000".substring(value.length() <= 6 ? value.length() : 6) + value); + } catch (final ParseException e2) { + // if Parsing is not possible ConversionException is thrown + } + } + throw new ConversionException("input is not in HH:MM:SS format."); + } + + @Override + public void valueChange(final Property.ValueChangeEvent event) { + // do not delete this method, even when removing the code inside this + // method. This method overwrites the super method, which is + // necessary, that parsing works correctly on pressing enter key + + if (!(event.getProperty() instanceof DurationField)) { + return; + } + final Date value = (Date) event.getProperty().getValue(); + + // setValue() calls valueChanged again, when the minimum is greater + // than the maximum this can lead to an endless loop + if (value != null && minimumDuration != null && maximumDuration != null + && minimumDuration.before(maximumDuration)) { + + if (compareTimeOfDates(value, maximumDuration) > 0) { + ((DateField) event.getProperty()).setValue(maximumDuration); + } + + if (compareTimeOfDates(minimumDuration, value) > 0) { + ((DateField) event.getProperty()).setValue(minimumDuration); + } + } + } + + @Override + public void validate(final Date value) throws InvalidValueException { + super.validate(value); + + if (value != null && maximumDuration != null && compareTimeOfDates(value, maximumDuration) > 0) { + throw new InvalidValueException("value is greater than the allowed maximum value"); + } + + if (value != null && minimumDuration != null && compareTimeOfDates(minimumDuration, value) > 0) { + throw new InvalidValueException("value is smaller than the allowed minimum value"); + } + } + + /** + * Sets the duration value + * + * @param duration + * duration, only values <= 23:59:59 are excepted + */ + public void setDuration(@NotNull final Duration duration) { + if (duration.compareTo(MAXIMUM_DURATION) > 0) { + throw new IllegalArgumentException("The duaration has to be smaller than 23:59:59."); + } + super.setValue(durationToDate(duration)); + } + + /** + * Gets the duration value of the TextField + * + * @return duration which is written in the vaadin Field + */ + public Duration getDuration() { + if (this.getValue() == null) { + return null; + } + return dateToDuration(this.getValue()); + } + + /** + * Sets the minimal allowed duration value as a String + * + * @param minimumDuration + * minimum Duration, only values smaller 23:59:59 are excepted + */ + public void setMinimumDuration(@NotNull final Duration minimumDuration) { + if (minimumDuration.compareTo(MAXIMUM_DURATION) > 0) { + throw new IllegalArgumentException("The minimum duaration has to be smaller than 23:59:59."); + } + this.minimumDuration = durationToDate(minimumDuration); + } + + /** + * Sets the maximum allowed duration value as a String + * + * @param maximumDuration + * maximumDuration, only values smaller 23:59:59 are excepted + */ + public void setMaximumDuration(@NotNull final Duration maximumDuration) { + if (maximumDuration.compareTo(MAXIMUM_DURATION) > 0) { + throw new IllegalArgumentException("The maximum duaration has to be smaller than 23:59:59."); + } + this.maximumDuration = durationToDate(maximumDuration); + } + + private static Date durationToDate(final Duration duration) { + if (duration.compareTo(MAXIMUM_DURATION) > 0) { + throw new IllegalArgumentException("The duaration has to be smaller than 23:59:59."); + } + + final LocalTime lt = LocalTime.ofNanoOfDay(duration.toNanos()); + return Date.from(lt.atDate(LocalDate.now(ZONEID_UTC)).atZone(ZONEID_UTC).toInstant()); + } + + private static Duration dateToDuration(final Date date) { + final LocalTime endExclusive = LocalDateTime.ofInstant(date.toInstant(), ZONEID_UTC).toLocalTime(); + return Duration.between(LocalTime.MIDNIGHT, LocalTime.from(endExclusive)); + } + + /** + * Because parsing done by base class returns a different date than parsing + * done by the user or converting duration to a date. But for the + * DurationField comparison only the time is important. This function helps + * comparing the time and ignores the values for day, month and year. + * + * @param d1 + * date, which time will compared with the time of d2 + * @param d2 + * date, which time will compared with the time of d1 + * @return the value 0 if the time represented d1 is equal to the time + * represented by d2; a value less than 0 if the time of d1 is + * before the time of d2; and a value greater than 0 if the time of + * d1 is after the time represented by d2. + */ + private int compareTimeOfDates(final Date d1, final Date d2) { + final LocalTime lt1 = LocalDateTime.ofInstant(d1.toInstant(), ZONEID_UTC).toLocalTime(); + final LocalTime lt2 = LocalDateTime.ofInstant(d2.toInstant(), ZONEID_UTC).toLocalTime(); + + return lt1.compareTo(lt2); + } +} diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/systemconfig.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/systemconfig.scss index 95eea57f2..ee88a1398 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/systemconfig.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/systemconfig.scss @@ -44,5 +44,11 @@ bottom: 13px; color: $button-icon-color; } + } -} + + .durationfield .v-datefield-calendarpanel-header, + .durationfield .v-datefield-calendarpanel-body { + display: none; + } +} \ No newline at end of file diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index c63b0ba8a..27103a0b9 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -390,12 +390,17 @@ link.support.name=Support link.usermanagement.name=User Management # System Configuration View -notification.configuration.save=Saved changes +notification.configuration.save.successful=Saved changes +notification.configuration.save.notpossible = Saving was not possible, because of invalid user input. configuration.defaultdistributionset.title=Distribution Set Type configuration.defaultdistributionset.select.label=Select the default Distribution Set type: configuration.savebutton.tooltip=Save Configurations configuration.cancellbutton.tooltip=Cancel Configurations configuration.authentication.title=Authentication Configuration +configuration.polling.title=Polling Configuration +configuration.polling.time=Polling Time +configuration.polling.overduetime=Polling Overdue Time +configuration.polling.custom.value=use a custom value #Calendar calendar.year=year diff --git a/hawkbit-ui/src/main/resources/messages_en.properties b/hawkbit-ui/src/main/resources/messages_en.properties index 762b68c56..74f50f378 100644 --- a/hawkbit-ui/src/main/resources/messages_en.properties +++ b/hawkbit-ui/src/main/resources/messages_en.properties @@ -380,6 +380,9 @@ configuration.defaultdistributionset.select.label=Select the default Distributio configuration.savebutton.tooltip=Save Configurations configuration.cancellbutton.tooltip=Cancel Configurations configuration.authentication.title=Authentication Configuration +controller.polling.title=Polling Configuration +controller.polling.time=Polling Time +controller.polling.overduetime=Polling Overdue Time #Calendar calendar.year=year