From e1aa4f2622d11397a3338091eee7fbcdd93d2a51 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 5 Aug 2019 10:24:51 +0200 Subject: [PATCH 01/28] Synchronizing device list with Eclipse Hono. Signed-off-by: Brandon Schmitt --- hawkbit-autoconfigure/pom.xml | 6 + .../repository/HonoSyncAutoConfiguration.java | 39 ++++++ .../hawkbit-repository-hono/pom.xml | 41 ++++++ .../repository/hono/HonoTargetSync.java | 132 ++++++++++++++++++ hawkbit-repository/pom.xml | 1 + 5 files changed, 219 insertions(+) create mode 100644 hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java create mode 100644 hawkbit-repository/hawkbit-repository-hono/pom.xml create mode 100644 hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index 1cd89aa38f..2f0738d78d 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -37,6 +37,12 @@ ${project.version} true + + org.eclipse.hawkbit + hawkbit-repository-hono + ${project.version} + true + org.eclipse.hawkbit hawkbit-repository-jpa diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java new file mode 100644 index 0000000000..49603f680b --- /dev/null +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.autoconfigure.repository; + +import org.eclipse.hawkbit.repository.hono.HonoTargetSync; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Configuration +@ConditionalOnProperty(prefix = "hawkbit.server.repository.hono-sync", name = "devices-uri") +public class HonoSyncAutoConfiguration { + + @Value("${hawkbit.server.repository.hono-sync.poll-rate:60}") + private Integer pollRate; + + @Bean + @ConditionalOnMissingBean + public HonoTargetSync honoTargetSync() { + HonoTargetSync honoTargetSync = new HonoTargetSync(); + + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleAtFixedRate(honoTargetSync, 0, pollRate, TimeUnit.SECONDS); + + return honoTargetSync; + } +} diff --git a/hawkbit-repository/hawkbit-repository-hono/pom.xml b/hawkbit-repository/hawkbit-repository-hono/pom.xml new file mode 100644 index 0000000000..375e5ffcb5 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-hono/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.eclipse.hawkbit + hawkbit-repository + 0.3.0-SNAPSHOT + + + hawkbit-repository-hono + hawkBit :: Repository :: Hono-Sync + + + + + org.eclipse.hawkbit + hawkbit-repository-api + ${project.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + + + net.minidev + json-smart + 2.3 + + + + \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java b/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java new file mode 100644 index 0000000000..4aa97b1d12 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.hono; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.Target; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HonoTargetSync implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(HonoTargetSync.class); + + @Autowired + private TargetManagement targetManagement; + + @Autowired + private EntityFactory entityFactory; + + @Value("${hawkbit.server.repository.hono-sync.devices-uri}") + private String honoDevicesEndpoint; + + @Override + public void run() { + try { + Map honoTargets = getAllTargets(); + List targetIdsToDelete = new ArrayList<>(); + + long count = targetManagement.count(); + for (int i = 0; count > 0; count -= 100, ++i) { + Slice slice = targetManagement.findAll(PageRequest.of(i, 100)); + for (Target target : slice) { + Long id = target.getId(); + + if (honoTargets.containsKey(id)) { + // TODO: Update Target if necessary! What properties will be synchronized? + // Remove target from map since it won't be needed anymore + honoTargets.remove(id); + } else { + targetIdsToDelete.add(id); + } + } + } + + if (!targetIdsToDelete.isEmpty()) { + targetManagement.delete(targetIdsToDelete); + } + + // At this point honoTargets only contains objects which were not found in hawkBit's target repository + for (Map.Entry entry : honoTargets.entrySet()) { + targetManagement.create(entityFactory.target().create() + .controllerId(entry.getKey().toString()) + ); + } + } catch (IOException | NoSuchFieldException | ParseException e) { + LOG.error("Could not parse hono api response", e); + } + } + + private Map getAllTargets() throws IOException, ParseException, NoSuchFieldException { + + Map honoTargets = new HashMap<>(); + + // Initialize the total variable with an arbitrary number > 0 so it starts fetching targets. It will be updated + // with the correct number during the interpretation of the first response. + long total = Long.MAX_VALUE; + + JSONParser parser = new JSONParser(); + while (honoTargets.size() < total) { + URL url = new URL(honoDevicesEndpoint); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.writeBytes("offset=" + honoTargets.size()); + outputStream.flush(); + outputStream.close(); + + int statusCode = connection.getResponseCode(); + if (statusCode >= 200 && statusCode < 300) { + String totalHeader = connection.getHeaderField("Total"); + if (totalHeader == null) { + throw new NoSuchFieldException("Response does not contain expected HTTP header \"Total\"."); + } + total = Long.parseLong(totalHeader); + + Object response = parser.parse(connection.getInputStream()); + if (response instanceof JSONArray) { + JSONArray targetArray = (JSONArray) response; + for (Object object : targetArray) { + JSONObject target = (JSONObject) object; + Long controllerId = (Long) target.get("controllerId"); + + honoTargets.put(controllerId, entityFactory.target().create() + .controllerId(String.valueOf(controllerId)) + .build()); + } + } + } + else { + throw new IOException("Received HTTP status code " + statusCode + " connecting to " + url.toString()); + } + } + + return honoTargets; + } +} diff --git a/hawkbit-repository/pom.xml b/hawkbit-repository/pom.xml index f11d9d75f8..ad7c30b1e4 100644 --- a/hawkbit-repository/pom.xml +++ b/hawkbit-repository/pom.xml @@ -29,6 +29,7 @@ hawkbit-repository-api hawkbit-repository-test hawkbit-repository-core + hawkbit-repository-hono From 659fce13a2c5686c5d4151ac5c46033ee8d28bbc Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 8 Aug 2019 17:52:52 +0200 Subject: [PATCH 02/28] Use devices' credentials stored in Hono to authenticate. Signed-off-by: Brandon Schmitt --- .../SecurityManagedConfiguration.java | 49 ++++- ...pControllerPreAuthenticatedHonoFilter.java | 63 ++++++ .../security/PreAuthHonoProviderTest.java | 75 ++++++++ hawkbit-security-integration/pom.xml | 5 + .../ControllerPreAuthenticatedHonoFilter.java | 179 ++++++++++++++++++ .../security/HeaderAuthentication.java | 8 + .../hawkbit/security/HonoCredentials.java | 126 ++++++++++++ .../PreAuthHonoAuthenticationProvider.java | 69 +++++++ ...okenSourceTrustAuthenticationProvider.java | 2 +- 9 files changed, 572 insertions(+), 4 deletions(-) create mode 100644 hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java create mode 100644 hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java create mode 100644 hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java create mode 100644 hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java create mode 100644 hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index 45598c2a91..f3a93eddb7 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -40,8 +40,10 @@ import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateAnonymousDownloadFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter; +import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedHonoFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter; import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter; +import org.eclipse.hawkbit.security.PreAuthHonoAuthenticationProvider; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -49,6 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -84,7 +87,6 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.session.SessionManagementFilter; -import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -157,6 +159,9 @@ static class ControllerSecurityConfigurationAdapter extends WebSecurityConfigure private final HawkbitSecurityProperties securityProperties; private final SystemSecurityContext systemSecurityContext; + @Value("${hawkbit.server.repository.hono-sync.credentials-endpoint:}") + private String honoCredentialsEndpoint; + @Autowired ControllerSecurityConfigurationAdapter(final ControllerManagement controllerManagement, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, @@ -205,6 +210,17 @@ protected void configure(final HttpSecurity http) throws Exception { securityHeaderFilter.setCheckForPrincipalChanges(true); securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + + HttpControllerPreAuthenticatedHonoFilter honoFilter = null; + if (!honoCredentialsEndpoint.isEmpty()) { + honoFilter = new HttpControllerPreAuthenticatedHonoFilter( + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext, + honoCredentialsEndpoint); + honoFilter.setAuthenticationManager(authenticationManager()); + honoFilter.setCheckForPrincipalChanges(true); + honoFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + } + final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); securityTokenFilter.setAuthenticationManager(authenticationManager()); @@ -236,7 +252,11 @@ protected void configure(final HttpSecurity http) throws Exception { .authenticationFilter(anoymousFilter); } else { - httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter) + httpSec.addFilter(securityHeaderFilter); + if (honoFilter != null) { + httpSec.addFilter(honoFilter); + } + httpSec.addFilter(securityTokenFilter) .addFilter(gatewaySecurityTokenFilter).requestMatchers().antMatchers(DDI_ANT_MATCHERS).and() .anonymous().disable().authorizeRequests().anyRequest().authenticated().and() .exceptionHandling() @@ -249,6 +269,8 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } @@ -273,6 +295,9 @@ static class ControllerDownloadSecurityConfigurationAdapter extends WebSecurityC private final HawkbitSecurityProperties securityProperties; private final SystemSecurityContext systemSecurityContext; + @Value("${hawkbit.server.repository.hono-sync.credentials-endpoint:}") + private String honoCredentialsEndpoint; + @Autowired ControllerDownloadSecurityConfigurationAdapter(final ControllerManagement controllerManagement, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, @@ -321,6 +346,16 @@ protected void configure(final HttpSecurity http) throws Exception { securityHeaderFilter.setCheckForPrincipalChanges(true); securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + HttpControllerPreAuthenticatedHonoFilter honoFilter = null; + if (!honoCredentialsEndpoint.isEmpty()) { + honoFilter = new HttpControllerPreAuthenticatedHonoFilter( + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext, + honoCredentialsEndpoint); + honoFilter.setAuthenticationManager(authenticationManager()); + honoFilter.setCheckForPrincipalChanges(true); + honoFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + } + final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); securityTokenFilter.setAuthenticationManager(authenticationManager()); @@ -358,7 +393,11 @@ protected void configure(final HttpSecurity http) throws Exception { .authenticationFilter(anoymousFilter); } else { - httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter) + httpSec.addFilter(securityHeaderFilter); + if (honoFilter != null) { + httpSec.addFilter(honoFilter); + } + httpSec.addFilter(securityTokenFilter) .addFilter(gatewaySecurityTokenFilter).addFilter(controllerAnonymousDownloadFilter) .requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().anonymous().disable() .authorizeRequests().anyRequest().authenticated().and().exceptionHandling() @@ -371,6 +410,8 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } @@ -445,6 +486,8 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java new file mode 100644 index 0000000000..77289a8dd0 --- /dev/null +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.security; + +import org.eclipse.hawkbit.repository.ControllerManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.tenancy.TenantAware; + +/** + * An pre-authenticated processing filter which extracts (if enabled through + * configuration) the possibility to authenticate a target based on its target + * security-token with the {@code Authorization} HTTP header. + * {@code Example Header: Authorization: HonoToken + * 5d8fSD54fdsFG98DDsa.} + * + * The {@code Authorization} header is a HTTP standard and reverse proxy or + * other proxies will keep the Authorization headers untouched instead of maybe + * custom headers which have then weird side-effects. Furthermore frameworks are + * aware of the sensitivity of the Authorization header and do not log it and + * store it somewhere. + */ +public class HttpControllerPreAuthenticatedHonoFilter extends AbstractHttpControllerAuthenticationFilter { + + private final ControllerManagement controllerManagement; + private final String honoCredentialsEndpoint; + + /** + * Constructor. + * + * @param tenantConfigurationManagement + * the system management service to retrieve configuration + * properties + * @param tenantAware + * the tenant aware service to get configuration for the specific + * tenant + * @param controllerManagement + * the controller management to retrieve the specific target + * security token to verify + * @param systemSecurityContext + * the system security context + */ + public HttpControllerPreAuthenticatedHonoFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final ControllerManagement controllerManagement, final SystemSecurityContext systemSecurityContext, + final String honoCredentialsEndpoint) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + this.controllerManagement = controllerManagement; + this.honoCredentialsEndpoint = honoCredentialsEndpoint; + } + + @Override + protected PreAuthenticationFilter createControllerAuthenticationFilter() { + return new ControllerPreAuthenticatedHonoFilter(tenantConfigurationManagement, controllerManagement, + tenantAware, systemSecurityContext, honoCredentialsEndpoint); + } + +} diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java new file mode 100644 index 0000000000..0cc097e361 --- /dev/null +++ b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.security; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +@Feature("Unit Tests - Security") +@Story("PreAuth Hono Provider Test") +@RunWith(MockitoJUnitRunner.class) +public class PreAuthHonoProviderTest { + + private final PreAuthHonoAuthenticationProvider testProvider = new PreAuthHonoAuthenticationProvider(); + + @Mock + private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock; + + @Test + @Description("Testing that the provided credentials are incorrect.") + public void invalidCredentialsThrowsAuthenticationException() { + + HeaderAuthentication principal = new HeaderAuthentication("deviceId", "wrongPassword"); + + HonoPasswordSecret secret = new HonoPasswordSecret(null, null, "sha-256", "salt", "hash"); + HonoCredentials credentials = new HonoCredentials("deviceId", "hashed-password", "authId", true, Collections.singletonList(secret)); + + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, + Collections.singletonList(credentials)); + token.setDetails(webAuthenticationDetailsMock); + + // test, should throw authentication exception + try { + testProvider.authenticate(token); + fail("Should not work with wrong credentials"); + } catch (final BadCredentialsException e) { + } + } + + @Test + @Description("Testing that the provided credentials are correct.") + public void credentialsAreCorrect() { + + HeaderAuthentication principal = new HeaderAuthentication("deviceId", "password"); + + HonoPasswordSecret secret = new HonoPasswordSecret(null, null, "sha-256", "salt", "7a37b85c8918eac19a9089c0fa5a2ab4dce3f90528dcdeec108b23ddf3607b99"); + HonoCredentials credentials = new HonoCredentials("deviceId", "hashed-password", "authId", true, Collections.singletonList(secret)); + + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, + Collections.singletonList(credentials)); + token.setDetails(webAuthenticationDetailsMock); + + // test, should throw authentication exception + final Authentication authenticate = testProvider.authenticate(token); + assertThat(authenticate.isAuthenticated()).isTrue(); + } +} diff --git a/hawkbit-security-integration/pom.xml b/hawkbit-security-integration/pom.xml index bd9bddf1cf..3139789616 100644 --- a/hawkbit-security-integration/pom.xml +++ b/hawkbit-security-integration/pom.xml @@ -35,6 +35,11 @@ javax.servlet-api provided + + net.minidev + json-smart + 2.3 + diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java new file mode 100644 index 0000000000..f65b20b5c7 --- /dev/null +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.security; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +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.TenantConfigurationProperties.TenantConfigurationKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An pre-authenticated processing filter which extracts (if enabled through + * configuration) the possibility to authenticate a target based on its target + * security-token with the {@code Authorization} HTTP header. + * {@code Example Header: Authorization: HonoToken + * 5d8fSD54fdsFG98DDsa.} + */ +public class ControllerPreAuthenticatedHonoFilter extends AbstractControllerAuthenticationFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(ControllerPreAuthenticatedHonoFilter.class); + private static final String TARGET_SECURITY_TOKEN_AUTH_SCHEME = "HonoToken "; + private static final int OFFSET_TARGET_TOKEN = TARGET_SECURITY_TOKEN_AUTH_SCHEME.length(); + + private final ControllerManagement controllerManagement; + private final String honoCredentialsEndpoint; + + /** + * Constructor. + * + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration + * properties + * @param controllerManagement + * the controller management to retrieve the specific target + * security token to verify + * @param tenantAware + * the tenant aware service to get configuration for the specific + * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration + */ + public ControllerPreAuthenticatedHonoFilter( + final TenantConfigurationManagement tenantConfigurationManagement, + final ControllerManagement controllerManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext, final String honoCredentialsEndpoint) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + this.controllerManagement = controllerManagement; + this.honoCredentialsEndpoint = honoCredentialsEndpoint; + } + + @Override + public HeaderAuthentication getPreAuthenticatedPrincipal(final DmfTenantSecurityToken secruityToken) { + final String controllerId = resolveControllerId(secruityToken); + final String authHeader = secruityToken.getHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER); + if ((authHeader != null) && authHeader.startsWith(TARGET_SECURITY_TOKEN_AUTH_SCHEME)) { + LOGGER.debug("found authorization header with scheme {} using target security token for authentication", + TARGET_SECURITY_TOKEN_AUTH_SCHEME); + return new HeaderAuthentication(controllerId, authHeader.substring(OFFSET_TARGET_TOKEN)); + } + LOGGER.debug( + "security token filter is enabled but request does not contain either the necessary path variables {} or the authorization header with scheme {}", + secruityToken, TARGET_SECURITY_TOKEN_AUTH_SCHEME); + return null; + } + + @Override + public Collection getPreAuthenticatedCredentials(final DmfTenantSecurityToken securityToken) { + Object response; + try { + URL url = new URL(honoCredentialsEndpoint + .replace("$tenantId", securityToken.getTenant()) + .replace("$deviceId", resolveControllerId(securityToken))); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + int statusCode = connection.getResponseCode(); + if (statusCode < 200 || statusCode >= 300) { + return null; + } + JSONParser parser = new JSONParser(); + response = parser.parse(connection.getInputStream()); + } + catch (IOException | ParseException e) { + return null; + } + + ArrayList honoCredentials = new ArrayList<>(); + + if (response instanceof JSONArray) { + JSONArray credentialsArray = (JSONArray) response; + for (Object object : credentialsArray) { + JSONObject credentials = (JSONObject) object; + + String deviceId = credentials.getAsString("device-id"); + String type = credentials.getAsString("type"); + String authId = credentials.getAsString("auth-id"); + Boolean enabled = (Boolean) credentials.getOrDefault("enabled", true); + JSONArray secrets = (JSONArray) credentials.get("secrets"); + + ArrayList honoSecrets = new ArrayList<>(); + switch (type) { + case "hashed-password": + for (Object secretObject : secrets) { + JSONObject secret = (JSONObject) secretObject; + String notAfter = secret.getAsString("not-after"); + String notBefore = secret.getAsString("not-before"); + String pwdHash = secret.getAsString("pwd-hash"); + String salt = secret.getAsString("salt"); + String hashFunction = (String) secret.getOrDefault("hash-function", "sha-256"); + + honoSecrets.add(new HonoPasswordSecret(notBefore, notAfter, hashFunction, salt, pwdHash)); + } + break; + + case "psk": + for (Object secretObject : secrets) { + JSONObject secret = (JSONObject) secretObject; + String notAfter = secret.getAsString("not-after"); + String notBefore = secret.getAsString("not-before"); + String key = secret.getAsString("key"); + + honoSecrets.add(new HonoPreSharedKey(notBefore, notAfter, key)); + } + break; + + case "x509-cert": + for (Object secretObject : secrets) { + JSONObject secret = (JSONObject) secretObject; + String notAfter = secret.getAsString("not-after"); + String notBefore = secret.getAsString("not-before"); + + honoSecrets.add(new HonoX509Certificate(notBefore, notAfter)); + } + + default: + // skip this credentials entry as it is not supported + continue; + } + honoCredentials.add(new HonoCredentials(deviceId, type, authId, enabled, honoSecrets)); + } + } + + return honoCredentials; + } + + private String resolveControllerId(final DmfTenantSecurityToken securityToken) { + if (securityToken.getControllerId() != null) { + return securityToken.getControllerId(); + } + final Optional foundTarget = systemSecurityContext.runAsSystemAsTenant( + () -> controllerManagement.get(securityToken.getTargetId()), securityToken.getTenant()); + return foundTarget.map(Target::getControllerId).orElse(null); + } + + @Override + protected String getTenantConfigurationKey() { + return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED; + } +} diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HeaderAuthentication.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HeaderAuthentication.java index e7e82f74a1..cfded18a0f 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HeaderAuthentication.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HeaderAuthentication.java @@ -63,6 +63,14 @@ public boolean equals(final Object obj) { return true; } + public String getControllerId() { + return controllerId; + } + + public String getHeaderAuth() { + return headerAuth; + } + @Override public String toString() { // only the controller ID because the principal is stored as string for diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java new file mode 100644 index 0000000000..3a92c7e820 --- /dev/null +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.security; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDateTime; +import java.util.Collection; + +public class HonoCredentials { + private String deviceId; + private String type; + private String authId; + private boolean enabled; + private Collection secrets; + + HonoCredentials(String deviceId, String type, String authId, boolean enabled, Collection secrets) { + this.deviceId = deviceId; + this.type = type; + this.authId = authId; + this.enabled = enabled; + this.secrets = secrets; + } + + public boolean matches(final String providedSecret) { + if (enabled) { + for (HonoSecret secret : secrets) { + if (secret.isValid() && secret.matches(providedSecret)) { + return true; + } + } + } + + return false; + } +} + +abstract class HonoSecret { + private LocalDateTime notBefore; + private LocalDateTime notAfter; + + HonoSecret(String notBefore, String notAfter) { + if (notBefore != null) { + this.notBefore = LocalDateTime.parse(notBefore); + } + if (notAfter != null) { + this.notAfter = LocalDateTime.parse(notAfter); + } + } + + public boolean isValid() { + LocalDateTime now = LocalDateTime.now(); + return (notBefore == null || now.compareTo(notBefore) >= 0) && (notAfter == null || now.compareTo(notAfter) <= 0); + } + + public abstract boolean matches(final String password); +} + +class HonoPasswordSecret extends HonoSecret { + private String hashFunction; + private String salt; + private String pwdHash; + + HonoPasswordSecret(String notBefore, String notAfter, String hashFunction, String salt, String pwdHash) { + super(notBefore, notAfter); + + this.hashFunction = hashFunction; + this.salt = salt; + this.pwdHash = pwdHash; + } + + @Override + public boolean matches(final String password) { + PasswordEncoder encoder; + if (hashFunction.equals("bcrypt")) { + encoder = new BCryptPasswordEncoder(); + } + else if(hashFunction.equals("sha-256")) { + encoder = new MessageDigestPasswordEncoder("SHA-256"); + } + else if(hashFunction.equals("sha-512")) { + encoder = new MessageDigestPasswordEncoder("SHA-512"); + } + else { + return false; + } + + return encoder.matches(password + (salt != null ? salt : ""), pwdHash); + } +} + +class HonoPreSharedKey extends HonoSecret { + private String key; + + HonoPreSharedKey(String notBefore, String notAfter, String key) { + super(notBefore, notAfter); + + this.key = key; + } + + @Override + public boolean matches(final String key) { + return this.key.equals(key); + } +} + +class HonoX509Certificate extends HonoSecret { + + HonoX509Certificate(String notBefore, String notAfter) { + super(notBefore, notAfter); + } + + @Override + public boolean matches(String password) { + // TODO: implement! + return false; + } +} diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java new file mode 100644 index 0000000000..8c962809bf --- /dev/null +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.security; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; + +import java.util.Collection; +import java.util.List; + +public class PreAuthHonoAuthenticationProvider extends PreAuthTokenSourceTrustAuthenticationProvider { + + public PreAuthHonoAuthenticationProvider() { + super(); + } + + public PreAuthHonoAuthenticationProvider(final List authorizedSourceIps) { + super(authorizedSourceIps); + } + + public PreAuthHonoAuthenticationProvider(final String... authorizedSourceIps) { + super(authorizedSourceIps); + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + if (!supports(authentication.getClass())) { + return null; + } + + final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) authentication; + final Object credentials = token.getCredentials(); + final Object principal = token.getPrincipal(); + final Object tokenDetails = token.getDetails(); + final Collection authorities = token.getAuthorities(); + + if (!(principal instanceof HeaderAuthentication) || !(credentials instanceof Collection)) { + throw new BadCredentialsException("The provided principal and credentials are not match"); + } + + boolean successAuthentication = false; + for (Object object : (Collection) credentials) { + if (object instanceof HonoCredentials) { + if (((HonoCredentials) object).matches(((HeaderAuthentication) principal).getHeaderAuth())) { + successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails);; + break; + } + } + } + + if (successAuthentication) { + final PreAuthenticatedAuthenticationToken successToken = new PreAuthenticatedAuthenticationToken(principal, + credentials, authorities); + successToken.setDetails(tokenDetails); + return successToken; + } + + throw new BadCredentialsException("The provided principal and credentials are not match"); + } +} diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java index d7dddb4f65..2b02cf6b46 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java @@ -143,7 +143,7 @@ private boolean calculateAuthenticationSuccess(final Object principal, final Obj return successAuthentication; } - private boolean checkSourceIPAddressIfNeccessary(final Object tokenDetails) { + boolean checkSourceIPAddressIfNeccessary(final Object tokenDetails) { boolean success = authorizedSourceIps == null; String remoteAddress = null; // controllerIds in URL path and request header are the same but is the From 884b54dc2ee18fbef537fc96b0cb1c76a601c64d Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 19 Aug 2019 17:21:25 +0200 Subject: [PATCH 03/28] Add Spring Cloud Stream to move to event driven synchronization. Signed-off-by: Brandon Schmitt --- hawkbit-autoconfigure/pom.xml | 8 +- .../dmf/hono/DmfHonoAutoConfiguration.java | 15 + .../main/resources/META-INF/spring.factories | 1 + hawkbit-dmf/hawkbit-dmf-hono/pom.xml | 33 +++ .../dmf/hono/DmfHonoConfiguration.java | 38 +++ .../eclipse/hawkbit/dmf/hono/HonoDevice.java | 42 +++ .../hawkbit/dmf/hono/HonoDeviceListPage.java | 24 ++ .../hawkbit/dmf/hono/HonoDeviceSync.java | 257 ++++++++++++++++++ .../hawkbit/dmf/hono/HonoInputSink.java | 19 ++ .../eclipse/hawkbit/dmf/hono/HonoTenant.java | 13 + .../hawkbit/dmf/hono/HonoTenantListPage.java | 24 ++ .../dmf/hono/IdentifiableHonoDevice.java | 22 ++ .../dmf/hono/IdentifiableHonoTenant.java | 22 ++ hawkbit-dmf/pom.xml | 1 + .../hawkbit-repository-hono/pom.xml | 41 --- .../repository/hono/HonoTargetSync.java | 132 --------- hawkbit-repository/pom.xml | 1 - hawkbit-runtime/hawkbit-update-server/pom.xml | 5 + 18 files changed, 523 insertions(+), 175 deletions(-) create mode 100644 hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/pom.xml create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java delete mode 100644 hawkbit-repository/hawkbit-repository-hono/pom.xml delete mode 100644 hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index 2f0738d78d..e609118c23 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -25,12 +25,18 @@ ${project.version} true - + org.eclipse.hawkbit hawkbit-dmf-amqp ${project.version} true + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + true + org.eclipse.hawkbit hawkbit-ui diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java new file mode 100644 index 0000000000..e63ac38a47 --- /dev/null +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java @@ -0,0 +1,15 @@ +package org.eclipse.hawkbit.autoconfigure.dmf.hono; + +import org.eclipse.hawkbit.dmf.hono.DmfHonoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * The Eclipse Hono based device Management Federation API (DMF) auto configuration. + */ +@Configuration +@ConditionalOnClass(DmfHonoConfiguration.class) +@Import(DmfHonoConfiguration.class) +public class DmfHonoAutoConfiguration { +} \ No newline at end of file diff --git a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories index 716504d52d..912968d95b 100644 --- a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories @@ -4,6 +4,7 @@ org.eclipse.hawkbit.autoconfigure.cache.CacheAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.cache.DownloadIdCacheAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.ddi.DDiApiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.dmf.amqp.DmfApiAutoConfiguration,\ +org.eclipse.hawkbit.autoconfigure.dmf.hono.DmfHonoAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.mgmt.ui.MgmtUiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.repository.event.EventPublisherAutoConfiguration,\ diff --git a/hawkbit-dmf/hawkbit-dmf-hono/pom.xml b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml new file mode 100644 index 0000000000..46e7d76c25 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + + hawkbit-dmf-parent + org.eclipse.hawkbit + 0.3.0-SNAPSHOT + + hawkbit-dmf-hono + hawkBit :: DMF :: Hono + + + + org.springframework.cloud + spring-cloud-stream + + + org.eclipse.hawkbit + hawkbit-repository-api + 0.3.0-SNAPSHOT + compile + + + org.eclipse.hawkbit + hawkbit-repository-core + 0.3.0-SNAPSHOT + compile + + + diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java new file mode 100644 index 0000000000..816a5ff390 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -0,0 +1,38 @@ +package org.eclipse.hawkbit.dmf.hono; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +public class DmfHonoConfiguration { + + @Value("${hawkbit.server.repository.hono-sync.tenant-list-uri}") + private String honoTenantListUri; + + @Value("${hawkbit.server.repository.hono-sync.device-list-uri}") + private String honoDeviceListUri; + + @Value("${hawkbit.server.repository.hono-sync.authorization-method:none}") + private String authorizationMethod; + + @Value("${hawkbit.server.repository.hono-sync.oidc-token-uri:}") + private String oidcTokenUri; + + @Value("${hawkbit.server.repository.hono-sync.oidc-client-id:}") + private String oidcClientId; + + @Value("${hawkbit.server.repository.hono-sync.user.name:}") + private String username; + + @Value("${hawkbit.server.repository.hono-sync.user.password:}") + private String password; + + @Bean + public HonoDeviceSync honoDeviceSync() { + return new HonoDeviceSync(honoTenantListUri, honoDeviceListUri, authorizationMethod, oidcTokenUri, oidcClientId, + username, password); + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java new file mode 100644 index 0000000000..77beac280e --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java @@ -0,0 +1,42 @@ +package org.eclipse.hawkbit.dmf.hono; + +import com.fasterxml.jackson.databind.JsonNode; + +class HonoDevice { + private String id; + private String tenant; + private boolean enabled; + private JsonNode ext; + + String getId() { + return id; + } + + String getTenant() { + return tenant; + } + + boolean isEnabled() { + return enabled; + } + + Object getExt() { + return ext; + } + + void setId(String id) { + this.id = id; + } + + void setTenant(String tenant) { + this.tenant = tenant; + } + + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + void setExt(JsonNode ext) { + this.ext = ext; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java new file mode 100644 index 0000000000..48ed5e24f7 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java @@ -0,0 +1,24 @@ +package org.eclipse.hawkbit.dmf.hono; + +import java.util.List; + +public class HonoDeviceListPage { + private long total; + private List items; + + public long getTotal() { + return total; + } + + public List getItems() { + return items; + } + + public void setTotal(long total) { + this.total = total; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java new file mode 100644 index 0000000000..1176ca8a52 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -0,0 +1,257 @@ +package org.eclipse.hawkbit.dmf.hono; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.data.domain.PageRequest; + +import javax.annotation.PostConstruct; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.Instant; +import java.util.*; + +@EnableBinding(HonoInputSink.class) +public class HonoDeviceSync { + + private static final Logger LOG = LoggerFactory.getLogger(HonoDeviceSync.class); + + @Autowired + private EntityFactory entityFactory; + + @Autowired + private SystemManagement systemManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Autowired + private TargetManagement targetManagement; + + private ObjectMapper objectMapper; + + private String oidcAccessToken; + private Instant oidcAccessTokenExpirationDate; + + private String honoTenantListUri; + private String honoDeviceListUri; + private String authorizationMethod; + private String oidcTokenUri; + private String oidcClientId; + private String username; + private String password; + + HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String authorizationMethod, + String oidcTokenUri, String oidcClientId, String username, String password) { + this.honoTenantListUri = honoTenantListUri; + this.honoDeviceListUri = honoDevicesEndpoint; + this.authorizationMethod = authorizationMethod; + this.oidcTokenUri = oidcTokenUri; + this.oidcClientId = oidcClientId; + this.username = username; + this.password = password; + + objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + oidcAccessToken = null; + } + + @PostConstruct + public void synchronize() { + try { + List tenants = getAllHonoTenants(); + for (IdentifiableHonoTenant honoTenant : tenants) { + String tenant = honoTenant.getId(); + Map honoDevices = getAllHonoDevices(tenant); + List targets = getAllHawkbitTargets(tenant); + + for (Target target : targets) { + String controllerId = target.getControllerId(); + if (honoDevices.containsKey(controllerId)) { + HonoDevice honoDevice = honoDevices.remove(controllerId); + honoDevice.setTenant(tenant); + updateTarget(honoDevice); + } + else { + systemSecurityContext.runAsSystemAsTenant(() -> { + targetManagement.deleteByControllerID(target.getControllerId()); + return true; + }, tenant); + } + } + + // At this point honoTargets only contains objects which were not found in hawkBit's target repository + for (Map.Entry entry : honoDevices.entrySet()) { + HonoDevice device = entry.getValue(); + device.setTenant(tenant); + systemSecurityContext.runAsSystemAsTenant(() -> createTarget(device), tenant); + } + } + } catch (IOException e) { + LOG.error("Could not parse hono api response", e); + } + } + + private List getAllHawkbitTargets(String tenant) { + List targets = new ArrayList<>(); + + return systemSecurityContext.runAsSystemAsTenant(() -> { + long count = targetManagement.count(); + for (int i = 0; count > 0; count -= Integer.MAX_VALUE, ++i) { + targets.addAll(targetManagement.findAll(PageRequest.of(i, Integer.MAX_VALUE)).getContent()); + } + + return targets; + }, tenant); + } + + private List getAllHonoTenants() throws IOException { + List tenants = new ArrayList<>(); + long offset = 0; + long total = Long.MAX_VALUE; + while (tenants.size() < total) { + URL url = new URL(honoTenantListUri + "?offset=" + offset); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + addAuthorizationHeader(connection); + + HonoTenantListPage page = objectMapper.readValue(connection.getInputStream(), HonoTenantListPage.class); + tenants.addAll(page.getItems()); + offset += page.getItems().size(); + total = page.getTotal(); + } + + return tenants; + } + + private Map getAllHonoDevices(String tenant) throws IOException { + Map devices = new HashMap<>(); + long offset = 0; + long total = Long.MAX_VALUE; + while (devices.size() < total) { + URL url = new URL(honoDeviceListUri.replace("$tenantId", tenant) + "?offset=" + offset); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + addAuthorizationHeader(connection); + + HonoDeviceListPage page = objectMapper.readValue(connection.getInputStream(), HonoDeviceListPage.class); + for (IdentifiableHonoDevice identifiableDevice : page.getItems()) { + devices.put(identifiableDevice.getId(), identifiableDevice.getDevice()); + } + offset += page.getItems().size(); + total = page.getTotal(); + } + + return devices; + } + + private void addAuthorizationHeader(HttpURLConnection connection) throws IOException { + switch (authorizationMethod) { + case "basic": + connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())); + break; + + case "oidc": + if (oidcAccessToken == null || + (oidcAccessTokenExpirationDate != null && oidcAccessTokenExpirationDate.isBefore(Instant.now()))) { + + URL url = new URL(oidcTokenUri); + HttpURLConnection jwtConnection = (HttpURLConnection) url.openConnection(); + jwtConnection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(jwtConnection.getOutputStream()); + outputStream.writeBytes("grant_type=password&client_id=" + oidcClientId + "&username=" + username + "&password=" + password); + outputStream.flush(); + outputStream.close(); + + int statusCode = jwtConnection.getResponseCode(); + if (statusCode >= 200 && statusCode < 300) { + JsonNode node = objectMapper.readValue(jwtConnection.getInputStream(), JsonNode.class); + oidcAccessToken = node.get("access_token").asText(); + JsonNode expiresIn = node.get("expires_in"); + if (expiresIn != null) { + oidcAccessTokenExpirationDate = Instant.now().plusSeconds(expiresIn.asLong()); + } + } + else { + throw new IOException("Server returned HTTP response code: " + statusCode + " for URL: " + url.toString()); + } + } + connection.setRequestProperty("Authorization", "Bearer " + oidcAccessToken); + break; + } + } + + @StreamListener(HonoInputSink.DEVICE_CREATED) + public void onDeviceCreated(HonoDevice honoDevice) { + final String tenant = honoDevice.getTenant(); + if (tenant == null) { + throw new RuntimeException("The delivered hono device does not contain information about the tenant"); + } + + systemSecurityContext.runAsSystemAsTenant(() -> createTarget(honoDevice), tenant); + } + + @StreamListener(HonoInputSink.DEVICE_UPDATED) + public void onDeviceUpdated(HonoDevice honoDevice) { + final String tenant = honoDevice.getTenant(); + if (tenant == null) { + throw new RuntimeException("The delivered hono device does not contain information about the tenant"); + } + + systemSecurityContext.runAsSystemAsTenant(() -> { + if (targetManagement.getByControllerID(honoDevice.getId()).isPresent()) { + return updateTarget(honoDevice); + } + else { + return createTarget(honoDevice); + } + }, tenant); + } + + @StreamListener(HonoInputSink.DEVICE_DELETED) + public void onDeviceDeleted(HonoDevice honoDevice) { + final String tenant = honoDevice.getTenant(); + if (tenant == null) { + throw new RuntimeException("The delivered hono device does not contain information about the tenant"); + } + + systemSecurityContext.runAsSystemAsTenant(() -> { + try { + targetManagement.deleteByControllerID(honoDevice.getId()); + } + catch (EntityNotFoundException e) { + // Do nothing as it is already deleted + } + return true; + }, tenant); + } + + private Target createTarget(HonoDevice honoDevice) { + // Create tenant if necessary + if (SystemManagementHolder.getInstance().getSystemManagement() == null) { + SystemManagementHolder.getInstance().setSystemManagement(systemManagement); // Make sure it's set. + } + systemManagement.getTenantMetadata(honoDevice.getTenant()); + return targetManagement.create(entityFactory.target().create() + .controllerId(honoDevice.getId()).description(honoDevice.getExt().toString())); + } + + private Target updateTarget(HonoDevice honoDevice) { + return targetManagement.update(entityFactory.target() + .update(honoDevice.getId()) + .description(honoDevice.getExt().toString())); + } + +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java new file mode 100644 index 0000000000..a3722390c2 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java @@ -0,0 +1,19 @@ +package org.eclipse.hawkbit.dmf.hono; + +import org.springframework.cloud.stream.annotation.Input; +import org.springframework.messaging.SubscribableChannel; + +public interface HonoInputSink { + String DEVICE_CREATED = "device-created"; + String DEVICE_DELETED = "device-deleted"; + String DEVICE_UPDATED = "device-updated"; + + @Input(DEVICE_CREATED) + SubscribableChannel onDeviceCreated(); + + @Input(DEVICE_UPDATED) + SubscribableChannel onDeviceUpdated(); + + @Input(DEVICE_DELETED) + SubscribableChannel onDeviceDeleted(); +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java new file mode 100644 index 0000000000..60f893d88a --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java @@ -0,0 +1,13 @@ +package org.eclipse.hawkbit.dmf.hono; + +public class HonoTenant { + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java new file mode 100644 index 0000000000..e048f7ecb8 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java @@ -0,0 +1,24 @@ +package org.eclipse.hawkbit.dmf.hono; + +import java.util.List; + +class HonoTenantListPage { + private long total; + private List items; + + long getTotal() { + return total; + } + + List getItems() { + return items; + } + + public void setTotal(long total) { + this.total = total; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java new file mode 100644 index 0000000000..26660a341a --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java @@ -0,0 +1,22 @@ +package org.eclipse.hawkbit.dmf.hono; + +public class IdentifiableHonoDevice { + private String id; + private HonoDevice device; + + public String getId() { + return id; + } + + public HonoDevice getDevice() { + return device; + } + + public void setId(String id) { + this.id = id; + } + + public void setDevice(HonoDevice device) { + this.device = device; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java new file mode 100644 index 0000000000..225fa19de0 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java @@ -0,0 +1,22 @@ +package org.eclipse.hawkbit.dmf.hono; + +class IdentifiableHonoTenant { + private String id; + private HonoTenant tenant; + + public String getId() { + return id; + } + + public HonoTenant getTenant() { + return tenant; + } + + public void setId(String id) { + this.id = id; + } + + public void setTenant(HonoTenant tenant) { + this.tenant = tenant; + } +} diff --git a/hawkbit-dmf/pom.xml b/hawkbit-dmf/pom.xml index 17fc38b603..3e5678baa3 100644 --- a/hawkbit-dmf/pom.xml +++ b/hawkbit-dmf/pom.xml @@ -27,6 +27,7 @@ hawkbit-dmf-api hawkbit-dmf-amqp + hawkbit-dmf-hono hawkbit-dmf-rabbitmq-test diff --git a/hawkbit-repository/hawkbit-repository-hono/pom.xml b/hawkbit-repository/hawkbit-repository-hono/pom.xml deleted file mode 100644 index 375e5ffcb5..0000000000 --- a/hawkbit-repository/hawkbit-repository-hono/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - org.eclipse.hawkbit - hawkbit-repository - 0.3.0-SNAPSHOT - - - hawkbit-repository-hono - hawkBit :: Repository :: Hono-Sync - - - - - org.eclipse.hawkbit - hawkbit-repository-api - ${project.version} - - - org.springframework.boot - spring-boot-starter-data-jpa - - - net.minidev - json-smart - 2.3 - - - - \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java b/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java deleted file mode 100644 index 4aa97b1d12..0000000000 --- a/hawkbit-repository/hawkbit-repository-hono/src/main/java/org/eclipse/hawkbit/repository/hono/HonoTargetSync.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2019 Kiwigrid 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.hono; - -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.eclipse.hawkbit.repository.EntityFactory; -import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.model.Target; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class HonoTargetSync implements Runnable { - - private static final Logger LOG = LoggerFactory.getLogger(HonoTargetSync.class); - - @Autowired - private TargetManagement targetManagement; - - @Autowired - private EntityFactory entityFactory; - - @Value("${hawkbit.server.repository.hono-sync.devices-uri}") - private String honoDevicesEndpoint; - - @Override - public void run() { - try { - Map honoTargets = getAllTargets(); - List targetIdsToDelete = new ArrayList<>(); - - long count = targetManagement.count(); - for (int i = 0; count > 0; count -= 100, ++i) { - Slice slice = targetManagement.findAll(PageRequest.of(i, 100)); - for (Target target : slice) { - Long id = target.getId(); - - if (honoTargets.containsKey(id)) { - // TODO: Update Target if necessary! What properties will be synchronized? - // Remove target from map since it won't be needed anymore - honoTargets.remove(id); - } else { - targetIdsToDelete.add(id); - } - } - } - - if (!targetIdsToDelete.isEmpty()) { - targetManagement.delete(targetIdsToDelete); - } - - // At this point honoTargets only contains objects which were not found in hawkBit's target repository - for (Map.Entry entry : honoTargets.entrySet()) { - targetManagement.create(entityFactory.target().create() - .controllerId(entry.getKey().toString()) - ); - } - } catch (IOException | NoSuchFieldException | ParseException e) { - LOG.error("Could not parse hono api response", e); - } - } - - private Map getAllTargets() throws IOException, ParseException, NoSuchFieldException { - - Map honoTargets = new HashMap<>(); - - // Initialize the total variable with an arbitrary number > 0 so it starts fetching targets. It will be updated - // with the correct number during the interpretation of the first response. - long total = Long.MAX_VALUE; - - JSONParser parser = new JSONParser(); - while (honoTargets.size() < total) { - URL url = new URL(honoDevicesEndpoint); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - - connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.writeBytes("offset=" + honoTargets.size()); - outputStream.flush(); - outputStream.close(); - - int statusCode = connection.getResponseCode(); - if (statusCode >= 200 && statusCode < 300) { - String totalHeader = connection.getHeaderField("Total"); - if (totalHeader == null) { - throw new NoSuchFieldException("Response does not contain expected HTTP header \"Total\"."); - } - total = Long.parseLong(totalHeader); - - Object response = parser.parse(connection.getInputStream()); - if (response instanceof JSONArray) { - JSONArray targetArray = (JSONArray) response; - for (Object object : targetArray) { - JSONObject target = (JSONObject) object; - Long controllerId = (Long) target.get("controllerId"); - - honoTargets.put(controllerId, entityFactory.target().create() - .controllerId(String.valueOf(controllerId)) - .build()); - } - } - } - else { - throw new IOException("Received HTTP status code " + statusCode + " connecting to " + url.toString()); - } - } - - return honoTargets; - } -} diff --git a/hawkbit-repository/pom.xml b/hawkbit-repository/pom.xml index ad7c30b1e4..f11d9d75f8 100644 --- a/hawkbit-repository/pom.xml +++ b/hawkbit-repository/pom.xml @@ -29,7 +29,6 @@ hawkbit-repository-api hawkbit-repository-test hawkbit-repository-core - hawkbit-repository-hono diff --git a/hawkbit-runtime/hawkbit-update-server/pom.xml b/hawkbit-runtime/hawkbit-update-server/pom.xml index 242b70f75a..3250962cd0 100644 --- a/hawkbit-runtime/hawkbit-update-server/pom.xml +++ b/hawkbit-runtime/hawkbit-update-server/pom.xml @@ -87,6 +87,11 @@ ${project.version} test + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + From b77c093dbc00b8c5cf2cc2d1a0fdfc268a3aa47a Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Wed, 21 Aug 2019 17:15:25 +0200 Subject: [PATCH 04/28] Sync with Hono on startup Signed-off-by: Brandon Schmitt --- hawkbit-autoconfigure/pom.xml | 6 -- .../repository/HonoSyncAutoConfiguration.java | 39 -------- hawkbit-dmf/hawkbit-dmf-hono/pom.xml | 6 ++ .../eclipse/hawkbit/dmf/hono/HonoDevice.java | 18 ---- .../hawkbit/dmf/hono/HonoDeviceSync.java | 96 ++++++++++--------- .../dmf/hono/IdentifiableHonoDevice.java | 9 ++ hawkbit-runtime/hawkbit-update-server/pom.xml | 4 + 7 files changed, 68 insertions(+), 110 deletions(-) delete mode 100644 hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index e609118c23..dfc5c73bf3 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -43,12 +43,6 @@ ${project.version} true - - org.eclipse.hawkbit - hawkbit-repository-hono - ${project.version} - true - org.eclipse.hawkbit hawkbit-repository-jpa diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java deleted file mode 100644 index 49603f680b..0000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/HonoSyncAutoConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2019 Kiwigrid 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.autoconfigure.repository; - -import org.eclipse.hawkbit.repository.hono.HonoTargetSync; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Configuration -@ConditionalOnProperty(prefix = "hawkbit.server.repository.hono-sync", name = "devices-uri") -public class HonoSyncAutoConfiguration { - - @Value("${hawkbit.server.repository.hono-sync.poll-rate:60}") - private Integer pollRate; - - @Bean - @ConditionalOnMissingBean - public HonoTargetSync honoTargetSync() { - HonoTargetSync honoTargetSync = new HonoTargetSync(); - - ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleAtFixedRate(honoTargetSync, 0, pollRate, TimeUnit.SECONDS); - - return honoTargetSync; - } -} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/pom.xml b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml index 46e7d76c25..d5dc5f81bd 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml @@ -29,5 +29,11 @@ 0.3.0-SNAPSHOT compile + + org.eclipse.hawkbit + hawkbit-repository-jpa + 0.3.0-SNAPSHOT + compile + diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java index 77beac280e..11243f5184 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java @@ -3,19 +3,9 @@ import com.fasterxml.jackson.databind.JsonNode; class HonoDevice { - private String id; - private String tenant; private boolean enabled; private JsonNode ext; - String getId() { - return id; - } - - String getTenant() { - return tenant; - } - boolean isEnabled() { return enabled; } @@ -24,14 +14,6 @@ Object getExt() { return ext; } - void setId(String id) { - this.id = id; - } - - void setTenant(String tenant) { - this.tenant = tenant; - } - void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 1176ca8a52..4162de7f9b 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -1,5 +1,6 @@ package org.eclipse.hawkbit.dmf.hono; +import com.esotericsoftware.minlog.Log; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -8,22 +9,24 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.data.domain.PageRequest; +import org.springframework.context.event.EventListener; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; -import javax.annotation.PostConstruct; import java.io.DataOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.time.Instant; import java.util.*; +import java.util.concurrent.Semaphore; @EnableBinding(HonoInputSink.class) public class HonoDeviceSync { @@ -42,9 +45,12 @@ public class HonoDeviceSync { @Autowired private TargetManagement targetManagement; - private ObjectMapper objectMapper; + private ObjectMapper objectMapper = new ObjectMapper(); - private String oidcAccessToken; + private Semaphore mutex = new Semaphore(1); + private boolean syncedInitially = false; + + private String oidcAccessToken = null; private Instant oidcAccessTokenExpirationDate; private String honoTenantListUri; @@ -65,26 +71,35 @@ public class HonoDeviceSync { this.username = username; this.password = password; - objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - oidcAccessToken = null; } - @PostConstruct + @EventListener(ApplicationReadyEvent.class) + private void initialSync() { + // Since ApplicationReadyEvent is emitted multiple times make sure it is synced at most once during startup. + if (!syncedInitially) { + synchronize(); + syncedInitially = true; + } + } + public void synchronize() { try { + mutex.acquire(); + List tenants = getAllHonoTenants(); for (IdentifiableHonoTenant honoTenant : tenants) { String tenant = honoTenant.getId(); - Map honoDevices = getAllHonoDevices(tenant); - List targets = getAllHawkbitTargets(tenant); + Map honoDevices = getAllHonoDevices(tenant); + Slice targets = systemSecurityContext.runAsSystemAsTenant( + () -> targetManagement.findAll(Pageable.unpaged()), tenant); for (Target target : targets) { String controllerId = target.getControllerId(); if (honoDevices.containsKey(controllerId)) { - HonoDevice honoDevice = honoDevices.remove(controllerId); + IdentifiableHonoDevice honoDevice = honoDevices.remove(controllerId); honoDevice.setTenant(tenant); - updateTarget(honoDevice); + systemSecurityContext.runAsSystemAsTenant(() -> updateTarget(honoDevice), tenant); } else { systemSecurityContext.runAsSystemAsTenant(() -> { @@ -95,29 +110,18 @@ public void synchronize() { } // At this point honoTargets only contains objects which were not found in hawkBit's target repository - for (Map.Entry entry : honoDevices.entrySet()) { - HonoDevice device = entry.getValue(); - device.setTenant(tenant); - systemSecurityContext.runAsSystemAsTenant(() -> createTarget(device), tenant); + for (Map.Entry entry : honoDevices.entrySet()) { + systemSecurityContext.runAsSystemAsTenant(() -> createTarget(entry.getValue()), tenant); } } + + mutex.release(); } catch (IOException e) { - LOG.error("Could not parse hono api response", e); + LOG.error("Could not parse hono api response.", e); + } catch (InterruptedException e) { + LOG.error("Synchronizing hawkbit with Hono has been interrupted.", e); } } - - private List getAllHawkbitTargets(String tenant) { - List targets = new ArrayList<>(); - - return systemSecurityContext.runAsSystemAsTenant(() -> { - long count = targetManagement.count(); - for (int i = 0; count > 0; count -= Integer.MAX_VALUE, ++i) { - targets.addAll(targetManagement.findAll(PageRequest.of(i, Integer.MAX_VALUE)).getContent()); - } - - return targets; - }, tenant); - } private List getAllHonoTenants() throws IOException { List tenants = new ArrayList<>(); @@ -137,8 +141,8 @@ private List getAllHonoTenants() throws IOException { return tenants; } - private Map getAllHonoDevices(String tenant) throws IOException { - Map devices = new HashMap<>(); + private Map getAllHonoDevices(String tenant) throws IOException { + Map devices = new HashMap<>(); long offset = 0; long total = Long.MAX_VALUE; while (devices.size() < total) { @@ -147,10 +151,13 @@ private Map getAllHonoDevices(String tenant) throws IOExcept addAuthorizationHeader(connection); HonoDeviceListPage page = objectMapper.readValue(connection.getInputStream(), HonoDeviceListPage.class); - for (IdentifiableHonoDevice identifiableDevice : page.getItems()) { - devices.put(identifiableDevice.getId(), identifiableDevice.getDevice()); + if (page.getItems() != null) { + for (IdentifiableHonoDevice identifiableDevice : page.getItems()) { + identifiableDevice.setTenant(tenant); + devices.put(identifiableDevice.getId(), identifiableDevice); + } + offset += page.getItems().size(); } - offset += page.getItems().size(); total = page.getTotal(); } @@ -194,7 +201,7 @@ private void addAuthorizationHeader(HttpURLConnection connection) throws IOExcep } @StreamListener(HonoInputSink.DEVICE_CREATED) - public void onDeviceCreated(HonoDevice honoDevice) { + public void onDeviceCreated(IdentifiableHonoDevice honoDevice) { final String tenant = honoDevice.getTenant(); if (tenant == null) { throw new RuntimeException("The delivered hono device does not contain information about the tenant"); @@ -204,7 +211,7 @@ public void onDeviceCreated(HonoDevice honoDevice) { } @StreamListener(HonoInputSink.DEVICE_UPDATED) - public void onDeviceUpdated(HonoDevice honoDevice) { + public void onDeviceUpdated(IdentifiableHonoDevice honoDevice) { final String tenant = honoDevice.getTenant(); if (tenant == null) { throw new RuntimeException("The delivered hono device does not contain information about the tenant"); @@ -221,7 +228,7 @@ public void onDeviceUpdated(HonoDevice honoDevice) { } @StreamListener(HonoInputSink.DEVICE_DELETED) - public void onDeviceDeleted(HonoDevice honoDevice) { + public void onDeviceDeleted(IdentifiableHonoDevice honoDevice) { final String tenant = honoDevice.getTenant(); if (tenant == null) { throw new RuntimeException("The delivered hono device does not contain information about the tenant"); @@ -238,20 +245,15 @@ public void onDeviceDeleted(HonoDevice honoDevice) { }, tenant); } - private Target createTarget(HonoDevice honoDevice) { - // Create tenant if necessary - if (SystemManagementHolder.getInstance().getSystemManagement() == null) { - SystemManagementHolder.getInstance().setSystemManagement(systemManagement); // Make sure it's set. - } + private Target createTarget(IdentifiableHonoDevice honoDevice) { systemManagement.getTenantMetadata(honoDevice.getTenant()); return targetManagement.create(entityFactory.target().create() - .controllerId(honoDevice.getId()).description(honoDevice.getExt().toString())); + .controllerId(honoDevice.getId()).description(honoDevice.getDevice().getExt().toString())); } - private Target updateTarget(HonoDevice honoDevice) { + private Target updateTarget(IdentifiableHonoDevice honoDevice) { return targetManagement.update(entityFactory.target() .update(honoDevice.getId()) - .description(honoDevice.getExt().toString())); + .description(honoDevice.getDevice().getExt().toString())); } - } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java index 26660a341a..6d9d43c28e 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java @@ -2,12 +2,17 @@ public class IdentifiableHonoDevice { private String id; + private String tenant; private HonoDevice device; public String getId() { return id; } + String getTenant() { + return tenant; + } + public HonoDevice getDevice() { return device; } @@ -16,6 +21,10 @@ public void setId(String id) { this.id = id; } + void setTenant(String tenant) { + this.tenant = tenant; + } + public void setDevice(HonoDevice device) { this.device = device; } diff --git a/hawkbit-runtime/hawkbit-update-server/pom.xml b/hawkbit-runtime/hawkbit-update-server/pom.xml index 3250962cd0..04d1c84018 100644 --- a/hawkbit-runtime/hawkbit-update-server/pom.xml +++ b/hawkbit-runtime/hawkbit-update-server/pom.xml @@ -92,6 +92,10 @@ hawkbit-dmf-hono ${project.version} + + org.springframework.cloud + spring-cloud-stream-binder-rabbit + From cf2a4089c2465048adefab347d696aa291aaee54 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 26 Aug 2019 15:33:36 +0200 Subject: [PATCH 05/28] Allow frontend users to initiate the sync process for their current tenant. Signed-off-by: Brandon Schmitt --- .../hawkbit/dmf/hono/HonoDeviceSync.java | 70 +++++++++++-------- hawkbit-runtime/hawkbit-update-server/pom.xml | 43 +++++++++--- hawkbit-ui/pom.xml | 5 ++ .../AbstractDistributionSetTableHeader.java | 5 ++ .../AbstractSoftwareModuleTableHeader.java | 15 ++++ .../ui/common/table/AbstractTableHeader.java | 51 ++++++++++++-- .../dstable/DistributionSetTableHeader.java | 10 +++ .../hawkbit/ui/management/DeploymentView.java | 6 +- .../dstable/DistributionTableHeader.java | 10 +++ .../targettable/TargetTableHeader.java | 23 +++++- .../targettable/TargetTableLayout.java | 6 +- .../ui/utils/UIComponentIdProvider.java | 5 ++ .../hawkbit/ui/utils/UIMessageIdProvider.java | 2 + .../src/main/resources/messages.properties | 1 + 14 files changed, 206 insertions(+), 46 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 4162de7f9b..ecf36c729e 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -1,6 +1,5 @@ package org.eclipse.hawkbit.dmf.hono; -import com.esotericsoftware.minlog.Log; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -47,7 +46,7 @@ public class HonoDeviceSync { private ObjectMapper objectMapper = new ObjectMapper(); - private Semaphore mutex = new Semaphore(1); + private Map mutexes = new HashMap<>(); private boolean syncedInitially = false; private String oidcAccessToken = null; @@ -78,44 +77,28 @@ public class HonoDeviceSync { private void initialSync() { // Since ApplicationReadyEvent is emitted multiple times make sure it is synced at most once during startup. if (!syncedInitially) { - synchronize(); + synchronize(false); syncedInitially = true; } } - public void synchronize() { + public void synchronize(boolean syncOnlyCurrentTenant) { try { - mutex.acquire(); + String currentTenant = null; + if (syncOnlyCurrentTenant) { + currentTenant = systemManagement.currentTenant(); + } List tenants = getAllHonoTenants(); for (IdentifiableHonoTenant honoTenant : tenants) { String tenant = honoTenant.getId(); - Map honoDevices = getAllHonoDevices(tenant); - Slice targets = systemSecurityContext.runAsSystemAsTenant( - () -> targetManagement.findAll(Pageable.unpaged()), tenant); - - for (Target target : targets) { - String controllerId = target.getControllerId(); - if (honoDevices.containsKey(controllerId)) { - IdentifiableHonoDevice honoDevice = honoDevices.remove(controllerId); - honoDevice.setTenant(tenant); - systemSecurityContext.runAsSystemAsTenant(() -> updateTarget(honoDevice), tenant); - } - else { - systemSecurityContext.runAsSystemAsTenant(() -> { - targetManagement.deleteByControllerID(target.getControllerId()); - return true; - }, tenant); - } - } - // At this point honoTargets only contains objects which were not found in hawkBit's target repository - for (Map.Entry entry : honoDevices.entrySet()) { - systemSecurityContext.runAsSystemAsTenant(() -> createTarget(entry.getValue()), tenant); + if (syncOnlyCurrentTenant && !tenant.equals(currentTenant)) { + continue; } - } - mutex.release(); + synchronizeTenant(tenant); + } } catch (IOException e) { LOG.error("Could not parse hono api response.", e); } catch (InterruptedException e) { @@ -123,6 +106,37 @@ public void synchronize() { } } + private void synchronizeTenant(String tenant) throws IOException, InterruptedException { + Semaphore semaphore = mutexes.computeIfAbsent(tenant, t -> new Semaphore(1)); + semaphore.acquire(); + + Map honoDevices = getAllHonoDevices(tenant); + Slice targets = systemSecurityContext.runAsSystemAsTenant( + () -> targetManagement.findAll(Pageable.unpaged()), tenant); + + for (Target target : targets) { + String controllerId = target.getControllerId(); + if (honoDevices.containsKey(controllerId)) { + IdentifiableHonoDevice honoDevice = honoDevices.remove(controllerId); + honoDevice.setTenant(tenant); + systemSecurityContext.runAsSystemAsTenant(() -> updateTarget(honoDevice), tenant); + } + else { + systemSecurityContext.runAsSystemAsTenant(() -> { + targetManagement.deleteByControllerID(target.getControllerId()); + return true; + }, tenant); + } + } + + // At this point honoTargets only contains objects which were not found in hawkBit's target repository + for (Map.Entry entry : honoDevices.entrySet()) { + systemSecurityContext.runAsSystemAsTenant(() -> createTarget(entry.getValue()), tenant); + } + + semaphore.release(); + } + private List getAllHonoTenants() throws IOException { List tenants = new ArrayList<>(); long offset = 0; diff --git a/hawkbit-runtime/hawkbit-update-server/pom.xml b/hawkbit-runtime/hawkbit-update-server/pom.xml index 04d1c84018..7e7c2344ac 100644 --- a/hawkbit-runtime/hawkbit-update-server/pom.xml +++ b/hawkbit-runtime/hawkbit-update-server/pom.xml @@ -87,15 +87,40 @@ ${project.version} test - - org.eclipse.hawkbit - hawkbit-dmf-hono - ${project.version} - - - org.springframework.cloud - spring-cloud-stream-binder-rabbit - + + + hono-ampq + + true + + + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + + + org.springframework.cloud + spring-cloud-stream-binder-rabbit + + + + + hono-gcp-pubsub + + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + + + org.springframework.cloud + spring-cloud-gcp-pubsub-stream-binder + + + + + diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index 85aa510859..87fa55879b 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -168,6 +168,11 @@ hawkbit-repository-api ${project.version} + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + commons-io commons-io diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java index f89acde75d..7f493a5ed3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java @@ -54,6 +54,11 @@ protected String getAddIconId() { return UIComponentIdProvider.DIST_ADD_ICON; } + @Override + protected String getSyncIconId() { + return null; + } + @Override protected boolean isDropHintRequired() { return true; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java index 78f9828186..535f2feff7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java @@ -61,6 +61,11 @@ protected String getAddIconId() { return UIComponentIdProvider.SW_MODULE_ADD_BUTTON; } + @Override + protected String getSyncIconId() { + return null; + } + @Override protected String getShowFilterButtonLayoutId() { return "show.type.icon"; @@ -76,6 +81,11 @@ protected Boolean isAddNewItemAllowed() { return Boolean.TRUE; } + @Override + protected Boolean isHonoSyncAllowed() { + return Boolean.FALSE; + } + @Override protected void addNewItem(final ClickEvent event) { final Window addSoftwareModule = softwareModuleAddUpdateWindow.createAddSoftwareModuleWindow(); @@ -84,6 +94,11 @@ protected void addNewItem(final ClickEvent event) { addSoftwareModule.setVisible(Boolean.TRUE); } + @Override + protected void syncHono(final ClickEvent event) { + // do nothing + } + @Override protected boolean hasCreatePermission() { return permChecker.hasCreateRepositoryPermission(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java index ae88a3a0ff..851bb1bd19 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java @@ -59,6 +59,8 @@ public abstract class AbstractTableHeader extends VerticalLayout { private Button addIcon; + private Button syncHono; + private SPUIButton maxMinIcon; private HorizontalLayout filterDroppedInfo; @@ -106,6 +108,8 @@ private void createComponents() { addIcon = createAddIcon(); + syncHono = createSyncHonoIcon(); + bulkUploadIcon = createBulkUploadIcon(); showFilterButtonLayout = createShowFilterButtonLayout(); @@ -153,12 +157,22 @@ private void restoreState() { } private void hideAddAndUploadIcon() { - addIcon.setVisible(false); + if (syncHono == null) { + addIcon.setVisible(false); + } + else { + syncHono.setVisible(false); + } bulkUploadIcon.setVisible(false); } private void showAddAndUploadIcon() { - addIcon.setVisible(true); + if (syncHono == null) { + addIcon.setVisible(true); + } + else { + syncHono.setVisible(true); + } bulkUploadIcon.setVisible(true); } @@ -170,7 +184,11 @@ private void buildLayout() { titleFilterIconsLayout.setComponentAlignment(searchField, Alignment.TOP_RIGHT); titleFilterIconsLayout.setComponentAlignment(searchResetIcon, Alignment.TOP_RIGHT); titleFilterIconsLayout.setComponentAlignment(showFilterButtonLayout, Alignment.TOP_RIGHT); - if (hasCreatePermission() && isAddNewItemAllowed()) { + if (syncHono != null && isHonoSyncAllowed()) { + titleFilterIconsLayout.addComponent(syncHono); + titleFilterIconsLayout.setComponentAlignment(syncHono, Alignment.TOP_RIGHT); + } + else if (hasCreatePermission() && isAddNewItemAllowed()) { titleFilterIconsLayout.addComponent(addIcon); titleFilterIconsLayout.setComponentAlignment(addIcon, Alignment.TOP_RIGHT); } @@ -237,6 +255,14 @@ private Button createAddIcon() { return button; } + private Button createSyncHonoIcon() { + final Button button = SPUIComponentProvider.getButton(getSyncIconId(), "", + i18n.getMessage(UIMessageIdProvider.TOOLTIP_SYNC_HONO), null, false, FontAwesome.REFRESH, + SPUIButtonStyleNoBorder.class); + button.addClickListener(this::syncHono); + return button; + } + private Button createBulkUploadIcon() { final Button button = SPUIComponentProvider.getButton(getBulkUploadIconId(), "", i18n.getMessage(UIMessageIdProvider.TOOLTIP_BULK_UPLOAD), null, false, FontAwesome.UPLOAD, @@ -434,6 +460,14 @@ protected void reEnableSearch() { */ protected abstract Boolean isAddNewItemAllowed(); + /** + * Checks if the synchronization with Hono is allowed. Default is true. + * + * @return true if the synchronization with hono is allowed, otherwise returns + * false. + */ + protected abstract Boolean isHonoSyncAllowed(); + /** * Get Id of bulk upload Icon. * @@ -486,11 +520,18 @@ protected void reEnableSearch() { /** * Get Id of add Icon. - * + * * @return String of add Icon. */ protected abstract String getAddIconId(); + /** + * Get Id of sync Icon. + * + * @return String of sync Icon. + */ + protected abstract String getSyncIconId(); + /** * Get search box on load text value. * @@ -559,6 +600,8 @@ protected void reEnableSearch() { protected abstract void addNewItem(final Button.ClickEvent event); + protected abstract void syncHono(final Button.ClickEvent event); + protected ManagementUIState getManagementUIState() { return managementUIState; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTableHeader.java index 2bd2adf386..b8fe652e5a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTableHeader.java @@ -102,9 +102,19 @@ protected void addNewItem(final ClickEvent event) { newDistWindow.setVisible(Boolean.TRUE); } + @Override + protected void syncHono(final ClickEvent event) { + // do nothing + } + @Override protected Boolean isAddNewItemAllowed() { return Boolean.TRUE; } + @Override + protected Boolean isHonoSyncAllowed() { + return Boolean.FALSE; + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java index 170c746648..b6fb91647c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java @@ -13,6 +13,7 @@ import javax.annotation.PostConstruct; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; @@ -131,7 +132,8 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW final TargetFilterQueryManagement targetFilterQueryManagement, final SystemManagement systemManagement, final TenantConfigurationManagement configManagement, final SystemSecurityContext systemSecurityContext, final NotificationUnreadButton notificationUnreadButton, - final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor) { + final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor, + final HonoDeviceSync honoDeviceSync) { super(eventBus, notificationUnreadButton); this.permChecker = permChecker; this.i18n = i18n; @@ -156,7 +158,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW this.targetTableLayout = new TargetTableLayout(eventBus, targetTable, targetManagement, entityFactory, i18n, uiNotification, managementUIState, managementViewClientCriterion, deploymentManagement, - uiProperties, permChecker, targetTagManagement, distributionSetManagement, uiExecutor); + uiProperties, permChecker, targetTagManagement, distributionSetManagement, uiExecutor, honoDeviceSync); actionHistoryLayout.registerDetails(((ActionStatusGrid) actionStatusLayout.getGrid()).getDetailsSupport()); actionStatusLayout diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTableHeader.java index 077a20fe12..43a84d1da8 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionTableHeader.java @@ -96,9 +96,19 @@ protected void addNewItem(final ClickEvent event) { // is okay and not supported } + @Override + protected void syncHono(final ClickEvent event) { + // is okay and not supported + } + @Override protected Boolean isAddNewItemAllowed() { return Boolean.FALSE; } + @Override + protected Boolean isHonoSyncAllowed() { + return Boolean.FALSE; + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java index f00a6235b2..f0d6e097f1 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java @@ -12,6 +12,7 @@ import java.util.Set; import java.util.concurrent.Executor; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.EntityFactory; @@ -78,13 +79,15 @@ public class TargetTableHeader extends AbstractTableHeader { private final transient DistributionSetManagement distributionSetManagement; + private final transient HonoDeviceSync honoDeviceSync; + TargetTableHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, final UIEventBus eventBus, final UINotification notification, final ManagementUIState managementUIState, final ManagementViewClientCriterion managementViewClientCriterion, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final UiProperties uiproperties, final EntityFactory entityFactory, final UINotification uiNotification, final TargetTagManagement tagManagement, final DistributionSetManagement distributionSetManagement, - final Executor uiExecutor, final TargetTable targetTable) { + final Executor uiExecutor, final TargetTable targetTable, final HonoDeviceSync honoDeviceSync) { super(i18n, permChecker, eventBus, managementUIState, null, null); this.notification = notification; this.managementViewClientCriterion = managementViewClientCriterion; @@ -94,6 +97,7 @@ public class TargetTableHeader extends AbstractTableHeader { managementUIState, deploymentManagement, uiproperties, permChecker, uiNotification, tagManagement, distributionSetManagement, entityFactory, uiExecutor); this.distributionSetManagement = distributionSetManagement; + this.honoDeviceSync = honoDeviceSync; onLoadRestoreState(); } @@ -186,6 +190,11 @@ protected String getAddIconId() { return UIComponentIdProvider.TARGET_TBL_ADD_ICON_ID; } + @Override + protected String getSyncIconId() { + return UIComponentIdProvider.TARGET_TBL_SYNC_ICON_ID; + } + @Override protected String getBulkUploadIconId() { return UIComponentIdProvider.TARGET_TBL_BULK_UPLOAD_ICON_ID; @@ -286,6 +295,13 @@ protected void addNewItem(final ClickEvent event) { addTargetWindow.setVisible(Boolean.TRUE); } + @Override + protected void syncHono(final ClickEvent event) { + event.getButton().setEnabled(false); // Make sure there is only one synchronization process at a time. + honoDeviceSync.synchronize(true); + event.getButton().setEnabled(true); + } + @Override protected void bulkUpload(final ClickEvent event) { targetBulkUpdateWindow.resetComponents(); @@ -435,4 +451,9 @@ protected String getFilterIconStyle() { protected Boolean isAddNewItemAllowed() { return Boolean.TRUE; } + + @Override + protected Boolean isHonoSyncAllowed() { + return Boolean.TRUE; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java index 7545064946..ca4d271d83 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java @@ -10,6 +10,7 @@ import java.util.concurrent.Executor; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.EntityFactory; @@ -42,7 +43,8 @@ public TargetTableLayout(final UIEventBus eventBus, final TargetTable targetTabl final ManagementViewClientCriterion managementViewClientCriterion, final DeploymentManagement deploymentManagement, final UiProperties uiProperties, final SpPermissionChecker permissionChecker, final TargetTagManagement tagManagement, - final DistributionSetManagement distributionSetManagement, final Executor uiExecutor) { + final DistributionSetManagement distributionSetManagement, final Executor uiExecutor, + final HonoDeviceSync honoDeviceSync) { final TargetMetadataPopupLayout targetMetadataPopupLayout = new TargetMetadataPopupLayout(i18n, uiNotification, eventBus, targetManagement, entityFactory, permissionChecker); this.eventBus = eventBus; @@ -51,7 +53,7 @@ public TargetTableLayout(final UIEventBus eventBus, final TargetTable targetTabl targetTable); TargetTableHeader targetTableHeader = new TargetTableHeader(i18n, permissionChecker, eventBus, uiNotification, managementUIState, managementViewClientCriterion, targetManagement, deploymentManagement, uiProperties, - entityFactory, uiNotification, tagManagement, distributionSetManagement, uiExecutor, targetTable); + entityFactory, uiNotification, tagManagement, distributionSetManagement, uiExecutor, targetTable, honoDeviceSync); super.init(i18n, targetTableHeader, targetTable, targetDetails); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java index b96e67f485..3750d1d815 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java @@ -633,6 +633,11 @@ public final class UIComponentIdProvider { */ public static final String TARGET_TBL_ADD_ICON_ID = "target.add"; + /** + * Id of the target table sync Icon. + */ + public static final String TARGET_TBL_SYNC_ICON_ID = "target.sync"; + /** * Id of IP address label in target details. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java index 05e874c9f1..d13b1d3df5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java @@ -111,6 +111,8 @@ public final class UIMessageIdProvider { public static final String TOOLTIP_ADD = "tooltip.add"; + public static final String TOOLTIP_SYNC_HONO = "tooltip.sync.hono"; + public static final String TOOLTIP_SHOW_TAGS = "tooltip.showTags"; public static final String TOOLTIP_BULK_UPLOAD = "tooltip.bulkUpload"; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index c67d0959f4..6e8262a90f 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -303,6 +303,7 @@ tooltip.next.maintenance.window = next on {0} tooltip.target.attributes.update.request = Request attributes update tooltip.target.attributes.update.requested = Update already requested tooltip.documentation.link=Documentation +tooltip.sync.hono = Synchronize with Eclipse Hono #rollout action tooltip.rollout.run = Run From e441ff391acce5bfdd1604c005b0ac65cedb06e5 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 2 Sep 2019 18:37:24 +0200 Subject: [PATCH 06/28] Refactor class and package structure regarding hono credentials logic Signed-off-by: Brandon Schmitt --- .../SecurityManagedConfiguration.java | 38 ++++-- .../dmf/hono/DmfHonoConfiguration.java | 7 +- .../hawkbit/dmf/hono/HonoDeviceSync.java | 62 ++++++--- .../dmf/hono/model/HonoCredentials.java | 50 +++++++ .../dmf/hono/{ => model}/HonoDevice.java | 12 +- .../hono/{ => model}/HonoDeviceListPage.java | 2 +- .../dmf/hono/model/HonoPSKCredentials.java | 29 ++++ .../hono/model/HonoPasswordCredentials.java | 64 +++++++++ .../hawkbit/dmf/hono/model/HonoSecret.java | 57 ++++++++ .../dmf/hono/{ => model}/HonoTenant.java | 2 +- .../hono/{ => model}/HonoTenantListPage.java | 8 +- .../model/HonoX509CertificateCredentials.java | 19 +++ .../{ => model}/IdentifiableHonoDevice.java | 6 +- .../{ => model}/IdentifiableHonoTenant.java | 4 +- ...pControllerPreAuthenticatedHonoFilter.java | 9 +- .../security/PreAuthHonoProviderTest.java | 45 ++++++- hawkbit-security-integration/pom.xml | 10 +- .../ControllerPreAuthenticatedHonoFilter.java | 95 ++----------- .../hawkbit/security/HonoCredentials.java | 126 ------------------ .../PreAuthHonoAuthenticationProvider.java | 17 ++- 20 files changed, 381 insertions(+), 281 deletions(-) create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/HonoDevice.java (51%) rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/HonoDeviceListPage.java (91%) create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/HonoTenant.java (82%) rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/HonoTenantListPage.java (67%) create mode 100644 hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/IdentifiableHonoDevice.java (79%) rename hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/{ => model}/IdentifiableHonoTenant.java (79%) delete mode 100644 hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index f3a93eddb7..d412a8053c 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -24,6 +24,7 @@ import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.TenantUserPasswordAuthenticationToken; @@ -159,8 +160,8 @@ static class ControllerSecurityConfigurationAdapter extends WebSecurityConfigure private final HawkbitSecurityProperties securityProperties; private final SystemSecurityContext systemSecurityContext; - @Value("${hawkbit.server.repository.hono-sync.credentials-endpoint:}") - private String honoCredentialsEndpoint; + @Autowired(required = false) + private HonoDeviceSync honoDeviceSync; @Autowired ControllerSecurityConfigurationAdapter(final ControllerManagement controllerManagement, @@ -212,10 +213,10 @@ protected void configure(final HttpSecurity http) throws Exception { HttpControllerPreAuthenticatedHonoFilter honoFilter = null; - if (!honoCredentialsEndpoint.isEmpty()) { + if (honoDeviceSync != null) { honoFilter = new HttpControllerPreAuthenticatedHonoFilter( tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext, - honoCredentialsEndpoint); + honoDeviceSync); honoFilter.setAuthenticationManager(authenticationManager()); honoFilter.setCheckForPrincipalChanges(true); honoFilter.setAuthenticationDetailsSource(authenticationDetailsSource); @@ -269,8 +270,10 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + if (honoDeviceSync != null) { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider(honoDeviceSync, + ddiSecurityConfiguration.getRp().getTrustedIPs())); + } auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } @@ -295,8 +298,8 @@ static class ControllerDownloadSecurityConfigurationAdapter extends WebSecurityC private final HawkbitSecurityProperties securityProperties; private final SystemSecurityContext systemSecurityContext; - @Value("${hawkbit.server.repository.hono-sync.credentials-endpoint:}") - private String honoCredentialsEndpoint; + @Autowired(required = false) + private HonoDeviceSync honoDeviceSync; @Autowired ControllerDownloadSecurityConfigurationAdapter(final ControllerManagement controllerManagement, @@ -347,10 +350,10 @@ protected void configure(final HttpSecurity http) throws Exception { securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); HttpControllerPreAuthenticatedHonoFilter honoFilter = null; - if (!honoCredentialsEndpoint.isEmpty()) { + if (honoDeviceSync != null) { honoFilter = new HttpControllerPreAuthenticatedHonoFilter( tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext, - honoCredentialsEndpoint); + honoDeviceSync); honoFilter.setAuthenticationManager(authenticationManager()); honoFilter.setCheckForPrincipalChanges(true); honoFilter.setAuthenticationDetailsSource(authenticationDetailsSource); @@ -410,8 +413,10 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + if (honoDeviceSync != null) { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider(honoDeviceSync, + ddiSecurityConfiguration.getRp().getTrustedIPs())); + } auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } @@ -468,6 +473,9 @@ public static class IdRestSecurityConfigurationAdapter extends WebSecurityConfig @Autowired private DownloadIdCache downloadIdCache; + @Autowired(required = false) + private HonoDeviceSync honoDeviceSync; + @Override protected void configure(final HttpSecurity http) throws Exception { @@ -486,8 +494,10 @@ protected void configure(final HttpSecurity http) throws Exception { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(new PreAuthHonoAuthenticationProvider( - ddiSecurityConfiguration.getRp().getTrustedIPs())); + if (honoDeviceSync != null) { + auth.authenticationProvider(new PreAuthHonoAuthenticationProvider(honoDeviceSync, + ddiSecurityConfiguration.getRp().getTrustedIPs())); + } auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( ddiSecurityConfiguration.getRp().getTrustedIPs())); } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index 816a5ff390..ef9d631214 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -15,6 +15,9 @@ public class DmfHonoConfiguration { @Value("${hawkbit.server.repository.hono-sync.device-list-uri}") private String honoDeviceListUri; + @Value("${hawkbit.server.repository.hono-sync.credentials-list-uri}") + private String honoCredentialsListUri; + @Value("${hawkbit.server.repository.hono-sync.authorization-method:none}") private String authorizationMethod; @@ -32,7 +35,7 @@ public class DmfHonoConfiguration { @Bean public HonoDeviceSync honoDeviceSync() { - return new HonoDeviceSync(honoTenantListUri, honoDeviceListUri, authorizationMethod, oidcTokenUri, oidcClientId, - username, password); + return new HonoDeviceSync(honoTenantListUri, honoDeviceListUri, honoCredentialsListUri, authorizationMethod, + oidcTokenUri, oidcClientId, username, password); } } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index ecf36c729e..7b029fabf1 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -1,8 +1,11 @@ package org.eclipse.hawkbit.dmf.hono; +import com.esotericsoftware.minlog.Log; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.hawkbit.dmf.hono.model.*; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TargetManagement; @@ -44,9 +47,9 @@ public class HonoDeviceSync { @Autowired private TargetManagement targetManagement; - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); - private Map mutexes = new HashMap<>(); + private final Map mutexes = new HashMap<>(); private boolean syncedInitially = false; private String oidcAccessToken = null; @@ -54,16 +57,19 @@ public class HonoDeviceSync { private String honoTenantListUri; private String honoDeviceListUri; + private String honoCredentialsListUri; private String authorizationMethod; private String oidcTokenUri; private String oidcClientId; private String username; private String password; - HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String authorizationMethod, - String oidcTokenUri, String oidcClientId, String username, String password) { + HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String honoCredentialsListUri, + String authorizationMethod, String oidcTokenUri, String oidcClientId, String username, + String password) { this.honoTenantListUri = honoTenantListUri; this.honoDeviceListUri = honoDevicesEndpoint; + this.honoCredentialsListUri = honoCredentialsListUri; this.authorizationMethod = authorizationMethod; this.oidcTokenUri = oidcTokenUri; this.oidcClientId = oidcClientId; @@ -137,14 +143,24 @@ private void synchronizeTenant(String tenant) throws IOException, InterruptedExc semaphore.release(); } - private List getAllHonoTenants() throws IOException { + public void checkDeviceIfAbsentSync(String tenant, String deviceID) { + Optional target = systemSecurityContext.runAsSystemAsTenant( + () -> targetManagement.getByControllerID(deviceID), tenant); + if (!target.isPresent()) { + try { + synchronizeTenant(tenant); + } catch (IOException | InterruptedException e) { + Log.error("Could not synchronize with hono for tenant {}.", tenant, e); + } + } + } + + public List getAllHonoTenants() throws IOException { List tenants = new ArrayList<>(); long offset = 0; long total = Long.MAX_VALUE; while (tenants.size() < total) { - URL url = new URL(honoTenantListUri + "?offset=" + offset); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - addAuthorizationHeader(connection); + HttpURLConnection connection = getHonoData(honoTenantListUri + "?offset=" + offset); HonoTenantListPage page = objectMapper.readValue(connection.getInputStream(), HonoTenantListPage.class); tenants.addAll(page.getItems()); @@ -155,14 +171,12 @@ private List getAllHonoTenants() throws IOException { return tenants; } - private Map getAllHonoDevices(String tenant) throws IOException { + public Map getAllHonoDevices(String tenant) throws IOException { Map devices = new HashMap<>(); long offset = 0; long total = Long.MAX_VALUE; while (devices.size() < total) { - URL url = new URL(honoDeviceListUri.replace("$tenantId", tenant) + "?offset=" + offset); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - addAuthorizationHeader(connection); + HttpURLConnection connection = getHonoData(honoDeviceListUri.replace("$tenantId", tenant) + "?offset=" + offset); HonoDeviceListPage page = objectMapper.readValue(connection.getInputStream(), HonoDeviceListPage.class); if (page.getItems() != null) { @@ -178,7 +192,21 @@ private Map getAllHonoDevices(String tenant) thr return devices; } - private void addAuthorizationHeader(HttpURLConnection connection) throws IOException { + public Collection getAllHonoCredentials(String tenant, String deviceId) { + try { + HttpURLConnection connection = getHonoData(honoCredentialsListUri.replace("$tenantId", tenant) + .replace("$deviceId", deviceId)); + return objectMapper.readValue(connection.getInputStream(), new TypeReference>() {}); + } + catch (IOException e) { + return null; + } + } + + private HttpURLConnection getHonoData(String uri) throws IOException { + URL url = new URL(uri); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + switch (authorizationMethod) { case "basic": connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())); @@ -188,8 +216,8 @@ private void addAuthorizationHeader(HttpURLConnection connection) throws IOExcep if (oidcAccessToken == null || (oidcAccessTokenExpirationDate != null && oidcAccessTokenExpirationDate.isBefore(Instant.now()))) { - URL url = new URL(oidcTokenUri); - HttpURLConnection jwtConnection = (HttpURLConnection) url.openConnection(); + URL oidcTokenUrl = new URL(oidcTokenUri); + HttpURLConnection jwtConnection = (HttpURLConnection) oidcTokenUrl.openConnection(); jwtConnection.setDoOutput(true); DataOutputStream outputStream = new DataOutputStream(jwtConnection.getOutputStream()); outputStream.writeBytes("grant_type=password&client_id=" + oidcClientId + "&username=" + username + "&password=" + password); @@ -206,12 +234,14 @@ private void addAuthorizationHeader(HttpURLConnection connection) throws IOExcep } } else { - throw new IOException("Server returned HTTP response code: " + statusCode + " for URL: " + url.toString()); + throw new IOException("Server returned HTTP response code: " + statusCode + " for URL: " + oidcTokenUrl.toString()); } } connection.setRequestProperty("Authorization", "Bearer " + oidcAccessToken); break; } + + return connection; } @StreamListener(HonoInputSink.DEVICE_CREATED) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java new file mode 100644 index 0000000000..9092fb1dea --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java @@ -0,0 +1,50 @@ +package org.eclipse.hawkbit.dmf.hono.model; + +import java.util.Collection; + +public abstract class HonoCredentials { + private String authId; + private String type; + private boolean enabled = true; + Collection secrets; + + public String getType() { + return type; + } + + public String getAuthId() { + return authId; + } + + public boolean isEnabled() { + return enabled; + } + + public Collection getSecrets() { + return secrets; + } + + public void setType(String type) { + this.type = type; + } + + public void setAuthId(String authId) { + this.authId = authId; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean matches(final String providedSecret) { + if (enabled) { + for (HonoSecret secret : secrets) { + if (secret.isValid() && secret.matches(providedSecret)) { + return true; + } + } + } + + return false; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java similarity index 51% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java index 11243f5184..e99387de2a 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java @@ -1,24 +1,24 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; import com.fasterxml.jackson.databind.JsonNode; -class HonoDevice { +public class HonoDevice { private boolean enabled; private JsonNode ext; - boolean isEnabled() { + public boolean isEnabled() { return enabled; } - Object getExt() { + public Object getExt() { return ext; } - void setEnabled(boolean enabled) { + public void setEnabled(boolean enabled) { this.enabled = enabled; } - void setExt(JsonNode ext) { + public void setExt(JsonNode ext) { this.ext = ext; } } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java similarity index 91% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java index 48ed5e24f7..8aeb3b1c73 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceListPage.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java @@ -1,4 +1,4 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; import java.util.List; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java new file mode 100644 index 0000000000..9991f173f9 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java @@ -0,0 +1,29 @@ +package org.eclipse.hawkbit.dmf.hono.model; + +import com.fasterxml.jackson.annotation.JsonTypeName; + +import java.util.Collection; + +@JsonTypeName("psk") +public class HonoPSKCredentials extends HonoCredentials { + public void setSecrets(Collection secrets) { + this.secrets = secrets; + } + + public static class Secret extends HonoSecret { + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public boolean matches(final String key) { + return key != null && key.equals(this.key); + } + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java new file mode 100644 index 0000000000..c35f01b40e --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java @@ -0,0 +1,64 @@ +package org.eclipse.hawkbit.dmf.hono.model; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Collection; + +@JsonTypeName("hashed-password") +public class HonoPasswordCredentials extends HonoCredentials { + public void setSecrets(Collection secrets) { + this.secrets = secrets; + } + + public static class Secret extends HonoSecret { + private String hashFunction; + private String salt; + private String pwdHash; + + @Override + public boolean matches(final String password) { + PasswordEncoder encoder; + if (hashFunction.equals("bcrypt")) { + encoder = new BCryptPasswordEncoder(); + } + else if(hashFunction.equals("sha-256")) { + encoder = new MessageDigestPasswordEncoder("SHA-256"); + } + else if(hashFunction.equals("sha-512")) { + encoder = new MessageDigestPasswordEncoder("SHA-512"); + } + else { + return false; + } + + return encoder.matches(password + (salt != null ? salt : ""), pwdHash); + } + + public String getHashFunction() { + return hashFunction; + } + + public String getSalt() { + return salt; + } + + public String getPwdHash() { + return pwdHash; + } + + public void setHashFunction(String hashFunction) { + this.hashFunction = hashFunction; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public void setPwdHash(String pwdHash) { + this.pwdHash = pwdHash; + } + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java new file mode 100644 index 0000000000..e2cd118708 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java @@ -0,0 +1,57 @@ +package org.eclipse.hawkbit.dmf.hono.model; + +import java.time.LocalDateTime; + +public abstract class HonoSecret { + private String id; + private boolean enabled = true; + private LocalDateTime notBefore; + private LocalDateTime notAfter; + + public String getId() { + return id; + } + + public boolean isEnabled() { + return enabled; + } + + public LocalDateTime getNotBefore() { + return notBefore; + } + + public LocalDateTime getNotAfter() { + return notAfter; + } + + public void setId(String id) { + this.id = id; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setNotBefore(LocalDateTime notBefore) { + this.notBefore = notBefore; + } + + public void setNotBefore(String notBefore) { + this.notBefore = LocalDateTime.parse(notBefore); + } + + public void setNotAfter(LocalDateTime notAfter) { + this.notAfter = notAfter; + } + + public void setNotAfter(String notAfter) { + this.notAfter = LocalDateTime.parse(notAfter); + } + + public boolean isValid() { + LocalDateTime now = LocalDateTime.now(); + return (notBefore == null || now.compareTo(notBefore) >= 0) && (notAfter == null || now.compareTo(notAfter) <= 0); + } + + public abstract boolean matches(final String password); +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java similarity index 82% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java index 60f893d88a..664c43fe31 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenant.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java @@ -1,4 +1,4 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; public class HonoTenant { private boolean enabled; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java similarity index 67% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java index e048f7ecb8..135fc93378 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoTenantListPage.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java @@ -1,16 +1,16 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; import java.util.List; -class HonoTenantListPage { +public class HonoTenantListPage { private long total; private List items; - long getTotal() { + public long getTotal() { return total; } - List getItems() { + public List getItems() { return items; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java new file mode 100644 index 0000000000..2336dbc72e --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java @@ -0,0 +1,19 @@ +package org.eclipse.hawkbit.dmf.hono.model; + +import com.fasterxml.jackson.annotation.JsonTypeName; + +import java.util.Collection; + +@JsonTypeName("x509-cert") +public class HonoX509CertificateCredentials extends HonoCredentials { + public void setSecrets(Collection secrets) { + this.secrets = secrets; + } + + public static class Secret extends HonoSecret { + @Override + public boolean matches(String password) { + return false; + } + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java similarity index 79% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java index 6d9d43c28e..ef41b9076b 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java @@ -1,4 +1,4 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; public class IdentifiableHonoDevice { private String id; @@ -9,7 +9,7 @@ public String getId() { return id; } - String getTenant() { + public String getTenant() { return tenant; } @@ -21,7 +21,7 @@ public void setId(String id) { this.id = id; } - void setTenant(String tenant) { + public void setTenant(String tenant) { this.tenant = tenant; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java similarity index 79% rename from hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java rename to hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java index 225fa19de0..f98e55885f 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/IdentifiableHonoTenant.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java @@ -1,6 +1,6 @@ -package org.eclipse.hawkbit.dmf.hono; +package org.eclipse.hawkbit.dmf.hono.model; -class IdentifiableHonoTenant { +public class IdentifiableHonoTenant { private String id; private HonoTenant tenant; diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java index 77289a8dd0..e4dff7ea87 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedHonoFilter.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.security; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -28,7 +29,7 @@ public class HttpControllerPreAuthenticatedHonoFilter extends AbstractHttpControllerAuthenticationFilter { private final ControllerManagement controllerManagement; - private final String honoCredentialsEndpoint; + private final HonoDeviceSync honoDeviceSync; /** * Constructor. @@ -48,16 +49,16 @@ public class HttpControllerPreAuthenticatedHonoFilter extends AbstractHttpContro public HttpControllerPreAuthenticatedHonoFilter( final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, final ControllerManagement controllerManagement, final SystemSecurityContext systemSecurityContext, - final String honoCredentialsEndpoint) { + final HonoDeviceSync honoDeviceSync) { super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; - this.honoCredentialsEndpoint = honoCredentialsEndpoint; + this.honoDeviceSync = honoDeviceSync; } @Override protected PreAuthenticationFilter createControllerAuthenticationFilter() { return new ControllerPreAuthenticatedHonoFilter(tenantConfigurationManagement, controllerManagement, - tenantAware, systemSecurityContext, honoCredentialsEndpoint); + tenantAware, systemSecurityContext, honoDeviceSync); } } diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java index 0cc097e361..eadf33b61e 100644 --- a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java +++ b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java @@ -11,6 +11,9 @@ import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; +import org.eclipse.hawkbit.dmf.hono.model.HonoPasswordCredentials; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -23,25 +26,41 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; @Feature("Unit Tests - Security") @Story("PreAuth Hono Provider Test") @RunWith(MockitoJUnitRunner.class) public class PreAuthHonoProviderTest { - private final PreAuthHonoAuthenticationProvider testProvider = new PreAuthHonoAuthenticationProvider(); + private PreAuthHonoAuthenticationProvider testProvider; @Mock private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock; + @Mock + private HonoDeviceSync honoDeviceSyncMock; + + @Before + public void beforeTest() { + this.testProvider = new PreAuthHonoAuthenticationProvider(honoDeviceSyncMock); + } + @Test @Description("Testing that the provided credentials are incorrect.") public void invalidCredentialsThrowsAuthenticationException() { HeaderAuthentication principal = new HeaderAuthentication("deviceId", "wrongPassword"); - HonoPasswordSecret secret = new HonoPasswordSecret(null, null, "sha-256", "salt", "hash"); - HonoCredentials credentials = new HonoCredentials("deviceId", "hashed-password", "authId", true, Collections.singletonList(secret)); + HonoPasswordCredentials.Secret secret = new HonoPasswordCredentials.Secret(); + secret.setHashFunction("sha-256"); + secret.setSalt("salt"); + secret.setPwdHash("hash"); + + HonoPasswordCredentials credentials = new HonoPasswordCredentials(); + credentials.setAuthId("authId"); + credentials.setType("hashed-password"); + credentials.setSecrets(Collections.singletonList(secret)); final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, Collections.singletonList(credentials)); @@ -52,6 +71,7 @@ public void invalidCredentialsThrowsAuthenticationException() { testProvider.authenticate(token); fail("Should not work with wrong credentials"); } catch (final BadCredentialsException e) { + verifyNoMoreInteractions(honoDeviceSyncMock); } } @@ -59,10 +79,20 @@ public void invalidCredentialsThrowsAuthenticationException() { @Description("Testing that the provided credentials are correct.") public void credentialsAreCorrect() { - HeaderAuthentication principal = new HeaderAuthentication("deviceId", "password"); + String tenant = "tenant"; + String deviceID = "deviceId"; + + HeaderAuthentication principal = new HeaderAuthentication(deviceID, "password"); - HonoPasswordSecret secret = new HonoPasswordSecret(null, null, "sha-256", "salt", "7a37b85c8918eac19a9089c0fa5a2ab4dce3f90528dcdeec108b23ddf3607b99"); - HonoCredentials credentials = new HonoCredentials("deviceId", "hashed-password", "authId", true, Collections.singletonList(secret)); + HonoPasswordCredentials.Secret secret = new HonoPasswordCredentials.Secret(); + secret.setHashFunction("sha-256"); + secret.setSalt("salt"); + secret.setPwdHash("7a37b85c8918eac19a9089c0fa5a2ab4dce3f90528dcdeec108b23ddf3607b99"); + + HonoPasswordCredentials credentials = new HonoPasswordCredentials(); + credentials.setAuthId("authId"); + credentials.setType("hashed-password"); + credentials.setSecrets(Collections.singletonList(secret)); final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, Collections.singletonList(credentials)); @@ -71,5 +101,8 @@ public void credentialsAreCorrect() { // test, should throw authentication exception final Authentication authenticate = testProvider.authenticate(token); assertThat(authenticate.isAuthenticated()).isTrue(); + + verify(honoDeviceSyncMock, times(1)).checkDeviceIfAbsentSync(tenant, deviceID); + verifyNoMoreInteractions(honoDeviceSyncMock); } } diff --git a/hawkbit-security-integration/pom.xml b/hawkbit-security-integration/pom.xml index 3139789616..3ed309a87d 100644 --- a/hawkbit-security-integration/pom.xml +++ b/hawkbit-security-integration/pom.xml @@ -26,6 +26,11 @@ hawkbit-repository-api ${project.version} + + org.eclipse.hawkbit + hawkbit-dmf-hono + ${project.version} + org.springframework.security spring-security-web @@ -35,11 +40,6 @@ javax.servlet-api provided - - net.minidev - json-smart - 2.3 - diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java index f65b20b5c7..af21325e45 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedHonoFilter.java @@ -8,17 +8,11 @@ */ package org.eclipse.hawkbit.security; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; import java.util.Collection; import java.util.Optional; -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; +import org.eclipse.hawkbit.dmf.hono.model.HonoCredentials; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.model.Target; @@ -41,7 +35,7 @@ public class ControllerPreAuthenticatedHonoFilter extends AbstractControllerAuth private static final int OFFSET_TARGET_TOKEN = TARGET_SECURITY_TOKEN_AUTH_SCHEME.length(); private final ControllerManagement controllerManagement; - private final String honoCredentialsEndpoint; + private final HonoDeviceSync honoDeviceSync; /** * Constructor. @@ -58,14 +52,16 @@ public class ControllerPreAuthenticatedHonoFilter extends AbstractControllerAuth * @param systemSecurityContext * the system security context to get access to tenant * configuration + * @param honoDeviceSync + * the hono device sync interface */ public ControllerPreAuthenticatedHonoFilter( final TenantConfigurationManagement tenantConfigurationManagement, final ControllerManagement controllerManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext, final String honoCredentialsEndpoint) { + final SystemSecurityContext systemSecurityContext, final HonoDeviceSync honoDeviceSync) { super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; - this.honoCredentialsEndpoint = honoCredentialsEndpoint; + this.honoDeviceSync = honoDeviceSync; } @Override @@ -85,82 +81,7 @@ public HeaderAuthentication getPreAuthenticatedPrincipal(final DmfTenantSecurity @Override public Collection getPreAuthenticatedCredentials(final DmfTenantSecurityToken securityToken) { - Object response; - try { - URL url = new URL(honoCredentialsEndpoint - .replace("$tenantId", securityToken.getTenant()) - .replace("$deviceId", resolveControllerId(securityToken))); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - - int statusCode = connection.getResponseCode(); - if (statusCode < 200 || statusCode >= 300) { - return null; - } - JSONParser parser = new JSONParser(); - response = parser.parse(connection.getInputStream()); - } - catch (IOException | ParseException e) { - return null; - } - - ArrayList honoCredentials = new ArrayList<>(); - - if (response instanceof JSONArray) { - JSONArray credentialsArray = (JSONArray) response; - for (Object object : credentialsArray) { - JSONObject credentials = (JSONObject) object; - - String deviceId = credentials.getAsString("device-id"); - String type = credentials.getAsString("type"); - String authId = credentials.getAsString("auth-id"); - Boolean enabled = (Boolean) credentials.getOrDefault("enabled", true); - JSONArray secrets = (JSONArray) credentials.get("secrets"); - - ArrayList honoSecrets = new ArrayList<>(); - switch (type) { - case "hashed-password": - for (Object secretObject : secrets) { - JSONObject secret = (JSONObject) secretObject; - String notAfter = secret.getAsString("not-after"); - String notBefore = secret.getAsString("not-before"); - String pwdHash = secret.getAsString("pwd-hash"); - String salt = secret.getAsString("salt"); - String hashFunction = (String) secret.getOrDefault("hash-function", "sha-256"); - - honoSecrets.add(new HonoPasswordSecret(notBefore, notAfter, hashFunction, salt, pwdHash)); - } - break; - - case "psk": - for (Object secretObject : secrets) { - JSONObject secret = (JSONObject) secretObject; - String notAfter = secret.getAsString("not-after"); - String notBefore = secret.getAsString("not-before"); - String key = secret.getAsString("key"); - - honoSecrets.add(new HonoPreSharedKey(notBefore, notAfter, key)); - } - break; - - case "x509-cert": - for (Object secretObject : secrets) { - JSONObject secret = (JSONObject) secretObject; - String notAfter = secret.getAsString("not-after"); - String notBefore = secret.getAsString("not-before"); - - honoSecrets.add(new HonoX509Certificate(notBefore, notAfter)); - } - - default: - // skip this credentials entry as it is not supported - continue; - } - honoCredentials.add(new HonoCredentials(deviceId, type, authId, enabled, honoSecrets)); - } - } - - return honoCredentials; + return honoDeviceSync.getAllHonoCredentials(securityToken.getTenant(), resolveControllerId(securityToken)); } private String resolveControllerId(final DmfTenantSecurityToken securityToken) { diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java deleted file mode 100644 index 3a92c7e820..0000000000 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/HonoCredentials.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2019 Kiwigrid 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.security; - -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.time.LocalDateTime; -import java.util.Collection; - -public class HonoCredentials { - private String deviceId; - private String type; - private String authId; - private boolean enabled; - private Collection secrets; - - HonoCredentials(String deviceId, String type, String authId, boolean enabled, Collection secrets) { - this.deviceId = deviceId; - this.type = type; - this.authId = authId; - this.enabled = enabled; - this.secrets = secrets; - } - - public boolean matches(final String providedSecret) { - if (enabled) { - for (HonoSecret secret : secrets) { - if (secret.isValid() && secret.matches(providedSecret)) { - return true; - } - } - } - - return false; - } -} - -abstract class HonoSecret { - private LocalDateTime notBefore; - private LocalDateTime notAfter; - - HonoSecret(String notBefore, String notAfter) { - if (notBefore != null) { - this.notBefore = LocalDateTime.parse(notBefore); - } - if (notAfter != null) { - this.notAfter = LocalDateTime.parse(notAfter); - } - } - - public boolean isValid() { - LocalDateTime now = LocalDateTime.now(); - return (notBefore == null || now.compareTo(notBefore) >= 0) && (notAfter == null || now.compareTo(notAfter) <= 0); - } - - public abstract boolean matches(final String password); -} - -class HonoPasswordSecret extends HonoSecret { - private String hashFunction; - private String salt; - private String pwdHash; - - HonoPasswordSecret(String notBefore, String notAfter, String hashFunction, String salt, String pwdHash) { - super(notBefore, notAfter); - - this.hashFunction = hashFunction; - this.salt = salt; - this.pwdHash = pwdHash; - } - - @Override - public boolean matches(final String password) { - PasswordEncoder encoder; - if (hashFunction.equals("bcrypt")) { - encoder = new BCryptPasswordEncoder(); - } - else if(hashFunction.equals("sha-256")) { - encoder = new MessageDigestPasswordEncoder("SHA-256"); - } - else if(hashFunction.equals("sha-512")) { - encoder = new MessageDigestPasswordEncoder("SHA-512"); - } - else { - return false; - } - - return encoder.matches(password + (salt != null ? salt : ""), pwdHash); - } -} - -class HonoPreSharedKey extends HonoSecret { - private String key; - - HonoPreSharedKey(String notBefore, String notAfter, String key) { - super(notBefore, notAfter); - - this.key = key; - } - - @Override - public boolean matches(final String key) { - return this.key.equals(key); - } -} - -class HonoX509Certificate extends HonoSecret { - - HonoX509Certificate(String notBefore, String notAfter) { - super(notBefore, notAfter); - } - - @Override - public boolean matches(String password) { - // TODO: implement! - return false; - } -} diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java index 8c962809bf..7054c917f5 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.security; +import org.eclipse.hawkbit.dmf.hono.HonoDeviceSync; +import org.eclipse.hawkbit.dmf.hono.model.HonoCredentials; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -19,16 +21,21 @@ public class PreAuthHonoAuthenticationProvider extends PreAuthTokenSourceTrustAuthenticationProvider { - public PreAuthHonoAuthenticationProvider() { + private HonoDeviceSync honoDeviceSync; + + public PreAuthHonoAuthenticationProvider(HonoDeviceSync honoDeviceSync) { super(); + this.honoDeviceSync = honoDeviceSync; } - public PreAuthHonoAuthenticationProvider(final List authorizedSourceIps) { + public PreAuthHonoAuthenticationProvider(HonoDeviceSync honoDeviceSync, final List authorizedSourceIps) { super(authorizedSourceIps); + this.honoDeviceSync = honoDeviceSync; } - public PreAuthHonoAuthenticationProvider(final String... authorizedSourceIps) { + public PreAuthHonoAuthenticationProvider(HonoDeviceSync honoDeviceSync, final String... authorizedSourceIps) { super(authorizedSourceIps); + this.honoDeviceSync = honoDeviceSync; } @Override @@ -51,13 +58,15 @@ public Authentication authenticate(Authentication authentication) throws Authent for (Object object : (Collection) credentials) { if (object instanceof HonoCredentials) { if (((HonoCredentials) object).matches(((HeaderAuthentication) principal).getHeaderAuth())) { - successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails);; + successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); break; } } } if (successAuthentication) { + honoDeviceSync.checkDeviceIfAbsentSync("", ""); + final PreAuthenticatedAuthenticationToken successToken = new PreAuthenticatedAuthenticationToken(principal, credentials, authorities); successToken.setDetails(tokenDetails); From 8ead86d65d4773373abb3e6be7b5fb7e03efc8b2 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 11:27:51 +0200 Subject: [PATCH 07/28] Sync device if not present when it could successfully authenticate. Signed-off-by: Brandon Schmitt --- .../org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 9 +++++---- .../hawkbit/security/PreAuthHonoProviderTest.java | 4 +++- .../security/PreAuthHonoAuthenticationProvider.java | 5 ++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 7b029fabf1..a2d39265f4 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -1,6 +1,5 @@ package org.eclipse.hawkbit.dmf.hono; -import com.esotericsoftware.minlog.Log; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; @@ -26,6 +25,7 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.time.Instant; import java.util.*; import java.util.concurrent.Semaphore; @@ -108,7 +108,7 @@ public void synchronize(boolean syncOnlyCurrentTenant) { } catch (IOException e) { LOG.error("Could not parse hono api response.", e); } catch (InterruptedException e) { - LOG.error("Synchronizing hawkbit with Hono has been interrupted.", e); + LOG.warn("Synchronizing hawkbit with Hono has been interrupted.", e); } } @@ -150,7 +150,7 @@ public void checkDeviceIfAbsentSync(String tenant, String deviceID) { try { synchronizeTenant(tenant); } catch (IOException | InterruptedException e) { - Log.error("Could not synchronize with hono for tenant {}.", tenant, e); + LOG.error("Could not synchronize with hono for tenant {}.", tenant, e); } } } @@ -220,7 +220,8 @@ private HttpURLConnection getHonoData(String uri) throws IOException { HttpURLConnection jwtConnection = (HttpURLConnection) oidcTokenUrl.openConnection(); jwtConnection.setDoOutput(true); DataOutputStream outputStream = new DataOutputStream(jwtConnection.getOutputStream()); - outputStream.writeBytes("grant_type=password&client_id=" + oidcClientId + "&username=" + username + "&password=" + password); + outputStream.writeBytes("grant_type=password&client_id=" + URLEncoder.encode(oidcClientId) + + "&username=" + URLEncoder.encode(username) + "&password=" + URLEncoder.encode(password)); outputStream.flush(); outputStream.close(); diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java index eadf33b61e..6ca7c0e359 100644 --- a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java +++ b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java @@ -96,7 +96,9 @@ public void credentialsAreCorrect() { final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); + + final TenantAwareWebAuthenticationDetails details = new TenantAwareWebAuthenticationDetails(tenant, "remoteAddress", true); + token.setDetails(details); // test, should throw authentication exception final Authentication authenticate = testProvider.authenticate(token); diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java index 7054c917f5..2fb55cd7f9 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthHonoAuthenticationProvider.java @@ -65,7 +65,10 @@ public Authentication authenticate(Authentication authentication) throws Authent } if (successAuthentication) { - honoDeviceSync.checkDeviceIfAbsentSync("", ""); + if (tokenDetails instanceof TenantAwareWebAuthenticationDetails) { + TenantAwareWebAuthenticationDetails tenantAwareTokenDetails = (TenantAwareWebAuthenticationDetails) tokenDetails; + honoDeviceSync.checkDeviceIfAbsentSync(tenantAwareTokenDetails.getTenant(), ((HeaderAuthentication) principal).getControllerId()); + } final PreAuthenticatedAuthenticationToken successToken = new PreAuthenticatedAuthenticationToken(principal, credentials, authorities); From d89df5a1c3b09b2f2b24fcc7ff44b6a4512b63b0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 13:05:24 +0200 Subject: [PATCH 08/28] Refactor Hono Configuration Signed-off-by: Brandon Schmitt --- .../dmf/hono/DmfHonoConfiguration.java | 91 ++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index ef9d631214..de4d2679ad 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -1,41 +1,90 @@ package org.eclipse.hawkbit.dmf.hono; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration +@ConfigurationProperties(prefix = "hawkbit.dmf.hono") @ComponentScan public class DmfHonoConfiguration { + private String tenantListUri; + private String deviceListUri; + private String credentialsListUri; + private String authorizationMethod = "none"; + private String oidcTokenUri = ""; + private String oidcClientId = ""; + private String username = ""; + private String password = ""; - @Value("${hawkbit.server.repository.hono-sync.tenant-list-uri}") - private String honoTenantListUri; + @Bean + public HonoDeviceSync honoDeviceSync() { + return new HonoDeviceSync(tenantListUri, deviceListUri, credentialsListUri, authorizationMethod, + oidcTokenUri, oidcClientId, username, password); + } - @Value("${hawkbit.server.repository.hono-sync.device-list-uri}") - private String honoDeviceListUri; + public String getTenantListUri() { + return tenantListUri; + } - @Value("${hawkbit.server.repository.hono-sync.credentials-list-uri}") - private String honoCredentialsListUri; + public void setTenantListUri(String tenantListUri) { + this.tenantListUri = tenantListUri; + } - @Value("${hawkbit.server.repository.hono-sync.authorization-method:none}") - private String authorizationMethod; + public String getDeviceListUri() { + return deviceListUri; + } - @Value("${hawkbit.server.repository.hono-sync.oidc-token-uri:}") - private String oidcTokenUri; + public void setDeviceListUri(String deviceListUri) { + this.deviceListUri = deviceListUri; + } - @Value("${hawkbit.server.repository.hono-sync.oidc-client-id:}") - private String oidcClientId; + public String getCredentialsListUri() { + return credentialsListUri; + } - @Value("${hawkbit.server.repository.hono-sync.user.name:}") - private String username; + public void setCredentialsListUri(String credentialsListUri) { + this.credentialsListUri = credentialsListUri; + } - @Value("${hawkbit.server.repository.hono-sync.user.password:}") - private String password; + public String getAuthorizationMethod() { + return authorizationMethod; + } - @Bean - public HonoDeviceSync honoDeviceSync() { - return new HonoDeviceSync(honoTenantListUri, honoDeviceListUri, honoCredentialsListUri, authorizationMethod, - oidcTokenUri, oidcClientId, username, password); + public void setAuthorizationMethod(String authorizationMethod) { + this.authorizationMethod = authorizationMethod; + } + + public String getOidcTokenUri() { + return oidcTokenUri; + } + + public void setOidcTokenUri(String oidcTokenUri) { + this.oidcTokenUri = oidcTokenUri; + } + + public String getOidcClientId() { + return oidcClientId; + } + + public void setOidcClientId(String oidcClientId) { + this.oidcClientId = oidcClientId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; } } From 5c55c2a7d5e30c4587430e296dd9d17a1334edc0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 14:49:37 +0200 Subject: [PATCH 09/28] Make Hono optional Signed-off-by: Brandon Schmitt --- .../dmf/hono/DmfHonoConfiguration.java | 14 ++++++----- .../hawkbit/dmf/hono/HonoDeviceSync.java | 8 +++---- .../AbstractDistributionSetTableHeader.java | 2 +- .../AbstractSoftwareModuleTableHeader.java | 2 +- .../ui/common/table/AbstractTableHeader.java | 24 ++++++++----------- .../hawkbit/ui/management/DeploymentView.java | 5 ++-- .../targettable/TargetTableHeader.java | 2 +- 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index de4d2679ad..0f65214ca6 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -1,5 +1,6 @@ package org.eclipse.hawkbit.dmf.hono; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -7,12 +8,13 @@ @Configuration @ConfigurationProperties(prefix = "hawkbit.dmf.hono") +@ConditionalOnProperty(prefix = "hawkbit.dmf.hono", name = "enabled") @ComponentScan public class DmfHonoConfiguration { private String tenantListUri; private String deviceListUri; private String credentialsListUri; - private String authorizationMethod = "none"; + private String authenticationMethod = "none"; private String oidcTokenUri = ""; private String oidcClientId = ""; private String username = ""; @@ -20,7 +22,7 @@ public class DmfHonoConfiguration { @Bean public HonoDeviceSync honoDeviceSync() { - return new HonoDeviceSync(tenantListUri, deviceListUri, credentialsListUri, authorizationMethod, + return new HonoDeviceSync(tenantListUri, deviceListUri, credentialsListUri, authenticationMethod, oidcTokenUri, oidcClientId, username, password); } @@ -48,12 +50,12 @@ public void setCredentialsListUri(String credentialsListUri) { this.credentialsListUri = credentialsListUri; } - public String getAuthorizationMethod() { - return authorizationMethod; + public String getAuthenticationMethod() { + return authenticationMethod; } - public void setAuthorizationMethod(String authorizationMethod) { - this.authorizationMethod = authorizationMethod; + public void setAuthenticationMethod(String authenticationMethod) { + this.authenticationMethod = authenticationMethod; } public String getOidcTokenUri() { diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index a2d39265f4..9f5b3841f3 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -58,19 +58,19 @@ public class HonoDeviceSync { private String honoTenantListUri; private String honoDeviceListUri; private String honoCredentialsListUri; - private String authorizationMethod; + private String authenticationMethod; private String oidcTokenUri; private String oidcClientId; private String username; private String password; HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String honoCredentialsListUri, - String authorizationMethod, String oidcTokenUri, String oidcClientId, String username, + String authenticationMethod, String oidcTokenUri, String oidcClientId, String username, String password) { this.honoTenantListUri = honoTenantListUri; this.honoDeviceListUri = honoDevicesEndpoint; this.honoCredentialsListUri = honoCredentialsListUri; - this.authorizationMethod = authorizationMethod; + this.authenticationMethod = authenticationMethod; this.oidcTokenUri = oidcTokenUri; this.oidcClientId = oidcClientId; this.username = username; @@ -207,7 +207,7 @@ private HttpURLConnection getHonoData(String uri) throws IOException { URL url = new URL(uri); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - switch (authorizationMethod) { + switch (authenticationMethod) { case "basic": connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())); break; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java index 7f493a5ed3..5c81ccfbdd 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractDistributionSetTableHeader.java @@ -31,7 +31,7 @@ public abstract class AbstractDistributionSetTableHeader extends AbstractTableHe protected AbstractDistributionSetTableHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, final UIEventBus eventbus, final ManagementUIState managementUIState, final ManageDistUIState manageDistUIstate, final ArtifactUploadState artifactUploadState) { - super(i18n, permChecker, eventbus, managementUIState, manageDistUIstate, artifactUploadState); + super(i18n, permChecker, eventbus, managementUIState, manageDistUIstate, artifactUploadState, false); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java index 535f2feff7..433e3c9817 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractSoftwareModuleTableHeader.java @@ -37,7 +37,7 @@ protected AbstractSoftwareModuleTableHeader(final VaadinMessageSource i18n, fina final UIEventBus eventbus, final ManagementUIState managementUIState, final ManageDistUIState manageDistUIstate, final ArtifactUploadState artifactUploadState, final SoftwareModuleAddUpdateWindow softwareModuleAddUpdateWindow) { - super(i18n, permChecker, eventbus, managementUIState, manageDistUIstate, artifactUploadState); + super(i18n, permChecker, eventbus, managementUIState, manageDistUIstate, artifactUploadState, false); this.softwareModuleAddUpdateWindow = softwareModuleAddUpdateWindow; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java index 851bb1bd19..4b9e947eb1 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java @@ -73,15 +73,19 @@ public abstract class AbstractTableHeader extends VerticalLayout { private final ArtifactUploadState artifactUploadState; + private final boolean honoSyncEnabled; + protected AbstractTableHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, final UIEventBus eventBus, final ManagementUIState managementUIState, - final ManageDistUIState manageDistUIstate, final ArtifactUploadState artifactUploadState) { + final ManageDistUIState manageDistUIstate, final ArtifactUploadState artifactUploadState, + final boolean honoSyncEnabled) { this.i18n = i18n; this.permChecker = permChecker; this.eventBus = eventBus; this.managementUIState = managementUIState; this.manageDistUIstate = manageDistUIstate; this.artifactUploadState = artifactUploadState; + this.honoSyncEnabled = honoSyncEnabled; createComponents(); buildLayout(); restoreState(); @@ -157,22 +161,14 @@ private void restoreState() { } private void hideAddAndUploadIcon() { - if (syncHono == null) { - addIcon.setVisible(false); - } - else { - syncHono.setVisible(false); - } + addIcon.setVisible(false); + syncHono.setVisible(false); bulkUploadIcon.setVisible(false); } private void showAddAndUploadIcon() { - if (syncHono == null) { - addIcon.setVisible(true); - } - else { - syncHono.setVisible(true); - } + addIcon.setVisible(true); + syncHono.setVisible(true); bulkUploadIcon.setVisible(true); } @@ -184,7 +180,7 @@ private void buildLayout() { titleFilterIconsLayout.setComponentAlignment(searchField, Alignment.TOP_RIGHT); titleFilterIconsLayout.setComponentAlignment(searchResetIcon, Alignment.TOP_RIGHT); titleFilterIconsLayout.setComponentAlignment(showFilterButtonLayout, Alignment.TOP_RIGHT); - if (syncHono != null && isHonoSyncAllowed()) { + if (honoSyncEnabled && isHonoSyncAllowed()) { titleFilterIconsLayout.addComponent(syncHono); titleFilterIconsLayout.setComponentAlignment(syncHono, Alignment.TOP_RIGHT); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java index b6fb91647c..ca3d0c8cf6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.ui.management; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Executor; import javax.annotation.PostConstruct; @@ -133,7 +134,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW final TenantConfigurationManagement configManagement, final SystemSecurityContext systemSecurityContext, final NotificationUnreadButton notificationUnreadButton, final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor, - final HonoDeviceSync honoDeviceSync) { + final Optional honoDeviceSync) { super(eventBus, notificationUnreadButton); this.permChecker = permChecker; this.i18n = i18n; @@ -158,7 +159,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW this.targetTableLayout = new TargetTableLayout(eventBus, targetTable, targetManagement, entityFactory, i18n, uiNotification, managementUIState, managementViewClientCriterion, deploymentManagement, - uiProperties, permChecker, targetTagManagement, distributionSetManagement, uiExecutor, honoDeviceSync); + uiProperties, permChecker, targetTagManagement, distributionSetManagement, uiExecutor, honoDeviceSync.orElse(null)); actionHistoryLayout.registerDetails(((ActionStatusGrid) actionStatusLayout.getGrid()).getDetailsSupport()); actionStatusLayout diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java index f0d6e097f1..591921bc17 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableHeader.java @@ -88,7 +88,7 @@ public class TargetTableHeader extends AbstractTableHeader { final EntityFactory entityFactory, final UINotification uiNotification, final TargetTagManagement tagManagement, final DistributionSetManagement distributionSetManagement, final Executor uiExecutor, final TargetTable targetTable, final HonoDeviceSync honoDeviceSync) { - super(i18n, permChecker, eventBus, managementUIState, null, null); + super(i18n, permChecker, eventBus, managementUIState, null, null, honoDeviceSync != null); this.notification = notification; this.managementViewClientCriterion = managementViewClientCriterion; this.targetAddUpdateWindow = new TargetAddUpdateWindowLayout(i18n, targetManagement, eventBus, uiNotification, From f1885987f0919c5487f6501162bc3a5bc5c1285e Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 15:11:11 +0200 Subject: [PATCH 10/28] Hono sync documentation Signed-off-by: Brandon Schmitt --- docs/content/guides/runhawkbit.md | 41 +++++++++++++++++++ .../src/main/resources/application.properties | 8 ++++ 2 files changed, 49 insertions(+) diff --git a/docs/content/guides/runhawkbit.md b/docs/content/guides/runhawkbit.md index 918ed60d45..f9278b3e9d 100644 --- a/docs/content/guides/runhawkbit.md +++ b/docs/content/guides/runhawkbit.md @@ -66,6 +66,47 @@ spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 ``` +### Set [Eclipse Hono](https://www.eclipse.org/hono/) as hawkBit's device registry + +``` +hawkbit.dmf.hono.enabled=true +hawkbit.dmf.hono.tenant-list-uri=http://HONO_HOST/v1/tenants +hawkbit.dmf.hono.device-list-uri=http://HONO_HOST/v1/devices/$tenantId +hawkbit.dmf.hono.credentials-list-uri=http://HONO_HOST/v1/credentials/$tenantId/$deviceId +``` +`$tenantId` and `$deviceId` are placeholders which will be replaced by hawkBit during the respective requests. + +hawkBit currently supports three different methods to authenticate with hono: +- None (`none`; default) +- BasicAuth (`basic`) +- OpenID Connect (`oidc`) + +If you intend to use any authentication method other than `none` you must provide these additional properties: + +``` +hawkbit.dmf.hono.authentication-method=oidc +hawkbit.dmf.hono.username=USERNAME +hawkbit.dmf.hono.password=PASSWORD + +// Only for authentication-method = oidc +hawkbit.dmf.hono.oidc-token-uri=http://OIDC_HOST/auth/realms/kiwigrid/protocol/openid-connect/token +hawkbit.dmf.hono.oidc-client-id=OIDC_CLIENT_ID +``` + +hawkBit handles device registry updates through CUD events emitted by Hono over any Spring Cloud Stream supported channel, such as AMQP or Google Cloud Pub/Sub. + +In order to have predictable channel names use the following properties: +``` +spring.cloud.stream.bindings.device-created.destination=device-registry.device-created +spring.cloud.stream.bindings.device-created.group=hawkBit +spring.cloud.stream.bindings.device-updated.destination=device-registry.device-updated +spring.cloud.stream.bindings.device-updated.group=hawkBit +spring.cloud.stream.bindings.device-deleted.destination=device-registry.device-deleted +spring.cloud.stream.bindings.device-deleted.group=hawkBit +``` +For Google Cloud Pub/Sub disable the default Maven profile `hono-amqp` and enable the profile `amqp-gcp-pubsub`. + + ### Adapt hostname of example scenario [creation script](https://github.com/eclipse/hawkbit-examples/blob/master/hawkbit-example-mgmt-simulator/src/main/resources/application.properties) Should only be necessary if your system does not run on localhost or uses a different port than the example app. diff --git a/hawkbit-runtime/hawkbit-update-server/src/main/resources/application.properties b/hawkbit-runtime/hawkbit-update-server/src/main/resources/application.properties index b920fe1365..fc43afb01a 100644 --- a/hawkbit-runtime/hawkbit-update-server/src/main/resources/application.properties +++ b/hawkbit-runtime/hawkbit-update-server/src/main/resources/application.properties @@ -12,6 +12,14 @@ spring.security.user.name=admin spring.security.user.password={noop}admin spring.main.allow-bean-definition-overriding=true +# Configuration of the Spring Cloud Stream endpoints for the optional Eclipse Hono synchronization +spring.cloud.stream.bindings.device-created.destination=device-registry.device-created +spring.cloud.stream.bindings.device-created.group=hawkBit +spring.cloud.stream.bindings.device-updated.destination=device-registry.device-updated +spring.cloud.stream.bindings.device-updated.group=hawkBit +spring.cloud.stream.bindings.device-deleted.destination=device-registry.device-deleted +spring.cloud.stream.bindings.device-deleted.group=hawkBit + # DDI authentication configuration hawkbit.server.ddi.security.authentication.anonymous.enabled=false hawkbit.server.ddi.security.authentication.targettoken.enabled=true From 0fee1bfe6cb96a9c06c5c2e08514572c25dc98d0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 15:28:28 +0200 Subject: [PATCH 11/28] Add missing Kiwigrid GmbH licences Signed-off-by: Brandon Schmitt --- .../dmf/hono/DmfHonoAutoConfiguration.java | 8 ++++++++ hawkbit-dmf/hawkbit-dmf-hono/pom.xml | 11 ++++++++++- .../hawkbit/dmf/hono/DmfHonoConfiguration.java | 8 ++++++++ .../org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 8 ++++++++ .../org/eclipse/hawkbit/dmf/hono/HonoInputSink.java | 8 ++++++++ .../hawkbit/dmf/hono/model/HonoCredentials.java | 8 ++++++++ .../eclipse/hawkbit/dmf/hono/model/HonoDevice.java | 8 ++++++++ .../hawkbit/dmf/hono/model/HonoDeviceListPage.java | 8 ++++++++ .../hawkbit/dmf/hono/model/HonoPSKCredentials.java | 8 ++++++++ .../dmf/hono/model/HonoPasswordCredentials.java | 8 ++++++++ .../eclipse/hawkbit/dmf/hono/model/HonoSecret.java | 8 ++++++++ .../eclipse/hawkbit/dmf/hono/model/HonoTenant.java | 8 ++++++++ .../hawkbit/dmf/hono/model/HonoTenantListPage.java | 8 ++++++++ .../hono/model/HonoX509CertificateCredentials.java | 8 ++++++++ .../dmf/hono/model/IdentifiableHonoDevice.java | 8 ++++++++ .../dmf/hono/model/IdentifiableHonoTenant.java | 8 ++++++++ licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt | 6 ++++++ pom.xml | 1 + 18 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java index e63ac38a47..0051cf2b55 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/dmf/hono/DmfHonoAutoConfiguration.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.autoconfigure.dmf.hono; import org.eclipse.hawkbit.dmf.hono.DmfHonoConfiguration; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/pom.xml b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml index d5dc5f81bd..947976677a 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-hono/pom.xml @@ -1,4 +1,13 @@ - + diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index 0f65214ca6..d9d1691422 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 9f5b3841f3..6a19616813 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java index a3722390c2..f2af5f4bba 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoInputSink.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono; import org.springframework.cloud.stream.annotation.Input; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java index 9092fb1dea..5ef4f554e0 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import java.util.Collection; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java index e99387de2a..52d64eca88 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import com.fasterxml.jackson.databind.JsonNode; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java index 8aeb3b1c73..ce811683ce 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDeviceListPage.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import java.util.List; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java index 9991f173f9..1d326ef401 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java index c35f01b40e..c0ff205287 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java index e2cd118708..d0cf4caa3f 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import java.time.LocalDateTime; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java index 664c43fe31..55c1cf382b 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenant.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; public class HonoTenant { diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java index 135fc93378..309a12a2eb 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoTenantListPage.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import java.util.List; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java index 2336dbc72e..44dbc1cab0 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java index ef41b9076b..c714e45895 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoDevice.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; public class IdentifiableHonoDevice { diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java index f98e55885f..6891c78044 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/IdentifiableHonoTenant.java @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2019 Kiwigrid 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.dmf.hono.model; public class IdentifiableHonoTenant { diff --git a/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt b/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt new file mode 100644 index 0000000000..4e8787d29c --- /dev/null +++ b/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt @@ -0,0 +1,6 @@ +Copyright (c) 2019 Kiwigrid 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 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7f7e5b66ac..2a778db8b8 100644 --- a/pom.xml +++ b/pom.xml @@ -325,6 +325,7 @@ licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_18.txt licenses/LICENSE_HEADER_TEMPLATE_BOSCH_18.txt licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_19.txt + licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt **/banner.txt From 458d5b50e60e5c1be802ad54c81b3a5509485bd0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 17:01:13 +0200 Subject: [PATCH 12/28] Disable CUD permissions and ui buttons concerning targets when Hono sync is enabled. Signed-off-by: Brandon Schmitt --- .../eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 8 +++++++- .../im/authentication/PermissionService.java | 13 +++++++++++-- .../hawkbit/ui/management/DeploymentView.java | 3 ++- .../ui/management/targettable/TargetDetails.java | 13 +++++++++---- .../ui/management/targettable/TargetTable.java | 8 ++++++-- .../management/targettable/TargetTableLayout.java | 2 +- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 6a19616813..c019686af9 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.hawkbit.dmf.hono.model.*; +import org.eclipse.hawkbit.im.authentication.PermissionService; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TargetManagement; @@ -55,6 +56,9 @@ public class HonoDeviceSync { @Autowired private TargetManagement targetManagement; + @Autowired + PermissionService permissionService; + private final ObjectMapper objectMapper = new ObjectMapper(); private final Map mutexes = new HashMap<>(); @@ -88,7 +92,9 @@ public class HonoDeviceSync { } @EventListener(ApplicationReadyEvent.class) - private void initialSync() { + private void init() { + permissionService.setHonoSyncEnabled(true); + // Since ApplicationReadyEvent is emitted multiple times make sure it is synced at most once during startup. if (!syncedInitially) { synchronize(false); diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java index 2cbadd02db..3bc13853ff 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java @@ -8,8 +8,7 @@ */ package org.eclipse.hawkbit.im.authentication; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -22,6 +21,9 @@ */ public class PermissionService { + private boolean honoSyncEnabled = false; + private final List disabledPermissionsByUsingHono = Arrays.asList(SpPermission.CREATE_TARGET, SpPermission.UPDATE_TARGET, SpPermission.DELETE_TARGET); + /** * Checks if the given {@code permission} contains in the. In case no * {@code context} is available {@code false} will be returned. @@ -42,6 +44,10 @@ public boolean hasPermission(final String permission) { return false; } + if (honoSyncEnabled && disabledPermissionsByUsingHono.contains(permission)) { + return false; + } + for (final GrantedAuthority authority : authentication.getAuthorities()) { if (authority.getAuthority().equals(permission)) { return true; @@ -100,4 +106,7 @@ public boolean hasAtLeastOnePermission(final List permissions) { return false; } + public void setHonoSyncEnabled(boolean honoSyncEnabled) { + this.honoSyncEnabled = honoSyncEnabled; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java index ca3d0c8cf6..b0c35a3a88 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java @@ -153,7 +153,8 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW targetFilterQueryManagement, targetTagManagement); final TargetTable targetTable = new TargetTable(eventBus, i18n, uiNotification, targetManagement, managementUIState, permChecker, managementViewClientCriterion, distributionSetManagement, - targetTagManagement, deploymentManagement, configManagement, systemSecurityContext, uiProperties); + targetTagManagement, deploymentManagement, configManagement, systemSecurityContext, uiProperties, + honoDeviceSync.isPresent()); this.countMessageLabel = new CountMessageLabel(eventBus, targetManagement, i18n, managementUIState, targetTable); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetDetails.java index 354aed7a17..275a665a60 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetDetails.java @@ -74,13 +74,16 @@ public class TargetDetails extends AbstractTableDetailsLayout { private VerticalLayout installedDistLayout; + private final boolean honoSyncEnabled; + TargetDetails(final VaadinMessageSource i18n, final UIEventBus eventBus, final SpPermissionChecker permissionChecker, final ManagementUIState managementUIState, final UINotification uiNotification, final TargetTagManagement tagManagement, final TargetManagement targetManagement, final TargetMetadataPopupLayout targetMetadataPopupLayout, final DeploymentManagement deploymentManagement, final EntityFactory entityFactory, - final TargetTable targetTable) { + final TargetTable targetTable, final boolean honoSyncEnabled) { super(i18n, eventBus, permissionChecker, managementUIState); + this.honoSyncEnabled = honoSyncEnabled; this.targetTagToken = new TargetTagToken(permissionChecker, i18n, uiNotification, eventBus, managementUIState, tagManagement, targetManagement); this.targetAddUpdateWindowLayout = new TargetAddUpdateWindowLayout(i18n, targetManagement, eventBus, @@ -193,9 +196,11 @@ private void updateDetailsLayout(final String controllerId, final URI address, f typeLabel.setId(UIComponentIdProvider.TARGET_IP_ADDRESS); detailsTabLayout.addComponent(typeLabel); - final HorizontalLayout securityTokenLayout = getSecurityTokenLayout(securityToken); - controllerLabel.setId(UIComponentIdProvider.TARGET_SECURITY_TOKEN); - detailsTabLayout.addComponent(securityTokenLayout); + if (!honoSyncEnabled) { + final HorizontalLayout securityTokenLayout = getSecurityTokenLayout(securityToken); + controllerLabel.setId(UIComponentIdProvider.TARGET_SECURITY_TOKEN); + detailsTabLayout.addComponent(securityTokenLayout); + } } private HorizontalLayout getSecurityTokenLayout(final String securityToken) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java index 71189f0a80..07d57b69a5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java @@ -133,12 +133,15 @@ public class TargetTable extends AbstractTable { private boolean targetPinned; private ConfirmationDialog confirmDialog; + private final boolean honoSyncEnabled; + public TargetTable(final UIEventBus eventBus, final VaadinMessageSource i18n, final UINotification notification, final TargetManagement targetManagement, final ManagementUIState managementUIState, final SpPermissionChecker permChecker, final ManagementViewClientCriterion managementViewClientCriterion, final DistributionSetManagement distributionSetManagement, final TargetTagManagement tagManagement, final DeploymentManagement deploymentManagement, final TenantConfigurationManagement configManagement, - final SystemSecurityContext systemSecurityContext, final UiProperties uiProperties) { + final SystemSecurityContext systemSecurityContext, final UiProperties uiProperties, + final boolean honoSyncEnabled) { super(eventBus, i18n, notification, permChecker); this.targetManagement = targetManagement; this.managementViewClientCriterion = managementViewClientCriterion; @@ -151,6 +154,7 @@ public TargetTable(final UIEventBus eventBus, final VaadinMessageSource i18n, fi this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n); this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n); this.systemSecurityContext = systemSecurityContext; + this.honoSyncEnabled = honoSyncEnabled; setItemDescriptionGenerator(new AssignInstalledDSTooltipGenerator()); addNewContainerDS(); @@ -959,7 +963,7 @@ protected String getEntityType() { @Override protected boolean hasDeletePermission() { - return getPermChecker().hasDeleteRepositoryPermission() || getPermChecker().hasDeleteTargetPermission(); + return !honoSyncEnabled && (getPermChecker().hasDeleteRepositoryPermission() || getPermChecker().hasDeleteTargetPermission()); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java index ca4d271d83..c759c8fcf0 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTableLayout.java @@ -50,7 +50,7 @@ public TargetTableLayout(final UIEventBus eventBus, final TargetTable targetTabl this.eventBus = eventBus; TargetDetails targetDetails = new TargetDetails(i18n, eventBus, permissionChecker, managementUIState, uiNotification, tagManagement, targetManagement, targetMetadataPopupLayout, deploymentManagement, entityFactory, - targetTable); + targetTable, honoDeviceSync != null); TargetTableHeader targetTableHeader = new TargetTableHeader(i18n, permissionChecker, eventBus, uiNotification, managementUIState, managementViewClientCriterion, targetManagement, deploymentManagement, uiProperties, entityFactory, uiNotification, tagManagement, distributionSetManagement, uiExecutor, targetTable, honoDeviceSync); From 01f09950b1dff0cc1f68bdc376c8ee1d253b37b0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 5 Sep 2019 19:02:20 +0200 Subject: [PATCH 13/28] Use better placeholder for OIDC realm in hawkbit.dmf.hono.oidc-token-uri Signed-off-by: Brandon Schmitt --- docs/content/guides/runhawkbit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guides/runhawkbit.md b/docs/content/guides/runhawkbit.md index f9278b3e9d..11333f327c 100644 --- a/docs/content/guides/runhawkbit.md +++ b/docs/content/guides/runhawkbit.md @@ -89,7 +89,7 @@ hawkbit.dmf.hono.username=USERNAME hawkbit.dmf.hono.password=PASSWORD // Only for authentication-method = oidc -hawkbit.dmf.hono.oidc-token-uri=http://OIDC_HOST/auth/realms/kiwigrid/protocol/openid-connect/token +hawkbit.dmf.hono.oidc-token-uri=http://OIDC_HOST/auth/realms/REALM/protocol/openid-connect/token hawkbit.dmf.hono.oidc-client-id=OIDC_CLIENT_ID ``` From 1ae10d3317c2d0c1d482ae092365b2967423b081 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 12 Sep 2019 13:18:49 +0200 Subject: [PATCH 14/28] Add new line at end of license file Signed-off-by: Brandon Schmitt --- licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt b/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt index 4e8787d29c..4957bc6ea0 100644 --- a/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt +++ b/licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt @@ -3,4 +3,4 @@ Copyright (c) 2019 Kiwigrid 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 \ No newline at end of file +http://www.eclipse.org/legal/epl-v10.html From a2d8f049d016958e1a5f5372341da4e074b2418c Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 16 Sep 2019 17:14:26 +0200 Subject: [PATCH 15/28] Fix NullPointerException if there are no tenants in Hono yet. Signed-off-by: Brandon Schmitt --- .../java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index c019686af9..3435acb85e 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -177,8 +177,10 @@ public List getAllHonoTenants() throws IOException { HttpURLConnection connection = getHonoData(honoTenantListUri + "?offset=" + offset); HonoTenantListPage page = objectMapper.readValue(connection.getInputStream(), HonoTenantListPage.class); - tenants.addAll(page.getItems()); - offset += page.getItems().size(); + if (page.getItems() != null) { + tenants.addAll(page.getItems()); + offset += page.getItems().size(); + } total = page.getTotal(); } From 95f60ac5221b3055a112fb7b312bc1129bd91b7a Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Fri, 11 Oct 2019 00:47:44 +0200 Subject: [PATCH 16/28] Split off target filter permissions from target permissions. As the CUD permissions for targets are supposed be disabled due to the hono synchronization, split off the target filter permissions to still allow managing filters. Signed-off-by: Brandon Schmitt --- .../TargetFilterQueryManagement.java | 4 +- .../im/authentication/SpPermission.java | 44 ++++++++++++++++++- .../hawkbit/ui/SpPermissionChecker.java | 33 ++++++++++++-- .../CreateOrUpdateFilterHeader.java | 4 +- .../filtermanagement/TargetFilterHeader.java | 2 +- .../filter/MultipleTargetFilter.java | 7 +-- 6 files changed, 82 insertions(+), 12 deletions(-) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index d23c2eea92..a2e2862fb6 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -52,7 +52,7 @@ public interface TargetFilterQueryManagement { * if the maximum number of targets that is addressed by the * given query is exceeded (auto-assignments only) */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET_FILTER) TargetFilterQuery create(@NotNull @Valid TargetFilterQueryCreate create); /** @@ -64,7 +64,7 @@ public interface TargetFilterQueryManagement { * @throws EntityNotFoundException * if filter with given ID does not exist */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET_FILTER) void delete(long targetFilterQueryId); /** diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index 10f248c483..f30d9cb3e0 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -68,7 +68,7 @@ public final class SpPermission { /** * Permission to add new targets to the {@link ProvisioningTargetRepository} * including their meta information and or/relations or - * {@link DistributionSet} assignment.That corresponds in REST API to PUT. + * {@link DistributionSet} assignment. That corresponds in REST API to PUT. */ public static final String CREATE_TARGET = "CREATE_TARGET"; @@ -80,6 +80,24 @@ public final class SpPermission { */ public static final String DELETE_TARGET = "DELETE_TARGET"; + /** + * Permission to add new target filters to the {@link ProvisioningTargetRepository} + * That corresponds in REST API to PUT. + */ + public static final String UPDATE_TARGET_FILTER = "UPDATE_TARGET_FILTER"; + + /** + * Permission to add new target filters to the {@link ProvisioningTargetRepository} + * That corresponds in REST API to POST. + */ + public static final String CREATE_TARGET_FILTER = "CREATE_TARGET_FILTER"; + + /** + * Permission to delete target filters in the {@link ProvisioningTargetRepository}, + * That corresponds in REST API to DELETE. + */ + public static final String DELETE_TARGET_FILTER = "DELETE_TARGET_FILTER"; + /** * Permission to read {@link DistributionSet}s and/or {@link OsPackage}s. * That corresponds in REST API to GET. @@ -290,6 +308,30 @@ public static final class SpringEvalExpressions { public static final String HAS_AUTH_DELETE_TARGET = HAS_AUTH_PREFIX + DELETE_TARGET + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_TARGET_FILTER} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_TARGET_FILTER = HAS_AUTH_PREFIX + CREATE_TARGET_FILTER + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#UPDATE_TARGET_FILTER} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_UPDATE_TARGET_FILTER = HAS_AUTH_PREFIX + UPDATE_TARGET_FILTER + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DELETE_TARGET_FILTER} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DELETE_TARGET_FILTER = HAS_AUTH_PREFIX + DELETE_TARGET_FILTER + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#READ_REPOSITORY} and diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/SpPermissionChecker.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/SpPermissionChecker.java index 15f290a717..080aae64d7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/SpPermissionChecker.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/SpPermissionChecker.java @@ -46,7 +46,7 @@ public boolean hasTargetReadPermission() { /** * Gets the create Target Permission. - * + * * @return READ_TARGET boolean value */ public boolean hasCreateTargetPermission() { @@ -55,7 +55,7 @@ public boolean hasCreateTargetPermission() { /** * Gets the update Target Permission. - * + * * @return READ_TARGET boolean value */ public boolean hasUpdateTargetPermission() { @@ -64,13 +64,40 @@ public boolean hasUpdateTargetPermission() { /** * Gets the delete Target Permission. - * + * * @return READ_TARGET boolean value */ public boolean hasDeleteTargetPermission() { return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.DELETE_TARGET); } + /** + * Gets the create Target Filter Permission. + * + * @return CREATE_TARGET_FILTER boolean value + */ + public boolean hasCreateTargetFilterPermission() { + return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.CREATE_TARGET_FILTER); + } + + /** + * Gets the delete Target Filter Permission. + * + * @return UPDATE_TARGET_FILTER boolean value + */ + public boolean hasUpdateTargetFilterPermission() { + return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.UPDATE_TARGET_FILTER); + } + + /** + * Gets the delete Target Filter Permission. + * + * @return DELETE_TARGET_FILTER boolean value + */ + public boolean hasDeleteTargetFilterPermission() { + return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.DELETE_TARGET_FILTER); + } + /** * Gets the READ Repository Permission. * diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterHeader.java index cafe563352..d84c3527f3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterHeader.java @@ -396,9 +396,9 @@ private void updateCustomFilter() { private boolean hasSavePermission() { if (filterManagementUIState.isCreateFilterViewDisplayed()) { - return permissionChecker.hasCreateTargetPermission(); + return permissionChecker.hasCreateTargetFilterPermission(); } else { - return permissionChecker.hasUpdateTargetPermission(); + return permissionChecker.hasUpdateTargetFilterPermission(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java index 8c8a81acec..5093a12782 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java @@ -92,7 +92,7 @@ private Label createHeaderCaption() { private void buildLayout() { final HorizontalLayout titleFilterIconsLayout = createHeaderFilterIconLayout(); titleFilterIconsLayout.addComponents(headerCaption, searchField, searchResetIcon); - if (permissionChecker.hasCreateTargetPermission()) { + if (permissionChecker.hasCreateTargetFilterPermission()) { titleFilterIconsLayout.addComponent(createfilterButton); titleFilterIconsLayout.setComponentAlignment(createfilterButton, Alignment.TOP_LEFT); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java index ff9d7f35fd..40238fca9a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/MultipleTargetFilter.java @@ -106,9 +106,10 @@ private void buildComponents() { filterByButtons.addStyleName(SPUIStyleDefinitions.NO_TOP_BORDER); targetFilterQueryButtonsTab.init(customTargetTagFilterButtonClick); - menu = new ConfigMenuBar(permChecker.hasCreateTargetPermission(), permChecker.hasUpdateTargetPermission(), - permChecker.hasDeleteRepositoryPermission(), getAddButtonCommand(), getUpdateButtonCommand(), - getDeleteButtonCommand(), UIComponentIdProvider.TARGET_MENU_BAR_ID, i18n); + menu = new ConfigMenuBar(permChecker.hasCreateTargetFilterPermission(), + permChecker.hasUpdateTargetFilterPermission(), permChecker.hasDeleteRepositoryPermission(), + getAddButtonCommand(), getUpdateButtonCommand(), getDeleteButtonCommand(), + UIComponentIdProvider.TARGET_MENU_BAR_ID, i18n); menu.addStyleName("targetTag"); addStyleName(ValoTheme.ACCORDION_BORDERLESS); addTabs(); From 453e5daf35cb3dbc28d6e2ae57da41bd37b1aa97 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Fri, 11 Oct 2019 02:27:37 +0200 Subject: [PATCH 17/28] hono sync: Allow using a different target name than the device id. Signed-off-by: Brandon Schmitt --- docs/content/guides/runhawkbit.md | 6 +++ .../dmf/hono/DmfHonoConfiguration.java | 11 +++++- .../hawkbit/dmf/hono/HonoDeviceSync.java | 37 ++++++++++++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/content/guides/runhawkbit.md b/docs/content/guides/runhawkbit.md index 11333f327c..5686fb6947 100644 --- a/docs/content/guides/runhawkbit.md +++ b/docs/content/guides/runhawkbit.md @@ -106,6 +106,12 @@ spring.cloud.stream.bindings.device-deleted.group=hawkBit ``` For Google Cloud Pub/Sub disable the default Maven profile `hono-amqp` and enable the profile `amqp-gcp-pubsub`. +Additionally, you can specify a field of the device's extension object which will be used as the corresponding target's name: +``` +hawkbit.dmf.hono.target-name-field=fancyFieldName +``` +If none is specified the device's ID is used as the target's name. + ### Adapt hostname of example scenario [creation script](https://github.com/eclipse/hawkbit-examples/blob/master/hawkbit-example-mgmt-simulator/src/main/resources/application.properties) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index d9d1691422..ba22578572 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -27,11 +27,12 @@ public class DmfHonoConfiguration { private String oidcClientId = ""; private String username = ""; private String password = ""; + private String targetNameField = ""; @Bean public HonoDeviceSync honoDeviceSync() { return new HonoDeviceSync(tenantListUri, deviceListUri, credentialsListUri, authenticationMethod, - oidcTokenUri, oidcClientId, username, password); + oidcTokenUri, oidcClientId, username, password, targetNameField); } public String getTenantListUri() { @@ -97,4 +98,12 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + + public String getTargetNameField() { + return targetNameField; + } + + public void setTargetNameField(String targetNameField) { + this.targetNameField = targetNameField; + } } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 3435acb85e..aeb25dcdee 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -57,7 +57,7 @@ public class HonoDeviceSync { private TargetManagement targetManagement; @Autowired - PermissionService permissionService; + private PermissionService permissionService; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -75,10 +75,11 @@ public class HonoDeviceSync { private String oidcClientId; private String username; private String password; + private String targetNameFieldInDeviceExtension; HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String honoCredentialsListUri, String authenticationMethod, String oidcTokenUri, String oidcClientId, String username, - String password) { + String password, String targetNameFieldInDeviceExtension) { this.honoTenantListUri = honoTenantListUri; this.honoDeviceListUri = honoDevicesEndpoint; this.honoCredentialsListUri = honoCredentialsListUri; @@ -87,6 +88,7 @@ public class HonoDeviceSync { this.oidcClientId = oidcClientId; this.username = username; this.password = password; + this.targetNameFieldInDeviceExtension = targetNameFieldInDeviceExtension; objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @@ -169,7 +171,7 @@ public void checkDeviceIfAbsentSync(String tenant, String deviceID) { } } - public List getAllHonoTenants() throws IOException { + private List getAllHonoTenants() throws IOException { List tenants = new ArrayList<>(); long offset = 0; long total = Long.MAX_VALUE; @@ -187,7 +189,7 @@ public List getAllHonoTenants() throws IOException { return tenants; } - public Map getAllHonoDevices(String tenant) throws IOException { + private Map getAllHonoDevices(String tenant) throws IOException { Map devices = new HashMap<>(); long offset = 0; long total = Long.MAX_VALUE; @@ -309,12 +311,37 @@ public void onDeviceDeleted(IdentifiableHonoDevice honoDevice) { private Target createTarget(IdentifiableHonoDevice honoDevice) { systemManagement.getTenantMetadata(honoDevice.getTenant()); return targetManagement.create(entityFactory.target().create() - .controllerId(honoDevice.getId()).description(honoDevice.getDevice().getExt().toString())); + .controllerId(honoDevice.getId()) + .name(getDeviceName(honoDevice)) + .description(honoDevice.getDevice().getExt().toString())); } private Target updateTarget(IdentifiableHonoDevice honoDevice) { return targetManagement.update(entityFactory.target() .update(honoDevice.getId()) + .name(getDeviceName(honoDevice)) .description(honoDevice.getDevice().getExt().toString())); } + + private String getDeviceName(IdentifiableHonoDevice honoDevice) { + if (targetNameFieldInDeviceExtension != null) { + Object ext = honoDevice.getDevice().getExt(); + if (ext instanceof JsonNode) { + JsonNode nameValue = ((JsonNode) ext).get(targetNameFieldInDeviceExtension); + if (nameValue != null) { + return nameValue.asText(); + } + else { + LOG.warn("The extension object of the device with id = '{}' does not contain the field '{}'.", + honoDevice.getId(), targetNameFieldInDeviceExtension); + } + } + else { + LOG.warn("The extension field of the device with id = '{}' is not a valid JSON object.", + honoDevice.getId()); + } + } + + return honoDevice.getId(); + } } From 3275042806d1913d18152e551ebb9c30af37b7a9 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Fri, 11 Oct 2019 02:33:15 +0200 Subject: [PATCH 18/28] Add encoding to the URLEncoder. Signed-off-by: Brandon Schmitt --- .../java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index aeb25dcdee..9d9444ff4b 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -99,8 +99,8 @@ private void init() { // Since ApplicationReadyEvent is emitted multiple times make sure it is synced at most once during startup. if (!syncedInitially) { - synchronize(false); syncedInitially = true; + synchronize(false); } } @@ -238,8 +238,10 @@ private HttpURLConnection getHonoData(String uri) throws IOException { HttpURLConnection jwtConnection = (HttpURLConnection) oidcTokenUrl.openConnection(); jwtConnection.setDoOutput(true); DataOutputStream outputStream = new DataOutputStream(jwtConnection.getOutputStream()); - outputStream.writeBytes("grant_type=password&client_id=" + URLEncoder.encode(oidcClientId) - + "&username=" + URLEncoder.encode(username) + "&password=" + URLEncoder.encode(password)); + outputStream.writeBytes("grant_type=password" + + "&client_id=" + URLEncoder.encode(oidcClientId, "UTF-8") + + "&username=" + URLEncoder.encode(username, "UTF-8") + + "&password=" + URLEncoder.encode(password, "UTF-8")); outputStream.flush(); outputStream.close(); From e9675dc5cd43bfcaebe00f1929c8906d791481a6 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Wed, 16 Oct 2019 17:55:11 +0200 Subject: [PATCH 19/28] Fix credential polling. Signed-off-by: Brandon Schmitt --- .../hawkbit/dmf/hono/HonoDeviceSync.java | 45 ++++++++++--------- .../dmf/hono/model/HonoCredentials.java | 17 +++++++ .../hawkbit/dmf/hono/model/HonoDevice.java | 5 +++ .../dmf/hono/model/HonoPSKCredentials.java | 4 ++ .../hono/model/HonoPasswordCredentials.java | 16 +++++-- .../hawkbit/dmf/hono/model/HonoSecret.java | 33 +++++++++----- .../model/HonoX509CertificateCredentials.java | 2 + 7 files changed, 87 insertions(+), 35 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 9d9444ff4b..f4b14488db 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -132,31 +132,34 @@ private void synchronizeTenant(String tenant) throws IOException, InterruptedExc Semaphore semaphore = mutexes.computeIfAbsent(tenant, t -> new Semaphore(1)); semaphore.acquire(); - Map honoDevices = getAllHonoDevices(tenant); - Slice targets = systemSecurityContext.runAsSystemAsTenant( - () -> targetManagement.findAll(Pageable.unpaged()), tenant); - - for (Target target : targets) { - String controllerId = target.getControllerId(); - if (honoDevices.containsKey(controllerId)) { - IdentifiableHonoDevice honoDevice = honoDevices.remove(controllerId); - honoDevice.setTenant(tenant); - systemSecurityContext.runAsSystemAsTenant(() -> updateTarget(honoDevice), tenant); + try { + Map honoDevices = getAllHonoDevices(tenant); + Slice targets = systemSecurityContext.runAsSystemAsTenant( + () -> targetManagement.findAll(Pageable.unpaged()), tenant); + + for (Target target : targets) { + String controllerId = target.getControllerId(); + if (honoDevices.containsKey(controllerId)) { + IdentifiableHonoDevice honoDevice = honoDevices.remove(controllerId); + honoDevice.setTenant(tenant); + systemSecurityContext.runAsSystemAsTenant(() -> updateTarget(honoDevice), tenant); + } + else { + systemSecurityContext.runAsSystemAsTenant(() -> { + targetManagement.deleteByControllerID(target.getControllerId()); + return true; + }, tenant); + } } - else { - systemSecurityContext.runAsSystemAsTenant(() -> { - targetManagement.deleteByControllerID(target.getControllerId()); - return true; - }, tenant); + + // At this point honoTargets only contains objects which were not found in hawkBit's target repository + for (Map.Entry entry : honoDevices.entrySet()) { + systemSecurityContext.runAsSystemAsTenant(() -> createTarget(entry.getValue()), tenant); } } - - // At this point honoTargets only contains objects which were not found in hawkBit's target repository - for (Map.Entry entry : honoDevices.entrySet()) { - systemSecurityContext.runAsSystemAsTenant(() -> createTarget(entry.getValue()), tenant); + finally { + semaphore.release(); } - - semaphore.release(); } public void checkDeviceIfAbsentSync(String tenant, String deviceID) { diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java index 5ef4f554e0..7309b057c8 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoCredentials.java @@ -8,38 +8,55 @@ */ package org.eclipse.hawkbit.dmf.hono.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + import java.util.Collection; +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = HonoPasswordCredentials.class, name = "hashed-password"), + @JsonSubTypes.Type(value = HonoPSKCredentials.class, name = "psk"), + @JsonSubTypes.Type(value = HonoX509CertificateCredentials.class, name = "x509-cert") +}) public abstract class HonoCredentials { private String authId; private String type; private boolean enabled = true; Collection secrets; + @JsonProperty("type") public String getType() { return type; } + @JsonProperty("auth-id") public String getAuthId() { return authId; } + @JsonProperty("enabled") public boolean isEnabled() { return enabled; } + @JsonProperty("secrets") public Collection getSecrets() { return secrets; } + @JsonProperty("type") public void setType(String type) { this.type = type; } + @JsonProperty("auth-id") public void setAuthId(String authId) { this.authId = authId; } + @JsonProperty("enabled") public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java index 52d64eca88..f30c719d7d 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoDevice.java @@ -8,24 +8,29 @@ */ package org.eclipse.hawkbit.dmf.hono.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; public class HonoDevice { private boolean enabled; private JsonNode ext; + @JsonProperty("enabled") public boolean isEnabled() { return enabled; } + @JsonProperty("ext") public Object getExt() { return ext; } + @JsonProperty("enabled") public void setEnabled(boolean enabled) { this.enabled = enabled; } + @JsonProperty("ext") public void setExt(JsonNode ext) { this.ext = ext; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java index 1d326ef401..90f2e8b42c 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPSKCredentials.java @@ -8,12 +8,14 @@ */ package org.eclipse.hawkbit.dmf.hono.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import java.util.Collection; @JsonTypeName("psk") public class HonoPSKCredentials extends HonoCredentials { + @JsonProperty("secrets") public void setSecrets(Collection secrets) { this.secrets = secrets; } @@ -21,10 +23,12 @@ public void setSecrets(Collection secrets) { public static class Secret extends HonoSecret { private String key; + @JsonProperty("key") public String getKey() { return key; } + @JsonProperty("key") public void setKey(String key) { this.key = key; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java index c0ff205287..7397802174 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.dmf.hono.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; @@ -17,6 +18,7 @@ @JsonTypeName("hashed-password") public class HonoPasswordCredentials extends HonoCredentials { + @JsonProperty("secrets") public void setSecrets(Collection secrets) { this.secrets = secrets; } @@ -45,28 +47,34 @@ else if(hashFunction.equals("sha-512")) { return encoder.matches(password + (salt != null ? salt : ""), pwdHash); } + @JsonProperty("hash-function") public String getHashFunction() { return hashFunction; } + @JsonProperty("salt") public String getSalt() { return salt; } + @JsonProperty("pwd-hash") public String getPwdHash() { return pwdHash; } + @JsonProperty("hash-function") public void setHashFunction(String hashFunction) { this.hashFunction = hashFunction; } - public void setSalt(String salt) { - this.salt = salt; + @JsonProperty("salt") + public void setSalt(byte[] salt) { + this.salt = new String(salt); } - public void setPwdHash(String pwdHash) { - this.pwdHash = pwdHash; + @JsonProperty("pwd-hash") + public void setPwdHash(byte[] pwdHash) { + this.pwdHash = new String(pwdHash); } } } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java index d0cf4caa3f..bacd6f85ad 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoSecret.java @@ -8,56 +8,69 @@ */ package org.eclipse.hawkbit.dmf.hono.model; -import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.ZonedDateTime; public abstract class HonoSecret { private String id; private boolean enabled = true; - private LocalDateTime notBefore; - private LocalDateTime notAfter; + private ZonedDateTime notBefore; + private ZonedDateTime notAfter; + @JsonProperty("id") public String getId() { return id; } + @JsonProperty("enabled") public boolean isEnabled() { return enabled; } - public LocalDateTime getNotBefore() { + @JsonProperty("not-before") + public ZonedDateTime getNotBefore() { return notBefore; } - public LocalDateTime getNotAfter() { + @JsonProperty("not-after") + public ZonedDateTime getNotAfter() { return notAfter; } + @JsonProperty("id") public void setId(String id) { this.id = id; } + @JsonProperty("enabled") public void setEnabled(boolean enabled) { this.enabled = enabled; } - public void setNotBefore(LocalDateTime notBefore) { + @JsonProperty("not-before") + public void setNotBefore(ZonedDateTime notBefore) { this.notBefore = notBefore; } + @JsonProperty("not-before") public void setNotBefore(String notBefore) { - this.notBefore = LocalDateTime.parse(notBefore); + this.notBefore = ZonedDateTime.parse(notBefore); } - public void setNotAfter(LocalDateTime notAfter) { + @JsonProperty("not-after") + public void setNotAfter(ZonedDateTime notAfter) { this.notAfter = notAfter; } + @JsonProperty("not-after") public void setNotAfter(String notAfter) { - this.notAfter = LocalDateTime.parse(notAfter); + this.notAfter = ZonedDateTime.parse(notAfter); } public boolean isValid() { - LocalDateTime now = LocalDateTime.now(); + ZonedDateTime now = ZonedDateTime.now(); return (notBefore == null || now.compareTo(notBefore) >= 0) && (notAfter == null || now.compareTo(notAfter) <= 0); } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java index 44dbc1cab0..5258e0d669 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoX509CertificateCredentials.java @@ -8,12 +8,14 @@ */ package org.eclipse.hawkbit.dmf.hono.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import java.util.Collection; @JsonTypeName("x509-cert") public class HonoX509CertificateCredentials extends HonoCredentials { + @JsonProperty("secrets") public void setSecrets(Collection secrets) { this.secrets = secrets; } From 24d1fed46b20455be9710d9b3b4a45a3dd73fc11 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 21 Oct 2019 11:27:06 +0200 Subject: [PATCH 20/28] Update Tests. Signed-off-by: Brandon Schmitt --- .../eclipse/hawkbit/security/PreAuthHonoProviderTest.java | 8 ++++---- .../eclipse/hawkbit/im/authentication/PermissionTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java index 6ca7c0e359..ab06b2c9d2 100644 --- a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java +++ b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthHonoProviderTest.java @@ -54,8 +54,8 @@ public void invalidCredentialsThrowsAuthenticationException() { HonoPasswordCredentials.Secret secret = new HonoPasswordCredentials.Secret(); secret.setHashFunction("sha-256"); - secret.setSalt("salt"); - secret.setPwdHash("hash"); + secret.setSalt("salt".getBytes()); + secret.setPwdHash("hash".getBytes()); HonoPasswordCredentials credentials = new HonoPasswordCredentials(); credentials.setAuthId("authId"); @@ -86,8 +86,8 @@ public void credentialsAreCorrect() { HonoPasswordCredentials.Secret secret = new HonoPasswordCredentials.Secret(); secret.setHashFunction("sha-256"); - secret.setSalt("salt"); - secret.setPwdHash("7a37b85c8918eac19a9089c0fa5a2ab4dce3f90528dcdeec108b23ddf3607b99"); + secret.setSalt("salt".getBytes()); + secret.setPwdHash("7a37b85c8918eac19a9089c0fa5a2ab4dce3f90528dcdeec108b23ddf3607b99".getBytes()); HonoPasswordCredentials credentials = new HonoPasswordCredentials(); credentials.setAuthId("authId"); diff --git a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java index bd1f1d661f..c95fd50026 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/PermissionTest.java @@ -31,7 +31,7 @@ public final class PermissionTest { @Test @Description("Verify the get permission function") public void testGetPermissions() { - final int allPermission = 18; + final int allPermission = 21; final Collection allAuthorities = SpPermission.getAllAuthorities(); final List allAuthoritiesList = PermissionUtils.createAllAuthorityList(); assertThat(allAuthorities).hasSize(allPermission); From 95bbe040f10fa402d3705f019d0a06f5d260e6d0 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 4 Nov 2019 09:58:40 +0100 Subject: [PATCH 21/28] Require targetNameFieldInDeviceExtension to be not empty for it to be considered. Signed-off-by: Brandon Schmitt --- .../main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index f4b14488db..f218de267a 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -329,7 +329,7 @@ private Target updateTarget(IdentifiableHonoDevice honoDevice) { } private String getDeviceName(IdentifiableHonoDevice honoDevice) { - if (targetNameFieldInDeviceExtension != null) { + if (targetNameFieldInDeviceExtension != null && !targetNameFieldInDeviceExtension.isEmpty()) { Object ext = honoDevice.getDevice().getExt(); if (ext instanceof JsonNode) { JsonNode nameValue = ((JsonNode) ext).get(targetNameFieldInDeviceExtension); From b06a5d921f007300c6ce60f1b738f5175f5444a7 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 7 Nov 2019 17:26:31 +0100 Subject: [PATCH 22/28] Undo UPDATE_TARGET revokation as it is needed to assign targets to distribution sets. Signed-off-by: Brandon Schmitt --- .../eclipse/hawkbit/im/authentication/PermissionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java index 3bc13853ff..9d799e4730 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/PermissionService.java @@ -22,7 +22,7 @@ public class PermissionService { private boolean honoSyncEnabled = false; - private final List disabledPermissionsByUsingHono = Arrays.asList(SpPermission.CREATE_TARGET, SpPermission.UPDATE_TARGET, SpPermission.DELETE_TARGET); + private final List disabledPermissionsByUsingHono = Arrays.asList(SpPermission.CREATE_TARGET, SpPermission.DELETE_TARGET); /** * Checks if the given {@code permission} contains in the. In case no From 51013549c86604f43f66226716bf0931d34e575b Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 14 Nov 2019 13:15:36 +0100 Subject: [PATCH 23/28] Log error during credential fetching. Signed-off-by: Brandon Schmitt --- .../main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index f218de267a..6eaf12f048 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -220,6 +220,7 @@ public Collection getAllHonoCredentials(String tenant, String d return objectMapper.readValue(connection.getInputStream(), new TypeReference>() {}); } catch (IOException e) { + LOG.error("Could not read credentials for device '{}/{}'.", tenant, deviceId, e); return null; } } From 724ba90602510aaf8ba3d0be624f76798267de8e Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Thu, 14 Nov 2019 15:09:30 +0100 Subject: [PATCH 24/28] Allow hawkBit to use an OIDC service account. Signed-off-by: Brandon Schmitt --- docs/content/guides/runhawkbit.md | 1 + .../dmf/hono/DmfHonoConfiguration.java | 11 +++++++++- .../hawkbit/dmf/hono/HonoDeviceSync.java | 21 +++++++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/content/guides/runhawkbit.md b/docs/content/guides/runhawkbit.md index 5686fb6947..f32cbbabeb 100644 --- a/docs/content/guides/runhawkbit.md +++ b/docs/content/guides/runhawkbit.md @@ -91,6 +91,7 @@ hawkbit.dmf.hono.password=PASSWORD // Only for authentication-method = oidc hawkbit.dmf.hono.oidc-token-uri=http://OIDC_HOST/auth/realms/REALM/protocol/openid-connect/token hawkbit.dmf.hono.oidc-client-id=OIDC_CLIENT_ID +hawkbit.dmf.hono.oidc-client-secret=OIDC_CLIENT_SECRET # You can use a oidc client secret instead of username+password ``` hawkBit handles device registry updates through CUD events emitted by Hono over any Spring Cloud Stream supported channel, such as AMQP or Google Cloud Pub/Sub. diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java index ba22578572..9f54372670 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/DmfHonoConfiguration.java @@ -25,6 +25,7 @@ public class DmfHonoConfiguration { private String authenticationMethod = "none"; private String oidcTokenUri = ""; private String oidcClientId = ""; + private String oidcClientSecret = ""; private String username = ""; private String password = ""; private String targetNameField = ""; @@ -32,7 +33,7 @@ public class DmfHonoConfiguration { @Bean public HonoDeviceSync honoDeviceSync() { return new HonoDeviceSync(tenantListUri, deviceListUri, credentialsListUri, authenticationMethod, - oidcTokenUri, oidcClientId, username, password, targetNameField); + oidcTokenUri, oidcClientId, oidcClientSecret, username, password, targetNameField); } public String getTenantListUri() { @@ -83,6 +84,14 @@ public void setOidcClientId(String oidcClientId) { this.oidcClientId = oidcClientId; } + public String getOidcClientSecret() { + return oidcClientSecret; + } + + public void setOidcClientSecret(String oidcClientSecret) { + this.oidcClientSecret = oidcClientSecret; + } + public String getUsername() { return username; } diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 6eaf12f048..6d3a4bb0a9 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -73,19 +73,21 @@ public class HonoDeviceSync { private String authenticationMethod; private String oidcTokenUri; private String oidcClientId; + private String oidcClientSecret; private String username; private String password; private String targetNameFieldInDeviceExtension; HonoDeviceSync(String honoTenantListUri, String honoDevicesEndpoint, String honoCredentialsListUri, - String authenticationMethod, String oidcTokenUri, String oidcClientId, String username, - String password, String targetNameFieldInDeviceExtension) { + String authenticationMethod, String oidcTokenUri, String oidcClientId, String oidcClientSecret, + String username, String password, String targetNameFieldInDeviceExtension) { this.honoTenantListUri = honoTenantListUri; this.honoDeviceListUri = honoDevicesEndpoint; this.honoCredentialsListUri = honoCredentialsListUri; this.authenticationMethod = authenticationMethod; this.oidcTokenUri = oidcTokenUri; this.oidcClientId = oidcClientId; + this.oidcClientSecret = oidcClientSecret; this.username = username; this.password = password; this.targetNameFieldInDeviceExtension = targetNameFieldInDeviceExtension; @@ -242,10 +244,17 @@ private HttpURLConnection getHonoData(String uri) throws IOException { HttpURLConnection jwtConnection = (HttpURLConnection) oidcTokenUrl.openConnection(); jwtConnection.setDoOutput(true); DataOutputStream outputStream = new DataOutputStream(jwtConnection.getOutputStream()); - outputStream.writeBytes("grant_type=password" - + "&client_id=" + URLEncoder.encode(oidcClientId, "UTF-8") - + "&username=" + URLEncoder.encode(username, "UTF-8") - + "&password=" + URLEncoder.encode(password, "UTF-8")); + if (oidcClientSecret != null && !oidcClientSecret.isEmpty()) { + outputStream.writeBytes("grant_type=client_credentials" + + "&client_id=" + URLEncoder.encode(oidcClientId, "UTF-8") + + "&client_secret=" + URLEncoder.encode(oidcClientSecret, "UTF-8")); + } + else { + outputStream.writeBytes("grant_type=password" + + "&client_id=" + URLEncoder.encode(oidcClientId, "UTF-8") + + "&username=" + URLEncoder.encode(username, "UTF-8") + + "&password=" + URLEncoder.encode(password, "UTF-8")); + } outputStream.flush(); outputStream.close(); From dc711e99e92f0fd8e0c95966c3aa70704e766b56 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Fri, 22 Nov 2019 16:11:02 +0100 Subject: [PATCH 25/28] Add "none" as hash function. Signed-off-by: Brandon Schmitt --- .../dmf/hono/model/HonoPasswordCredentials.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java index 7397802174..6397d64c42 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/model/HonoPasswordCredentials.java @@ -30,14 +30,18 @@ public static class Secret extends HonoSecret { @Override public boolean matches(final String password) { + if ("none".equals(hashFunction)) { + return pwdHash.equals(password); + } + PasswordEncoder encoder; - if (hashFunction.equals("bcrypt")) { + if ("bcrypt".equals(hashFunction)) { encoder = new BCryptPasswordEncoder(); } - else if(hashFunction.equals("sha-256")) { + else if("sha-256".equals(hashFunction)) { encoder = new MessageDigestPasswordEncoder("SHA-256"); } - else if(hashFunction.equals("sha-512")) { + else if("SHA-512".equals(hashFunction)) { encoder = new MessageDigestPasswordEncoder("SHA-512"); } else { From 9256fdf21e9e1b7ce4bf4948a502ee8de9620b86 Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Fri, 22 Nov 2019 17:06:39 +0100 Subject: [PATCH 26/28] Sync target tags if they are in the `ext` field. Signed-off-by: Brandon Schmitt --- .../hawkbit/dmf/hono/HonoDeviceSync.java | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 6d3a4bb0a9..54bb98d7db 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -17,8 +17,10 @@ import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +31,7 @@ import org.springframework.context.event.EventListener; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.lang.Nullable; import java.io.DataOutputStream; import java.io.IOException; @@ -56,6 +59,9 @@ public class HonoDeviceSync { @Autowired private TargetManagement targetManagement; + @Autowired + private TargetTagManagement targetTagManagement; + @Autowired private PermissionService permissionService; @@ -325,21 +331,53 @@ public void onDeviceDeleted(IdentifiableHonoDevice honoDevice) { private Target createTarget(IdentifiableHonoDevice honoDevice) { systemManagement.getTenantMetadata(honoDevice.getTenant()); - return targetManagement.create(entityFactory.target().create() + Target target = targetManagement.create(entityFactory.target().create() .controllerId(honoDevice.getId()) .name(getDeviceName(honoDevice)) .description(honoDevice.getDevice().getExt().toString())); + syncTags(target, getDeviceTags(honoDevice)); + return target; } private Target updateTarget(IdentifiableHonoDevice honoDevice) { - return targetManagement.update(entityFactory.target() + Target target = targetManagement.update(entityFactory.target() .update(honoDevice.getId()) .name(getDeviceName(honoDevice)) .description(honoDevice.getDevice().getExt().toString())); + syncTags(target, getDeviceTags(honoDevice)); + return target; + } + + private void syncTags(Target target, @Nullable String tags) { + String controllerId = target.getControllerId(); + Collection tagNames; + if (tags != null) { + tagNames = new ArrayList<>(Arrays.asList(tags.split(","))); + } + else { + tagNames = Collections.emptyList(); + } + + Slice assignedTags = targetTagManagement.findByTarget(Pageable.unpaged(), target.getControllerId()); + for (TargetTag tag : assignedTags) { + if (tagNames.contains(tag.getName())) { + tagNames.remove(tag.getName()); + } + else { + targetManagement.unAssignTag(controllerId, tag.getId()); + } + } + + for (String name : tagNames) { + TargetTag tag = targetTagManagement.getByName(name).orElseGet( + () -> targetTagManagement.create(entityFactory.tag().create().name(name))); + + targetManagement.assignTag(Collections.singleton(target.getControllerId()), tag.getId()); + } } private String getDeviceName(IdentifiableHonoDevice honoDevice) { - if (targetNameFieldInDeviceExtension != null && !targetNameFieldInDeviceExtension.isEmpty()) { + if (targetNameFieldInDeviceExtension != null) { Object ext = honoDevice.getDevice().getExt(); if (ext instanceof JsonNode) { JsonNode nameValue = ((JsonNode) ext).get(targetNameFieldInDeviceExtension); @@ -347,16 +385,32 @@ private String getDeviceName(IdentifiableHonoDevice honoDevice) { return nameValue.asText(); } else { - LOG.warn("The extension object of the device with id = '{}' does not contain the field '{}'.", - honoDevice.getId(), targetNameFieldInDeviceExtension); + LOG.warn("The extension object of device '{}/{}' does not contain the field '{}'.", + honoDevice.getTenant(), honoDevice.getId(), targetNameFieldInDeviceExtension); } } else { - LOG.warn("The extension field of the device with id = '{}' is not a valid JSON object.", + LOG.warn("The extension field of device '{}/{}' is not a valid JSON object.", honoDevice.getTenant(), honoDevice.getId()); } } return honoDevice.getId(); } + + private String getDeviceTags(IdentifiableHonoDevice honoDevice) { + Object ext = honoDevice.getDevice().getExt(); + if (ext instanceof JsonNode) { + JsonNode tagsValue = ((JsonNode) ext).get("TargetTags"); + if (tagsValue != null) { + return tagsValue.asText(); + } + } + else { + LOG.warn("The extension field of device '{}/{}' is not a valid JSON object.", honoDevice.getTenant(), + honoDevice.getId()); + } + + return null; + } } From 82425ca89f9b9e7fd06fd4e38f89f497cbbecd7a Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Wed, 27 Nov 2019 13:13:44 +0100 Subject: [PATCH 27/28] Remove description as it is easily to long. Signed-off-by: Brandon Schmitt --- .../java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index 54bb98d7db..fffc69189f 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -333,8 +333,7 @@ private Target createTarget(IdentifiableHonoDevice honoDevice) { systemManagement.getTenantMetadata(honoDevice.getTenant()); Target target = targetManagement.create(entityFactory.target().create() .controllerId(honoDevice.getId()) - .name(getDeviceName(honoDevice)) - .description(honoDevice.getDevice().getExt().toString())); + .name(getDeviceName(honoDevice))); syncTags(target, getDeviceTags(honoDevice)); return target; } @@ -342,8 +341,7 @@ private Target createTarget(IdentifiableHonoDevice honoDevice) { private Target updateTarget(IdentifiableHonoDevice honoDevice) { Target target = targetManagement.update(entityFactory.target() .update(honoDevice.getId()) - .name(getDeviceName(honoDevice)) - .description(honoDevice.getDevice().getExt().toString())); + .name(getDeviceName(honoDevice))); syncTags(target, getDeviceTags(honoDevice)); return target; } From a7420f31b33800bc919a3940739b8b169418339f Mon Sep 17 00:00:00 2001 From: Brandon Schmitt Date: Mon, 13 Jul 2020 14:40:00 +0200 Subject: [PATCH 28/28] Allow Hono API URLs to predefine arguments. Signed-off-by: Brandon Schmitt --- .../java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java index fffc69189f..e0e274c773 100644 --- a/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java +++ b/hawkbit-dmf/hawkbit-dmf-hono/src/main/java/org/eclipse/hawkbit/dmf/hono/HonoDeviceSync.java @@ -187,7 +187,7 @@ private List getAllHonoTenants() throws IOException { long offset = 0; long total = Long.MAX_VALUE; while (tenants.size() < total) { - HttpURLConnection connection = getHonoData(honoTenantListUri + "?offset=" + offset); + HttpURLConnection connection = getHonoData(honoTenantListUri + (honoTenantListUri.contains("?") ? "&" : "?") + "offset=" + offset); HonoTenantListPage page = objectMapper.readValue(connection.getInputStream(), HonoTenantListPage.class); if (page.getItems() != null) { @@ -205,7 +205,8 @@ private Map getAllHonoDevices(String tenant) thr long offset = 0; long total = Long.MAX_VALUE; while (devices.size() < total) { - HttpURLConnection connection = getHonoData(honoDeviceListUri.replace("$tenantId", tenant) + "?offset=" + offset); + HttpURLConnection connection = getHonoData(honoDeviceListUri.replace("$tenantId", tenant) + + (honoDeviceListUri.contains("?") ? "&" : "?") + "offset=" + offset); HonoDeviceListPage page = objectMapper.readValue(connection.getInputStream(), HonoDeviceListPage.class); if (page.getItems() != null) {