Custom Tenant configuration. (#395)

* Tenant configuration configurable.
Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-12-23 07:19:46 +01:00
committed by GitHub
parent 4d35413f71
commit feb3369858
53 changed files with 730 additions and 447 deletions

View File

@@ -7,6 +7,7 @@
# http://www.eclipse.org/legal/epl-v10.html
#
# Displayed basic auth realm
security.basic.realm=HawkBit
@@ -82,3 +83,45 @@ hawkbit.artifact.url.protocols.md5sum-http.port=${hawkbit.artifact.url.protocols
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
# DDI and download security
hawkbit.server.ddi.security.authentication.header.enabled=false
hawkbit.server.ddi.security.authentication.header.authority=
hawkbit.server.ddi.security.authentication.targettoken.enabled=false
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false
hawkbit.server.download.anonymous.enabled=false
hawkbit.server.ddi.security.authentication.gatewaytoken.key=
# Default tenant configuration properties
hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled
hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.header.enabled}
hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-header-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority
hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=${hawkbit.server.ddi.security.authentication.header.authority}
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.targettoken.enabled}
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.enabled}
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.key}
hawkbit.server.tenant.configuration.polling-time.keyName=pollingTime
hawkbit.server.tenant.configuration.polling-time.defaultValue=${hawkbit.controller.pollingTime}
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.polling-overdue-time.keyName=pollingOverdueTime
hawkbit.server.tenant.configuration.polling-overdue-time.defaultValue=${hawkbit.controller.pollingOverdueTime}
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled
hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=${hawkbit.server.download.anonymous.enabled}
hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator

View File

@@ -10,9 +10,7 @@ package org.eclipse.hawkbit;
import java.io.Serializable;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
/**
* Defines global configuration for the controllers/clients on the provisioning
@@ -20,8 +18,7 @@ import org.springframework.context.EnvironmentAware;
*
*
* 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}.
* level.
*
*/
@ConfigurationProperties(prefix = "hawkbit.controller")
@@ -29,8 +26,8 @@ public class ControllerPollProperties implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Maximum polling time that can be configured by a tenant in HH:MM:SS
* notation.
* Maximum polling time that can be configured system wide and by tenant in
* HH:MM:SS notation.
*/
private String maxPollingTime = "23:59:59";
@@ -40,6 +37,34 @@ public class ControllerPollProperties implements Serializable {
*/
private String minPollingTime = "00:00:30";
/**
* Controller polling time that can be configured system wide and by tenant
* in HH:MM:SS notation.
*/
private String pollingTime = "00:05:00";
/**
* Controller polling overdue time that can be configured system wide and by
* tenant in HH:MM:SS notation.
*/
private String pollingOverdueTime = "00:05:00";
public String getPollingTime() {
return pollingTime;
}
public void setPollingTime(final String pollingTime) {
this.pollingTime = pollingTime;
}
public String getPollingOverdueTime() {
return pollingOverdueTime;
}
public void setPollingOverdueTime(final String pollingOverdueTime) {
this.pollingOverdueTime = pollingOverdueTime;
}
public String getMaxPollingTime() {
return maxPollingTime;
}

View File

@@ -22,12 +22,52 @@ public class HawkbitServerProperties {
*/
private String url = "http://localhost:8080";
private final Anonymous anonymous = new Anonymous();
private final Build build = new Build();
public Anonymous getAnonymous() {
return anonymous;
}
public Build getBuild() {
return build;
}
/**
* Properties for anonymous API access by Devices/Controllers.
*
*/
public static class Anonymous {
private final Download download = new Download();
public Download getDownload() {
return download;
}
/**
* Properties for artifact download under anonymous API access by
* Devices/Controllers.
*
*/
public static class Download {
/**
* Unauthenticated artifact download possible if true.
*/
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
}
/**
* Build information of the hawkBit instance. Influenced by maven.
*

View File

@@ -1,168 +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.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 separately. The non overridable properties are
* configured in {@link ControllerPollProperties} instead.
*
*/
public enum TenantConfigurationKey {
/**
* boolean value {@code true} {@code false}.
*/
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", 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.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.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", 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", String.class, null, TenantConfigurationStringValidator.class),
/**
* string value which holds the polling time interval in the format HH:mm:ss
*/
POLLING_TIME_INTERVAL("pollingTime", "hawkbit.controller.pollingTime", String.class, null, TenantConfigurationPollingDurationValidator.class),
/**
* string value which holds the polling time interval in the format HH:mm:ss
*/
POLLING_OVERDUE_TIME_INTERVAL("pollingOverdueTime", "hawkbit.controller.pollingOverdueTime", String.class, null, TenantConfigurationPollingDurationValidator.class),
/**
* boolean value {@code true} {@code false}.
*/
ANONYMOUS_DOWNLOAD_MODE_ENABLED("anonymous.download.enabled", "hawkbit.server.download.anonymous.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class);
private final String keyName;
private final String defaultKeyName;
private final Class<?> dataType;
private final String defaultValue;
private final Class<? extends TenantConfigurationValidator> validator;
/**
*
* @param key
* the property key name
* @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
*/
TenantConfigurationKey(final String key, final String defaultKeyName, final Class<?> dataType,
final String defaultValue, final Class<? extends TenantConfigurationValidator> validator) {
this.keyName = key;
this.dataType = dataType;
this.defaultKeyName = defaultKeyName;
this.defaultValue = defaultValue;
this.validator = validator;
}
/**
* @return the keyName
*/
public String getKeyName() {
return keyName;
}
/**
* @return the defaultKeyName
*/
public String getDefaultKeyName() {
return defaultKeyName;
}
/**
* @return the defaultValue
*/
public String getDefaultValue() {
return defaultValue;
}
/**
*
* @return the data type of the tenant configuration value. (e.g.
* Integer.class, String.class)
*/
@SuppressWarnings("unchecked")
public <T> Class<T> getDataType() {
return (Class<T>) 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<TenantConfigurationKey> 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.");
}
}

View File

@@ -0,0 +1,169 @@
/**
* 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.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.ControllerPollProperties;
import org.eclipse.hawkbit.HawkbitServerProperties.Anonymous.Download;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
/**
* Properties for tenant configuration default values.
*
*/
@ConfigurationProperties("hawkbit.server.tenant")
public class TenantConfigurationProperties {
private final Map<String, TenantConfigurationKey> configuration = new HashMap<>();
/**
* @return full map of all configured tenant properties
*/
public Map<String, TenantConfigurationKey> getConfiguration() {
return configuration;
}
/**
* @return full list of {@link TenantConfigurationKey}s
*/
public Collection<TenantConfigurationKey> getConfigurationKeys() {
return configuration.values();
}
/**
* @param keyName
* name of the TenantConfigurationKey
* @return the TenantConfigurationKey with the name keyName
*/
public TenantConfigurationKey fromKeyName(final String keyName) {
return configuration.values().stream().filter(conf -> conf.getKeyName().equals(keyName)).findFirst()
.orElseThrow(() -> new InvalidTenantConfigurationKeyException(
"The given configuration key " + keyName + " does not exist."));
}
/**
* Tenant specific configurations which can be configured for each tenant
* separately by means of override of the system defaults.
*
*/
public static class TenantConfigurationKey {
/**
* Header based authentication enabled.
*/
public static final String AUTHENTICATION_MODE_HEADER_ENABLED = "authentication.header.enabled";
/**
* Header based authentication authority name.
*/
public static final String AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME = "authentication.header.authority";
/**
* Target token based authentication enabled.
*/
public static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
/**
* Gateway token based authentication enabled.
*/
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
/**
* Gateway token value.
*/
public static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
/**
* See system default in
* {@link ControllerPollProperties#getPollingTime()}.
*/
public static final String POLLING_TIME_INTERVAL = "pollingTime";
/**
* See system default in
* {@link ControllerPollProperties#getPollingOverdueTime()}.
*/
public static final String POLLING_OVERDUE_TIME_INTERVAL = "pollingOverdueTime";
/**
* See system default {@link Download#isEnabled()}.
*/
public static final String ANONYMOUS_DOWNLOAD_MODE_ENABLED = "anonymous.download.enabled";
private String keyName;
private String defaultValue = "";
private Class<?> dataType = String.class;
private Class<? extends TenantConfigurationValidator> validator = TenantConfigurationStringValidator.class;
public String getKeyName() {
return keyName;
}
public void setKeyName(final String keyName) {
this.keyName = keyName;
}
/**
*
* @return the data type of the tenant configuration value. (e.g.
* Integer.class, String.class)
*/
@SuppressWarnings("unchecked")
public <T> Class<T> getDataType() {
return (Class<T>) dataType;
}
public void setDataType(final Class<?> dataType) {
this.dataType = dataType;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(final String defaultValue) {
this.defaultValue = defaultValue;
}
public Class<? extends TenantConfigurationValidator> getValidator() {
return validator;
}
public void setValidator(final Class<? extends TenantConfigurationValidator> validator) {
this.validator = validator;
}
/**
* 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);
}
}
}
}

View File

@@ -26,6 +26,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
@@ -43,7 +44,7 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.util.IpUtil;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -68,7 +69,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = { CONTROLLER_ROLE,
SYSTEM_ROLE }, autoCreateTenant = false)
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetDeletedEvent.class, count = 1) })
@Expect(type = TargetDeletedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception {
mvc.perform(get("/default-tenant/", tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
@@ -92,7 +93,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET }, allSpPermissions = false)
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1) })
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void targetPollDoesNotModifyAuditData() throws Exception {
// create target first with "knownPrincipal" user and audit data
final String knownTargetControllerId = "target1";
@@ -128,7 +129,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that the system creates a new target in plug and play manner, i.e. target is authenticated but does not exist yet.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlay() throws Exception {
final long current = System.currentTimeMillis();
@@ -155,7 +157,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.")
@WithUser(principal = "knownpricipal", allSpPermissions = false)
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void pollWithModifiedGloablPollingTime() throws Exception {
securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
@@ -174,6 +177,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 7),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void rootRsNotModified() throws Exception {
final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -239,7 +249,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Description("Ensures that the target state machine of a precomissioned target switches from "
+ "UNKNOWN to REGISTERED when the target polls for the first time.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1) })
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPrecommissioned() throws Exception {
final Target target = testdataFactory.createTarget("4711");
@@ -263,7 +273,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that the source IP address of the polling target is correctly stored in repository")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlayIpAddress() throws Exception {
// test
final String knownControllerId1 = "0815";
@@ -278,7 +289,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that the source IP address of the polling target is not stored in repository if disabled")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsIpAddressNotStoredIfDisabled() throws Exception {
securityProperties.getClients().setTrackRemoteIp(false);
@@ -300,7 +312,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 3),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");

View File

@@ -37,6 +37,15 @@ flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
hawkbit.server.tenant.configuration.polling-time.keyName=pollingTime
hawkbit.server.tenant.configuration.polling-time.defaultValue=${hawkbit.controller.pollingTime}
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.polling-overdue-time.keyName=pollingOverdueTime
hawkbit.server.tenant.configuration.polling-overdue-time.defaultValue=${hawkbit.controller.pollingOverdueTime}
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.artifact.url.protocols[0].rel=download
hawkbit.artifact.url.protocols[0].protocol=http
hawkbit.artifact.url.protocols[0].supports=DMF,DDI

View File

@@ -15,6 +15,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
@@ -216,7 +217,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
localArtifact.getId(), localArtifact.getSha1Hash())),
ApiType.DMF)
.stream().collect(Collectors.toMap(e -> e.getProtocol(), e -> e.getRef())));
.stream().collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef)));
artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));

View File

@@ -44,7 +44,7 @@ import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymo
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.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -11,13 +11,14 @@ package org.eclipse.hawkbit.mgmt.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 java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValue;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
/**
* A mapper which maps repository model to RESTful model representation and
@@ -32,19 +33,17 @@ public final class MgmtSystemMapper {
/**
* @param tenantConfigurationManagement
* instance of TenantConfigurationManagement
* @param tenantConfigurationProperties
* to get defined keys
* @return a map of all existing configuration values
*/
public static Map<String, MgmtSystemTenantConfigurationValue> toResponse(
final TenantConfigurationManagement tenantConfigurationManagement) {
final TenantConfigurationManagement tenantConfigurationManagement,
final TenantConfigurationProperties tenantConfigurationProperties) {
final Map<String, MgmtSystemTenantConfigurationValue> configurationMap = new HashMap<>();
for (final TenantConfigurationKey key : TenantConfigurationKey.values()) {
configurationMap.put(key.getKeyName(),
toResponse(key.getKeyName(), tenantConfigurationManagement.getConfigurationValue(key)));
}
return configurationMap;
return tenantConfigurationProperties.getConfigurationKeys().stream()
.collect(Collectors.toMap(TenantConfigurationKey::getKeyName, key -> toResponse(key.getKeyName(),
tenantConfigurationManagement.getConfigurationValue(key.getKeyName()))));
}
/**

View File

@@ -19,7 +19,7 @@ import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationV
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSystemRestApi;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,8 +38,15 @@ public class MgmtSystemResource implements MgmtSystemRestApi {
private static final Logger LOG = LoggerFactory.getLogger(MgmtSystemResource.class);
private final TenantConfigurationManagement tenantConfigurationManagement;
private final TenantConfigurationProperties tenantConfigurationProperties;
@Autowired
private TenantConfigurationManagement tenantConfigurationManagement;
MgmtSystemResource(final TenantConfigurationManagement tenantConfigurationManagement,
final TenantConfigurationProperties tenantConfigurationProperties) {
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.tenantConfigurationProperties = tenantConfigurationProperties;
}
@Override
public ResponseEntity<ResourceSupport> getSystem() {
@@ -53,7 +60,9 @@ public class MgmtSystemResource implements MgmtSystemRestApi {
*/
@Override
public ResponseEntity<Map<String, MgmtSystemTenantConfigurationValue>> getSystemConfiguration() {
return new ResponseEntity<>(MgmtSystemMapper.toResponse(tenantConfigurationManagement), HttpStatus.OK);
return new ResponseEntity<>(
MgmtSystemMapper.toResponse(tenantConfigurationManagement, tenantConfigurationProperties),
HttpStatus.OK);
}
/**
@@ -69,9 +78,7 @@ public class MgmtSystemResource implements MgmtSystemRestApi {
@Override
public ResponseEntity<Void> deleteConfigurationValue(@PathVariable("keyName") final String keyName) {
final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName);
tenantConfigurationManagement.deleteConfiguration(configKey);
tenantConfigurationManagement.deleteConfiguration(keyName);
LOG.debug("{} config value deleted, return status {}", keyName, HttpStatus.OK);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
@@ -91,11 +98,10 @@ public class MgmtSystemResource implements MgmtSystemRestApi {
public ResponseEntity<MgmtSystemTenantConfigurationValue> getConfigurationValue(
@PathVariable("keyName") final String keyName) {
final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName);
LOG.debug("{} config value getted, return status {}", keyName, HttpStatus.OK);
return new ResponseEntity<>(MgmtSystemMapper.toResponse(configKey.getKeyName(),
tenantConfigurationManagement.getConfigurationValue(configKey)), HttpStatus.OK);
return new ResponseEntity<>(
MgmtSystemMapper.toResponse(keyName, tenantConfigurationManagement.getConfigurationValue(keyName)),
HttpStatus.OK);
}
/**
@@ -115,10 +121,8 @@ public class MgmtSystemResource implements MgmtSystemRestApi {
@PathVariable("keyName") final String keyName,
@RequestBody final MgmtSystemTenantConfigurationValueRequest configurationValueRest) {
final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName);
final TenantConfigurationValue<? extends Serializable> updatedValue = tenantConfigurationManagement
.addOrUpdateConfiguration(configKey, configurationValueRest.getValue());
.addOrUpdateConfiguration(keyName, configurationValueRest.getValue());
return new ResponseEntity<>(MgmtSystemMapper.toResponse(keyName, updatedValue), HttpStatus.OK);
}

View File

@@ -51,3 +51,11 @@ MYSQL.spring.datasource.password=
# SP Controller configuration
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
hawkbit.server.tenant.configuration.polling-time.keyName=pollingTime
hawkbit.server.tenant.configuration.polling-time.defaultValue=${hawkbit.controller.pollingTime}
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.polling-overdue-time.keyName=pollingOverdueTime
hawkbit.server.tenant.configuration.polling-overdue-time.defaultValue=${hawkbit.controller.pollingOverdueTime}
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator

View File

@@ -29,7 +29,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
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.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.security.access.prepost.PreAuthorize;

View File

@@ -643,5 +643,4 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
Target updateTarget(TargetUpdate update);
}

View File

@@ -13,7 +13,7 @@ import java.io.Serializable;
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.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.env.Environment;
@@ -29,7 +29,7 @@ public interface TenantConfigurationManagement {
* Adds or updates a specific configuration for a specific tenant.
*
*
* @param configurationKey
* @param configurationKeyName
* the key of the configuration
* @param value
* the configuration value which will be written into the
@@ -42,8 +42,7 @@ public interface TenantConfigurationManagement {
* if the property cannot be converted to the given
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(
TenantConfigurationKey configurationKey, T value);
<T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(String configurationKeyName, T value);
/**
* Build the tenant configuration by the given key
@@ -69,14 +68,14 @@ public interface TenantConfigurationManagement {
* the configuration key to be deleted
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
void deleteConfiguration(TenantConfigurationKey configurationKey);
void deleteConfiguration(String configurationKey);
/**
* 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
* @param configurationKeyName
* the key of the configuration
* @return the converted configuration value either from the tenant specific
* configuration stored or from the fall back default values or
@@ -90,7 +89,7 @@ public interface TenantConfigurationManagement {
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(TenantConfigurationKey configurationKey);
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName);
/**
* Retrieves a configuration value from the e.g. tenant overwritten
@@ -99,7 +98,7 @@ public interface TenantConfigurationManagement {
*
* @param <T>
* the type of the configuration value
* @param configurationKey
* @param configurationKeyName
* the key of the configuration
* @param propertyType
* the type of the configuration value, e.g. {@code String.class}
@@ -116,7 +115,7 @@ public interface TenantConfigurationManagement {
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(TenantConfigurationKey configurationKey,
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName,
Class<T> propertyType);
/**
@@ -125,7 +124,7 @@ public interface TenantConfigurationManagement {
*
* @param <T>
* the type of the configuration value
* @param configurationKey
* @param configurationKeyName
* the key of the configuration
* @param propertyType
* the type of the configuration value, e.g. {@code String.class}
@@ -140,5 +139,5 @@ public interface TenantConfigurationManagement {
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T> T getGlobalConfigurationValue(TenantConfigurationKey configurationKey, Class<T> propertyType);
<T> T getGlobalConfigurationValue(String configurationKeyName, Class<T> propertyType);
}

View File

@@ -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.repository.event.remote;
import org.eclipse.hawkbit.repository.model.Target;
/**
* Event is send in case a target polls either through DDI or DMF.
*/
public class TargetPollEvent extends RemoteTenantAwareEvent {
private static final long serialVersionUID = 1L;
private String controllerId;
private String targetAdress;
/**
* Default constructor.
*/
public TargetPollEvent() {
// for serialization libs like jackson
}
public TargetPollEvent(final Target target, final String applicationId) {
super(target.getControllerId(), target.getTenant(), applicationId);
this.controllerId = target.getControllerId();
this.targetAdress = target.getTargetInfo().getAddress().toString();
}
public String getControllerId() {
return controllerId;
}
public String getTargetAdress() {
return targetAdress;
}
}

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetTagDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
@@ -94,6 +95,8 @@ public class EventType {
TYPES.put(21, SoftwareModuleCreatedEvent.class);
TYPES.put(22, SoftwareModuleDeletedEvent.class);
TYPES.put(23, SoftwareModuleUpdatedEvent.class);
TYPES.put(24, TargetPollEvent.class);
}
private int value;

View File

@@ -13,11 +13,11 @@ import java.time.Instant;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
/**
* Calculates non-persistent timestamps , e.g. the point a time a
* target is declared as overdue.<br>
* Calculates non-persistent timestamps , e.g. the point a time a target is
* declared as overdue.<br>
* Therefore tenant specific configuration may be considered.
*
*/
@@ -43,11 +43,11 @@ public final class TimestampCalculator {
- getDurationForKey(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL).toMillis();
}
private static Duration getDurationForKey(TenantConfigurationKey key) {
private static Duration getDurationForKey(final String key) {
return DurationHelper.formattedStringToDuration(getRawStringForKey(key));
}
private static String getRawStringForKey(TenantConfigurationKey key) {
private static String getRawStringForKey(final String key) {
return getTenantConfigurationManagement().getConfigurationValue(key, String.class).getValue();
}

View File

@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ToManyAttributeEntriesException;
@@ -49,11 +50,10 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
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.TenantConfiguration;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -127,14 +127,8 @@ public class JpaControllerManagement implements ControllerManagement {
@Override
public String getPollingTime() {
final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Class<String> propertyType = String.class;
JpaTenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue());
.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue());
}
@Override
@@ -203,10 +197,15 @@ public class JpaControllerManagement implements ControllerManagement {
final JpaTarget target = targetRepository.findOne(spec);
if (target == null) {
return targetManagement.createTarget(entityFactory.target().create().controllerId(controllerId)
.description("Plug and Play target: " + controllerId).name(controllerId)
final Target result = targetManagement.createTarget(entityFactory.target().create()
.controllerId(controllerId).description("Plug and Play target: " + controllerId).name(controllerId)
.status(TargetUpdateStatus.REGISTERED).lastTargetQuery(System.currentTimeMillis())
.address(Optional.ofNullable(address).map(URI::toString).orElse(null)));
afterCommit.afterCommit(
() -> eventPublisher.publishEvent(new TargetPollEvent(result, applicationContext.getId())));
return result;
}
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
@@ -224,9 +223,6 @@ public class JpaControllerManagement implements ControllerManagement {
if (lastTargetQuery != null) {
mtargetInfo.setLastTargetQuery(lastTargetQuery);
}
if (address != null) {
mtargetInfo.setAddress(address.toString());
}
if (mtargetInfo.getUpdateStatus() == TargetUpdateStatus.UNKNOWN) {
mtargetInfo.setUpdateStatus(TargetUpdateStatus.REGISTERED);
@@ -234,6 +230,12 @@ public class JpaControllerManagement implements ControllerManagement {
.publishEvent(new TargetUpdatedEvent(mtargetInfo.getTarget(), applicationContext.getId())));
}
if (address != null) {
mtargetInfo.setAddress(address.toString());
afterCommit.afterCommit(() -> eventPublisher
.publishEvent(new TargetPollEvent(mtargetInfo.getTarget(), applicationContext.getId())));
}
return targetInfoRepository.save(mtargetInfo);
}

View File

@@ -14,16 +14,15 @@ import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
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.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.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.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.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -34,22 +33,26 @@ import org.springframework.validation.annotation.Validated;
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
public class JpaTenantConfigurationManagement implements EnvironmentAware, TenantConfigurationManagement {
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private TenantConfigurationProperties tenantConfigurationProperties;
@Autowired
private ApplicationContext applicationContext;
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
private Environment environment;
@Override
@Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final TenantConfigurationKey configurationKey, final Class<T> propertyType) {
@Cacheable(value = "tenantConfiguration", key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName,
final Class<T> propertyType) {
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
@@ -69,6 +72,7 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
*/
static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
@@ -87,46 +91,44 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
} else if (configurationKey.getDefaultKeyName() != null) {
} else if (configurationKey.getDefaultValue() != null) {
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
.lastModifiedAt(null).lastModifiedBy(null)
.value(getGlobalConfigurationValue(configurationKey, propertyType)).build();
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build();
}
return null;
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final TenantConfigurationKey configurationKey) {
return getConfigurationValue(configurationKey, configurationKey.getDataType());
final String configurationKeyName) {
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
return getConfigurationValue(configurationKeyName, configurationKey.getDataType());
}
@Override
public <T> T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> 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 TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
if (!key.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(String.format(
"Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
}
final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType);
if (valueInProperties == null) {
return conversionService.convert(configurationKey.getDefaultValue(), propertyType);
}
return valueInProperties;
return conversionService.convert(key.getDefaultValue(), propertyType);
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public <T extends Serializable> TenantConfigurationValue<T> addOrUpdateConfiguration(
final TenantConfigurationKey configurationKey, final T value) {
final String configurationKeyName, final T value) {
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
throw new TenantConfigurationValidatorException(String.format(
@@ -159,15 +161,10 @@ public class JpaTenantConfigurationManagement implements EnvironmentAware, Tenan
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void deleteConfiguration(final TenantConfigurationKey configurationKey) {
tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName());
}
@Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
public void deleteConfiguration(final String configurationKeyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName);
}
}

View File

@@ -64,6 +64,7 @@ import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
@@ -94,7 +95,8 @@ import com.google.common.collect.Maps;
@EnableAspectJAutoProxy
@Configuration
@ComponentScan
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, RolloutProperties.class })
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, RolloutProperties.class,
TenantConfigurationProperties.class })
@EnableScheduling
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {

View File

@@ -48,7 +48,7 @@ import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.transaction.annotation.Isolation;
@@ -78,6 +79,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected TenantAwareCacheManager cacheManager;
@Autowired
protected TenantConfigurationProperties tenantConfigurationProperties;
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndStatus((JpaRollout) rollout, actionStatus);

View File

@@ -9,17 +9,17 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.fest.assertions.api.Assertions.fail;
import java.io.Serializable;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
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.InvalidTenantConfigurationKeyException;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
import org.junit.Assert;
import org.junit.Test;
@@ -35,13 +35,13 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@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());
final String envPropertyDefault = environment
.getProperty("hawkbit.server.ddi.security.authentication.gatewaytoken.key");
assertThat(envPropertyDefault).isNotNull();
// get the configuration from the system management
final TenantConfigurationValue<String> defaultConfigValue = tenantConfigurationManagement
.getConfigurationValue(configKey, String.class);
final TenantConfigurationValue<String> defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class);
assertThat(defaultConfigValue.isGlobal()).isEqualTo(true);
assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault);
@@ -49,11 +49,13 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
// update the tenant specific configuration
final String newConfigurationValue = "thisIsAnotherTokenName";
assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue());
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue);
tenantConfigurationManagement.addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, newConfigurationValue);
// verify that new configuration value is used
final TenantConfigurationValue<String> updatedConfigurationValue = tenantConfigurationManagement
.getConfigurationValue(configKey, String.class);
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY,
String.class);
assertThat(updatedConfigurationValue.isGlobal()).isEqualTo(false);
assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue);
@@ -63,7 +65,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@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 configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
final String value1 = "firstValue";
final String value2 = "secondValue";
@@ -81,7 +83,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@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 String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
final Boolean value1 = true;
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1);
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue())
@@ -95,7 +97,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@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 configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
final String value1 = "thisIsNotABoolean";
// add value as String
@@ -110,13 +112,13 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@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;
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
// gateway token does not have default value so no configuration value
// is should be available
// should be available
final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class)
.getValue();
assertThat(defaultConfigValue).isNull();
assertThat(defaultConfigValue).isEmpty();
// update the tenant specific configuration
final String newConfigurationValue = "thisIsAnotherValueForPolling";
@@ -131,14 +133,14 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
// 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();
// must be empty now
assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isEmpty();
}
@Test
@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 String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
final Integer wrongDataype = 123;
try {
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype);
@@ -151,7 +153,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@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 String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
final Integer wrongDataype = 123;
try {
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype);
@@ -164,7 +166,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@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 String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Integer wrongDataype = 123;
try {
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype);
@@ -177,7 +179,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@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 configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String wrongFormatted = "wrongFormatted";
try {
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted);
@@ -190,7 +192,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@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 configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String tooSmallDuration = DurationHelper
.durationToFormattedString(DurationHelper.getDurationByTimeValues(0, 0, 1));
@@ -205,7 +207,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Stores a correct formatted PollignTime and reads it again.")
public void storesCorrectDurationAsPollingInterval() {
final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0);
assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2));
@@ -233,9 +235,9 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Verifies that every TenenatConfiguraationKeyName exists only once")
public void verifyThatAllKeysAreDifferent() {
final Map<String, Void> keynames = new HashMap<String, Void>();
final Map<String, Void> keynames = new HashMap<>();
Arrays.stream(TenantConfigurationKey.values()).forEach(key -> {
tenantConfigurationProperties.getConfigurationKeys().forEach(key -> {
if (keynames.containsKey(key.getKeyName())) {
throw new IllegalStateException("The key names are not unique");
@@ -248,9 +250,20 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Get TenantConfigurationKeyByName")
public void getTenantConfigurationKeyByName() {
final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
assertThat(TenantConfigurationKey.fromKeyName(configKey.getKeyName())).isEqualTo(configKey);
assertThat(tenantConfigurationProperties.fromKeyName(configKey).getKeyName()).isEqualTo(configKey);
}
@Test
@Description("Tenant configuration which is not declared throws exception")
public void storeTenantConfigurationWhichIsNotDeclaredThrowsException() {
final String configKeyWhichDoesNotExists = "configKeyWhichDoesNotExists";
try {
tenantConfigurationManagement.addOrUpdateConfiguration(configKeyWhichDoesNotExists, "value");
fail("Expected InvalidTenantConfigurationKeyException for tenant configuration key which is not declared");
} catch (final InvalidTenantConfigurationKeyException e) {
// expected exception
}
}
}

View File

@@ -40,7 +40,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -61,7 +61,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class RSQLUtilityTest {
@Spy
private VirtualPropertyResolver macroResolver = new VirtualPropertyResolver();
private final VirtualPropertyResolver macroResolver = new VirtualPropertyResolver();
@Mock
private TenantConfigurationManagement confMgmt;
@@ -78,9 +78,9 @@ public class RSQLUtilityTest {
private Attribute attribute;
private static final TenantConfigurationValue<String> TEST_POLLING_TIME_INTERVAL = TenantConfigurationValue
.<String>builder().value("00:05:00").build();
.<String> builder().value("00:05:00").build();
private static final TenantConfigurationValue<String> TEST_POLLING_OVERDUE_TIME_INTERVAL = TenantConfigurationValue
.<String>builder().value("00:07:37").build();
.<String> builder().value("00:07:37").build();
@Test
public void wrongRsqlSyntaxThrowSyntaxException() {
@@ -111,8 +111,7 @@ public class RSQLUtilityTest {
String wrongRSQL = TargetFields.ATTRIBUTE + "==abc";
try {
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
} catch (final RSQLParameterUnsupportedFieldException e) {
}
@@ -120,8 +119,7 @@ public class RSQLUtilityTest {
wrongRSQL = TargetFields.ATTRIBUTE + ".unkwon.wrong==abc";
try {
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
} catch (final RSQLParameterUnsupportedFieldException e) {
}
@@ -141,8 +139,7 @@ public class RSQLUtilityTest {
String wrongRSQL = TargetFields.ASSIGNEDDS + "==abc";
try {
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
} catch (final RSQLParameterUnsupportedFieldException e) {
}
@@ -150,8 +147,7 @@ public class RSQLUtilityTest {
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField==abc";
try {
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
} catch (final RSQLParameterUnsupportedFieldException e) {
}
@@ -159,8 +155,7 @@ public class RSQLUtilityTest {
wrongRSQL = TargetFields.ASSIGNEDDS + ".unknownField.ToMuch==abc";
try {
RSQLUtility.parse(wrongRSQL, TargetFields.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax");
} catch (final RSQLParameterUnsupportedFieldException e) {
}
@@ -174,7 +169,7 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("version")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
// test
@@ -192,7 +187,7 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
@@ -213,7 +208,7 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
@@ -234,7 +229,7 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>greaterThanOrEqualTo(any(Expression.class), any(String.class)))
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
.thenReturn(mock(Predicate.class));
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null).toPredicate(baseSoftwareModuleRootMock,
@@ -255,8 +250,7 @@ public class RSQLUtilityTest {
// test
RSQLUtility.parse(correctRsql, TestFieldEnum.class, null).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock,
criteriaBuilderMock);
criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
@@ -291,16 +285,17 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) String.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
when(criteriaBuilderMock.<String> lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
.thenReturn(mock(Predicate.class));
// test
RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup())
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup()).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
// verification
verify(macroResolver).lookup(overdueProp);
// the macro is already replaced when passed to #lessThanOrEqualTo -> the method is never invoked with the
// the macro is already replaced when passed to #lessThanOrEqualTo ->
// the method is never invoked with the
// placeholder:
verify(criteriaBuilderMock, never()).lessThanOrEqualTo(eq(pathOfString(baseSoftwareModuleRootMock)),
eq(overduePropPlaceholder));
@@ -316,16 +311,17 @@ public class RSQLUtilityTest {
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) String.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String>lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
when(criteriaBuilderMock.<String> lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder)))
.thenReturn(mock(Predicate.class));
// test
Predicate result = RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup())
final Predicate result = RSQLUtility.parse(correctRsql, TestFieldEnum.class, setupMacroLookup())
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
// verification
verify(macroResolver).lookup(overdueProp);
// the macro is unknown and hence never replaced -> #lessThanOrEqualTo is invoked with the placeholder:
// the macro is unknown and hence never replaced -> #lessThanOrEqualTo
// is invoked with the placeholder:
verify(criteriaBuilderMock).lessThanOrEqualTo(eq(pathOfString(baseSoftwareModuleRootMock)),
eq(overduePropPlaceholder));
}
@@ -353,7 +349,9 @@ public class RSQLUtilityTest {
/*
* (non-Javadoc)
*
* @see org.eclipse.hawkbit.server.rest.resource.model.FieldNameProvider# getFieldName()
* @see
* org.eclipse.hawkbit.server.rest.resource.model.FieldNameProvider#
* getFieldName()
*/
@Override
public String getFieldName() {

View File

@@ -21,7 +21,7 @@ import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.TimestampCalculator;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,7 +41,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class VirtualPropertyResolverTest {
@Spy
private VirtualPropertyResolver resolverUnderTest = new VirtualPropertyResolver();
private final VirtualPropertyResolver resolverUnderTest = new VirtualPropertyResolver();
@Mock
private TenantConfigurationManagement confMgmt;
@@ -59,66 +59,65 @@ public class VirtualPropertyResolverTest {
public void before() {
nowTestTime = Instant.now().toEpochMilli();
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class))
.thenReturn(TEST_POLLING_TIME_INTERVAL);
.thenReturn(TEST_POLLING_TIME_INTERVAL);
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class))
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
mockStatic(TimestampCalculator.class);
when(TimestampCalculator.getTenantConfigurationManagement()).thenReturn(confMgmt);
substitutor = new StrSubstitutor(resolverUnderTest,
StrSubstitutor.DEFAULT_PREFIX,
substitutor = new StrSubstitutor(resolverUnderTest, StrSubstitutor.DEFAULT_PREFIX,
StrSubstitutor.DEFAULT_SUFFIX, StrSubstitutor.DEFAULT_ESCAPE);
}
}
@Test
@Description("Tests resolution of NOW_TS by using a StrSubstitutor configured with the VirtualPropertyResolver.")
public void resolveNowTimestampPlaceholder() {
String placeholder = "${NOW_TS}";
String testString = "lhs=lt=" + placeholder;
final String placeholder = "${NOW_TS}";
final String testString = "lhs=lt=" + placeholder;
String resolvedPlaceholders = substitutor.replace(testString);
final String resolvedPlaceholders = substitutor.replace(testString);
assertThat("NOW_TS has to be resolved!", resolvedPlaceholders, not(containsString(placeholder)));
}
@Test
@Description("Tests resolution of OVERDUE_TS by using a StrSubstitutor configured with the VirtualPropertyResolver.")
public void resolveOverdueTimestampPlaceholder() {
String placeholder = "${OVERDUE_TS}";
String testString = "lhs=lt=" + placeholder;
final String placeholder = "${OVERDUE_TS}";
final String testString = "lhs=lt=" + placeholder;
String resolvedPlaceholders = substitutor.replace(testString);
final String resolvedPlaceholders = substitutor.replace(testString);
assertThat("OVERDUE_TS has to be resolved!", resolvedPlaceholders, not(containsString(placeholder)));
}
@Test
@Description("Tests case insensititity of VirtualPropertyResolver.")
public void resolveOverdueTimestampPlaceholderLowerCase() {
String placeholder = "${overdue_ts}";
String testString = "lhs=lt=" + placeholder;
final String placeholder = "${overdue_ts}";
final String testString = "lhs=lt=" + placeholder;
String resolvedPlaceholders = substitutor.replace(testString);
final String resolvedPlaceholders = substitutor.replace(testString);
assertThat("overdue_ts has to be resolved!", resolvedPlaceholders, not(containsString(placeholder)));
}
@Test
@Description("Tests VirtualPropertyResolver with a placeholder unknown to VirtualPropertyResolver.")
public void handleUnknownPlaceholder() {
String placeholder = "${unknown}";
String testString = "lhs=lt=" + placeholder;
final String placeholder = "${unknown}";
final String testString = "lhs=lt=" + placeholder;
String resolvedPlaceholders = substitutor.replace(testString);
final String resolvedPlaceholders = substitutor.replace(testString);
assertThat("unknown should not be resolved!", resolvedPlaceholders, containsString(placeholder));
}
@Test
@Description("Tests escape mechanism for placeholders (syntax is $${SOME_PLACEHOLDER}).")
public void handleEscapedPlaceholder() {
String placeholder = "${OVERDUE_TS}";
String escaptedPlaceholder = StrSubstitutor.DEFAULT_ESCAPE + placeholder;
String testString = "lhs=lt=" + escaptedPlaceholder;
final String placeholder = "${OVERDUE_TS}";
final String escaptedPlaceholder = StrSubstitutor.DEFAULT_ESCAPE + placeholder;
final String testString = "lhs=lt=" + escaptedPlaceholder;
String resolvedPlaceholders = substitutor.replace(testString);
final String resolvedPlaceholders = substitutor.replace(testString);
assertThat("Escaped OVERDUE_TS should not be resolved!", resolvedPlaceholders, containsString(placeholder));
}
}

View File

@@ -13,9 +13,6 @@ logging.level.org.eclipse.persistence=ERROR
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
multipart.max-file-size=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
@@ -39,3 +36,47 @@ flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
# DDI configuration
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
# DDI and download security
hawkbit.server.ddi.security.authentication.header.authority=
hawkbit.server.ddi.security.authentication.targettoken.enabled=false
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false
hawkbit.server.download.anonymous.enabled=false
hawkbit.server.ddi.security.authentication.header.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.name=TestToken
hawkbit.server.ddi.security.authentication.gatewaytoken.key=
# Default tenant configuration properties
hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled
hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.header.enabled}
hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-header-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority
hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=${hawkbit.server.ddi.security.authentication.header.authority}
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.targettoken.enabled}
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-targettoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.enabled}
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key
hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=${hawkbit.server.ddi.security.authentication.gatewaytoken.key}
hawkbit.server.tenant.configuration.polling-time.keyName=pollingTime
hawkbit.server.tenant.configuration.polling-time.defaultValue=${hawkbit.controller.pollingTime}
hawkbit.server.tenant.configuration.polling-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.polling-overdue-time.keyName=pollingOverdueTime
hawkbit.server.tenant.configuration.polling-overdue-time.defaultValue=${hawkbit.controller.pollingOverdueTime}
hawkbit.server.tenant.configuration.polling-overdue-time.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator
hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled
hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=${hawkbit.server.download.anonymous.enabled}
hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator

View File

@@ -37,6 +37,7 @@ import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -67,7 +68,7 @@ import org.springframework.util.AntPathMatcher;
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY, proxyTargetClass = false, securedEnabled = true)
@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class,
ArtifactUrlHandlerProperties.class, ArtifactFilesystemProperties.class, HawkbitSecurityProperties.class,
ControllerPollProperties.class })
ControllerPollProperties.class, TenantConfigurationProperties.class })
@Profile("test")
@EnableAutoConfiguration
public class TestConfiguration implements AsyncConfigurer {

View File

@@ -102,7 +102,7 @@ import com.google.common.collect.Lists;
// strange test failures.
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public abstract class AbstractIntegrationTest implements EnvironmentAware {
private final static Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class);
private static final Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class);
protected static final Pageable pageReq = new PageRequest(0, 400);

View File

@@ -228,11 +228,11 @@ public final class SpPermission {
/*
* Spring security eval expressions.
*/
private static final String BRACKET_OPEN = "(";
private static final String BRACKET_CLOSE = ")";
private static final String HAS_AUTH_PREFIX = "hasAuthority" + BRACKET_OPEN + "'";
private static final String HAS_AUTH_SUFFIX = "'" + BRACKET_CLOSE;
private static final String HAS_AUTH_AND = " and ";
public static final String BRACKET_OPEN = "(";
public static final String BRACKET_CLOSE = ")";
public static final String HAS_AUTH_PREFIX = "hasAuthority" + BRACKET_OPEN + "'";
public static final String HAS_AUTH_SUFFIX = "'" + BRACKET_CLOSE;
public static final String HAS_AUTH_AND = " and ";
/**
* The role which contains in the spring security context in case an

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.security;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +38,7 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe
this.configurationKeyTenantRunner = new SecurityConfigurationKeyTenantRunner();
}
protected abstract TenantConfigurationKey getTenantConfigurationKey();
protected abstract String getTenantConfigurationKey();
@Override
public boolean isEnable(final TenantSecurityToken secruityToken) {

View File

@@ -15,7 +15,7 @@ import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -106,7 +106,7 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro
}
@Override
protected TenantConfigurationKey getTenantConfigurationKey() {
protected String getTenantConfigurationKey() {
return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED;
}
}

View File

@@ -14,7 +14,7 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -58,7 +58,7 @@ public class ControllerPreAuthenticatedAnonymousDownload extends AbstractControl
}
@Override
protected TenantConfigurationKey getTenantConfigurationKey() {
protected String getTenantConfigurationKey() {
return TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED;
}

View File

@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.security;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,7 +78,7 @@ public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends Abstra
}
@Override
protected TenantConfigurationKey getTenantConfigurationKey() {
protected String getTenantConfigurationKey() {
return TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
}

View File

@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -108,10 +108,11 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
controllerId = secruityToken.getHeader(caCommonNameHeader);
}
List<String> knownHashes = splitMultiHashBySemicolon(authorityNameConfigurationValue);
final List<String> knownHashes = splitMultiHashBySemicolon(authorityNameConfigurationValue);
final String cntlId = controllerId;
return knownHashes.stream().map(hashItem -> new HeaderAuthentication(cntlId, hashItem)).collect(Collectors.toSet());
return knownHashes.stream().map(hashItem -> new HeaderAuthentication(cntlId, hashItem))
.collect(Collectors.toSet());
}
/**
@@ -122,7 +123,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
*/
private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHashes) {
// there may be several knownIssuerHashes configured for the tenant
List<String> knownHashes = splitMultiHashBySemicolon(knownIssuerHashes);
final List<String> knownHashes = splitMultiHashBySemicolon(knownIssuerHashes);
// iterate over the headers until we get a null header.
int iHeader = 1;
@@ -143,7 +144,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
}
@Override
protected TenantConfigurationKey getTenantConfigurationKey() {
protected String getTenantConfigurationKey() {
return TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
}
@@ -155,7 +156,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
}
}
private static List<String> splitMultiHashBySemicolon(String knownIssuerHashes) {
private static List<String> splitMultiHashBySemicolon(final String knownIssuerHashes) {
return Arrays.asList(knownIssuerHashes.split(";"));
}
}

View File

@@ -19,7 +19,7 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -13,20 +13,18 @@ import java.io.Serializable;
import org.eclipse.hawkbit.im.authentication.PermissionService;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Bean which contains all SP permissions.
*
*/
@Service
public class SpPermissionChecker implements Serializable {
private static final long serialVersionUID = 2757865286212875704L;
private transient PermissionService permissionService;
protected transient PermissionService permissionService;
@Autowired
SpPermissionChecker(final PermissionService permissionService) {
protected SpPermissionChecker(final PermissionService permissionService) {
this.permissionService = permissionService;
}

View File

@@ -170,7 +170,7 @@ public final class UserDetailsFormatter {
return Optional.ofNullable(userPrincipal.getEmail());
}
private static UserDetails getCurrentUser() {
public static UserDetails getCurrentUser() {
final SecurityContext context = (SecurityContext) VaadinService.getCurrentRequest().getWrappedSession()
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
return (UserDetails) context.getAuthentication().getPrincipal();

View File

@@ -27,6 +27,8 @@ public abstract class AbstractTextFieldBuilder<E extends AbstractTextField> {
private String id;
private boolean immediate;
private boolean required;
private boolean readOnly;
private boolean enabled = true;
private int maxLengthAllowed;
/**
@@ -69,6 +71,26 @@ public abstract class AbstractTextFieldBuilder<E extends AbstractTextField> {
return this;
}
/**
* @param readOnly
* the readOnly to set
* @return the builder
*/
public AbstractTextFieldBuilder<E> readOnly(final boolean readOnly) {
this.readOnly = readOnly;
return this;
}
/**
* @param enabled
* the enabled to set
* @return the builder
*/
public AbstractTextFieldBuilder<E> enabled(final boolean enabled) {
this.enabled = enabled;
return this;
}
/**
* @param prompt
* the prompt to set
@@ -119,6 +141,8 @@ public abstract class AbstractTextFieldBuilder<E extends AbstractTextField> {
textComponent.setRequired(required);
textComponent.setImmediate(immediate);
textComponent.setReadOnly(readOnly);
textComponent.setEnabled(enabled);
if (StringUtils.isNotEmpty(caption)) {
textComponent.setCaption(caption);

View File

@@ -20,7 +20,12 @@ public class SPUICheckBox extends CheckBox {
SPUICheckBox(final String caption, final String style, final String styleName, final boolean required,
final String data) {
decorate(caption, style, styleName, required, data);
decorate(null, caption, style, styleName, required, data);
}
SPUICheckBox(final String id, final String caption, final String style, final String styleName,
final boolean required, final String data) {
decorate(id, caption, style, styleName, required, data);
}
/**
@@ -37,24 +42,27 @@ public class SPUICheckBox extends CheckBox {
* @param promt
* inputpromt of the CheckBox
*/
private void decorate(final String caption, final String style, final String styleName, final boolean required,
final String data) {
private void decorate(final String id, final String caption, final String style, final String styleName,
final boolean required, final String data) {
// Default settings
setRequired(required);
addStyleName(ValoTheme.CHECKBOX_SMALL);
if (null != caption) {
if (id != null) {
setId(id);
}
if (caption != null) {
setCaption(caption);
}
// Add style
if (null != style) {
if (style != null) {
setStyleName(style);
}
// Add style Name
if (null != styleName) {
if (styleName != null) {
addStyleName(styleName);
}
// Set Data
if (null != data) {
if (data != null) {
setData(data);
}
}

View File

@@ -96,6 +96,28 @@ public final class SPUIComponentProvider {
return new SPUICheckBox(caption, style, styleName, required, data);
}
/**
* Get Label UI component.
*
* @param id
* id of the checkbox
* @param caption
* as caption
* @param style
* combo style to add
* @param styleName
* combo style to set
* @param required
* signifies if combo is mandatory
* @param data
* combo box data
* @return ComboBox
*/
public static CheckBox getCheckBox(final String id, final String caption, final String style,
final String styleName, final boolean required, final String data) {
return new SPUICheckBox(id, caption, style, styleName, required, data);
}
/**
* Get Button - Factory Approach for decoration.
*

View File

@@ -125,7 +125,7 @@ public final class DashboardMenu extends CustomComponent {
return dashboardMenuLayout;
}
private VerticalLayout getMenuLayout() {
private static VerticalLayout getMenuLayout() {
final VerticalLayout menuContent = new VerticalLayout();
menuContent.addStyleName(ValoTheme.MENU_PART);
menuContent.addStyleName("sidebar");
@@ -197,7 +197,7 @@ public final class DashboardMenu extends CustomComponent {
}
private Component buildUserMenu() {
private static Component buildUserMenu() {
final MenuBar settings = new MenuBar();
settings.addStyleName("user-menu");
settings.setHtmlContentAllowed(true);

View File

@@ -25,7 +25,7 @@ public abstract class BaseConfigurationView extends CustomComponent implements C
private final List<ConfigurationItemChangeListener> configurationChangeListeners = new ArrayList<>();
protected void notifyConfigurationChanged() {
configurationChangeListeners.forEach(listener -> listener.configurationHasChanged());
configurationChangeListeners.forEach(ConfigurationItemChangeListener::configurationHasChanged);
}
@Override

View File

@@ -25,4 +25,12 @@ public interface ConfigurationGroup extends Component, ConfigurationItem {
* called to rollback any configuration changes.
*/
void undo();
/**
* @return <code>true</code> if view can be shown (e.g. sufficient
* permissions).
*/
default boolean show() {
return true;
}
}

View File

@@ -14,7 +14,7 @@ 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.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.tenantconfiguration.polling.DurationConfigField;
import org.eclipse.hawkbit.ui.utils.I18N;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
@@ -108,7 +108,7 @@ public class PollingConfigurationView extends BaseConfigurationView
}
}
private void saveDurationConfigurationValue(final TenantConfigurationKey key, final Duration duration) {
private void saveDurationConfigurationValue(final String key, final Duration duration) {
if (duration == null) {
tenantConfigurationManagement.deleteConfiguration(key);
} else {

View File

@@ -8,7 +8,9 @@
*/
package org.eclipse.hawkbit.ui.tenantconfiguration;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
@@ -68,6 +70,9 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
private final List<ConfigurationGroup> configurationViews = Lists.newArrayListWithExpectedSize(3);
@Autowired(required = false)
private Collection<ConfigurationGroup> customConfigurationViews;
@Autowired
TenantConfigurationDashboardView(final I18N i18n, final UiProperties uiProperties,
final UINotification uINotification, final SystemManagement systemManagement,
@@ -94,6 +99,10 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
configurationViews.add(defaultDistributionSetTypeLayout);
configurationViews.add(authenticationConfigurationView);
configurationViews.add(pollingConfigurationView);
if (customConfigurationViews != null) {
configurationViews.addAll(
customConfigurationViews.stream().filter(ConfigurationGroup::show).collect(Collectors.toList()));
}
final Panel rootPanel = new Panel();
rootPanel.setStyleName("tenantconfig-root");

View File

@@ -12,7 +12,7 @@ import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.utils.I18N;
@@ -28,7 +28,7 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay
private final I18N i18n;
private final TenantConfigurationKey configurationKey;
private final String configurationKey;
private final transient TenantConfigurationManagement tenantConfigurationManagement;
private final List<ConfigurationItemChangeListener> configurationChangeListeners = new ArrayList<>();
@@ -40,7 +40,7 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay
* the tenant configuration management to retrieve the
* configuration value
*/
public AbstractAuthenticationTenantConfigurationItem(final TenantConfigurationKey configurationKey,
public AbstractAuthenticationTenantConfigurationItem(final String configurationKey,
final TenantConfigurationManagement tenantConfigurationManagement, final I18N i18n) {
this.configurationKey = configurationKey;
this.tenantConfigurationManagement = tenantConfigurationManagement;
@@ -57,7 +57,10 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay
@Override
public boolean isConfigEnabled() {
return tenantConfigurationManagement.getConfigurationValue(configurationKey, Boolean.class).getValue();
final TenantConfigurationValue<Boolean> enabled = tenantConfigurationManagement
.getConfigurationValue(configurationKey, Boolean.class);
return enabled.getValue() && !enabled.isGlobal();
}
/**
@@ -70,12 +73,12 @@ abstract class AbstractAuthenticationTenantConfigurationItem extends VerticalLay
/**
* @return the configurationKey
*/
protected TenantConfigurationKey getConfigurationKey() {
protected String getConfigurationKey() {
return configurationKey;
}
protected void notifyConfigurationChanged() {
configurationChangeListeners.forEach(listener -> listener.configurationHasChanged());
configurationChangeListeners.forEach(ConfigurationItemChangeListener::configurationHasChanged);
}
@Override

View File

@@ -9,7 +9,7 @@
package org.eclipse.hawkbit.ui.tenantconfiguration.authentication;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.utils.I18N;
/**

View File

@@ -9,7 +9,7 @@
package org.eclipse.hawkbit.ui.tenantconfiguration.authentication;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
import org.eclipse.hawkbit.ui.utils.I18N;

View File

@@ -8,11 +8,11 @@
*/
package org.eclipse.hawkbit.ui.tenantconfiguration.authentication;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmall;
import org.eclipse.hawkbit.ui.utils.I18N;
@@ -21,7 +21,6 @@ import com.vaadin.server.FontAwesome;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
@@ -35,15 +34,11 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
private final transient SecurityTokenGenerator securityTokenGenerator;
private final TextField gatewayTokenNameTextField;
private final Label gatewayTokenkeyLabel;
private boolean configurationEnabled;
private boolean configurationEnabledChange;
private boolean keyNameChanged;
private boolean keyChanged;
private final VerticalLayout detailLayout;
@@ -61,10 +56,6 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
detailLayout = new VerticalLayout();
detailLayout.setImmediate(true);
gatewayTokenNameTextField = new TextFieldBuilder().immediate(true).buildTextComponent();
// hide text field until we support multiple gateway tokens for a tenan
gatewayTokenNameTextField.setVisible(false);
gatewayTokenNameTextField.addTextChangeListener(event -> doKeyNameChanged());
final Button gatewaytokenBtn = SPUIComponentProvider.getButton("TODO-ID", "Regenerate Key", "",
ValoTheme.BUTTON_TINY + " " + "redicon", true, null, SPUIButtonStyleSmall.class);
@@ -80,24 +71,17 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
keyGenerationLayout.setSpacing(true);
keyGenerationLayout.setImmediate(true);
keyGenerationLayout.addComponent(gatewayTokenNameTextField);
keyGenerationLayout.addComponent(gatewayTokenkeyLabel);
keyGenerationLayout.addComponent(gatewaytokenBtn);
detailLayout.addComponent(keyGenerationLayout);
if (isConfigEnabled()) {
gatewayTokenNameTextField.setValue(getSecurityTokenName());
gatewayTokenkeyLabel.setValue(getSecurityTokenKey());
setDetailVisible(true);
}
}
private void doKeyNameChanged() {
keyNameChanged = true;
notifyConfigurationChanged();
}
private void setDetailVisible(final boolean visible) {
if (visible) {
addComponent(detailLayout);
@@ -122,22 +106,13 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
configurationEnabled = true;
setDetailVisible(true);
String gatewayTokenKey = getSecurityTokenKey();
String gatewayTokenName = getSecurityTokenName();
if (gatewayTokenKey == null) {
gatewayTokenName = "GeneratedToken";
keyNameChanged = true;
if (StringUtils.isEmpty(gatewayTokenKey)) {
gatewayTokenKey = securityTokenGenerator.generateToken();
keyChanged = true;
}
gatewayTokenNameTextField.setValue(gatewayTokenName);
gatewayTokenkeyLabel.setValue(gatewayTokenKey);
}
private String getSecurityTokenName() {
return getTenantConfigurationManagement().getConfigurationValue(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME, String.class).getValue();
}
private String getSecurityTokenKey() {
return getTenantConfigurationManagement().getConfigurationValue(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class).getValue();
@@ -159,11 +134,6 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, configurationEnabled);
}
if (keyNameChanged) {
getTenantConfigurationManagement().addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME,
gatewayTokenNameTextField.getValue());
}
if (keyChanged) {
getTenantConfigurationManagement().addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY,
@@ -174,9 +144,7 @@ public class GatewaySecurityTokenAuthenticationConfigurationItem extends Abstrac
@Override
public void undo() {
configurationEnabledChange = false;
keyNameChanged = false;
keyChanged = false;
gatewayTokenNameTextField.setValue(getSecurityTokenName());
gatewayTokenkeyLabel.setValue(getSecurityTokenKey());
}

View File

@@ -9,7 +9,7 @@
package org.eclipse.hawkbit.ui.tenantconfiguration.authentication;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.utils.I18N;
/**

View File

@@ -64,6 +64,7 @@
margin-left: 1px;
position: relative;
z-index: 1;
font-size: 14px !important;
}
//Version displayed at bottom of menu *

View File

@@ -135,7 +135,7 @@
}
.margin-top-style {
margin-top: 10px;
margin-top: 10px !important;
}
//confimation dialogue popup - Content margin adjustment