Configurable download URL generation (#296)

Configurable download URL generation.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-09-28 09:18:03 +02:00
committed by GitHub
parent 0cc1cfcc8c
commit 5c53bef164
77 changed files with 2114 additions and 1110 deletions

View File

@@ -34,7 +34,6 @@ import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.Artifact.UrlProtocol;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
@@ -206,12 +205,12 @@ public class DeviceSimulatorUpdater {
private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status,
final Artifact artifact) {
if (artifact.getUrls().containsKey(UrlProtocol.HTTPS)) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTPS), targetToken,
artifact.getHashes().getSha1(), artifact.getSize()));
} else if (artifact.getUrls().containsKey(UrlProtocol.HTTP)) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTP), targetToken,
artifact.getHashes().getSha1(), artifact.getSize()));
if (artifact.getUrls().containsKey("https")) {
status.add(downloadUrl(artifact.getUrls().get("https"), targetToken, artifact.getHashes().getSha1(),
artifact.getSize()));
} else if (artifact.getUrls().containsKey("http")) {
status.add(downloadUrl(artifact.getUrls().get("http"), targetToken, artifact.getHashes().getSha1(),
artifact.getSize()));
}
}

View File

@@ -9,8 +9,15 @@
vaadin.servlet.productionMode=true
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=false
hawkbit.artifact.url.https.enabled=true
hawkbit.artifact.url.https.pattern={protocol}://{hostname}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.https.hostname=hawkbit.eu-gb.mybluemix.net
## Configuration for building download URLs - START
hawkbit.artifact.url.protocols.download-http.rel=download-http
hawkbit.artifact.url.protocols.download-http.protocol=https
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.protocols.download-http.hostname=hawkbit.eu-gb.mybluemix.net
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
## Configuration for building download URLs - END

View File

@@ -16,12 +16,6 @@ hawkbit.server.ddi.security.authentication.anonymous.enabled=true
hawkbit.server.ddi.security.authentication.targettoken.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=true
# Download URL generation config
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=true
hawkbit.artifact.url.http.port=8080
hawkbit.artifact.url.https.enabled=false
## Vaadin configuration
vaadin.servlet.productionMode=false

View File

@@ -50,11 +50,21 @@ public class SoftwareModuleTypeBuilder {
return this;
}
/**
* @param description
* of the module
* @return the builder itself
*/
public SoftwareModuleTypeBuilder description(final String description) {
this.description = description;
return this;
}
/**
* @param maxAssignments
* of a module of that type to the same distribution set
* @return the builder itself
*/
public SoftwareModuleTypeBuilder maxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
return this;
@@ -99,4 +109,4 @@ public class SoftwareModuleTypeBuilder {
return body;
}
}
}

View File

@@ -227,7 +227,7 @@ public class ArtifactStore implements ArtifactRepository {
* @return a paged list of artifacts mapped from the given dbFiles
*/
private List<DbArtifact> map(final List<GridFSDBFile> dbFiles) {
return dbFiles.stream().map(this::map).collect(Collectors.toList());
return dbFiles.stream().map(ArtifactStore::map).collect(Collectors.toList());
}
/**
@@ -263,7 +263,7 @@ public class ArtifactStore implements ArtifactRepository {
* the mongoDB gridFs file.
* @return a mapped artifact from the given dbFile
*/
private GridFsArtifact map(final GridFSFile fsFile) {
private static GridFsArtifact map(final GridFSFile fsFile) {
if (fsFile == null) {
return null;
}

View File

@@ -82,8 +82,7 @@ public class CacheAutoConfiguration extends CachingConfigurerSupport {
*/
@Override
public Collection<Cache> resolveCaches(final CacheOperationInvocationContext<?> context) {
return super.resolveCaches(context).stream().map(cache -> new TenantCacheWrapper(cache))
.collect(Collectors.toList());
return super.resolveCaches(context).stream().map(TenantCacheWrapper::new).collect(Collectors.toList());
}
/*

View File

@@ -148,7 +148,7 @@ public class SecurityManagedConfiguration {
private DdiSecurityProperties ddiSecurityConfiguration;
@Autowired
private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties;
private SecurityProperties springSecurityProperties;
@Autowired
private SystemSecurityContext systemSecurityContext;
@@ -478,7 +478,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
public void onAuthenticationSuccess(final Authentication authentication) throws Exception {
if (authentication.getClass().equals(TenantUserPasswordAuthenticationToken.class)) {
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(),
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata,
((TenantUserPasswordAuthenticationToken) authentication).getTenant().toString());
} else if (authentication.getClass().equals(UsernamePasswordAuthenticationToken.class)) {
// TODO: vaadin4spring-ext-security does not give us the
@@ -489,7 +489,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
// vaadin4spring 0.0.7 because it
// has been fixed.
final String defaultTenant = "DEFAULT";
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(), defaultTenant);
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant);
}
super.onAuthenticationSuccess(authentication);
@@ -526,7 +526,7 @@ class AuthenticationSuccessTenantMetadataCreationFilter implements Filter {
private void lazyCreateTenantMetadata() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
systemSecurityContext.runAsSystem(() -> systemManagement.getTenantMetadata());
systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata);
}
}

View File

@@ -12,8 +12,10 @@ import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.hawkbit.HawkbitServerProperties;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.eclipse.hawkbit.api.PropertyBasedArtifactUrlHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -22,25 +24,22 @@ import org.springframework.context.annotation.Configuration;
import com.google.common.base.Throwables;
/**
* Autoconfiguration of the {@link HostnameResolver} based on a property.
*
*
*
* Auto configuration for {@link HostnameResolver} and
* {@link ArtifactUrlHandler} based on a properties.
*/
@Configuration
@EnableConfigurationProperties(HawkbitServerProperties.class)
@EnableConfigurationProperties({ HawkbitServerProperties.class, ArtifactUrlHandlerProperties.class })
public class PropertyHostnameResolverAutoConfiguration {
@Autowired
private HawkbitServerProperties serverProperties;
/**
* @param serverProperties
* to get the servers URL
* @return the default autoconfigure hostname resolver implementation which
* is property based specified by the property {@link #url}
*/
@Bean
@ConditionalOnMissingBean(value = HostnameResolver.class)
public HostnameResolver hostnameResolver() {
public HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) {
return () -> {
try {
return new URL(serverProperties.getUrl());
@@ -50,4 +49,16 @@ public class PropertyHostnameResolverAutoConfiguration {
};
}
/**
* @param urlHandlerProperties
* for bean configuration
* @return PropertyBasedArtifactUrlHandler bean
*/
@Bean
@ConditionalOnMissingBean(ArtifactUrlHandler.class)
public PropertyBasedArtifactUrlHandler propertyBasedArtifactUrlHandler(
final ArtifactUrlHandlerProperties urlHandlerProperties) {
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties);
}
}

View File

@@ -41,9 +41,25 @@ hawkbit.controller.maxPollingTime=23:59:59
hawkbit.controller.minPollingTime=00:00:30
# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window
# Configuration for RabbitMQ integration
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl
hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter
hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver
hawkbit.dmf.rabbitmq.authenticationReceiverQueue=authentication_receiver
# Download URL generation configuration
hawkbit.artifact.url.protocols.download-http.rel=download-http
hawkbit.artifact.url.protocols.download-http.hostname=localhost
hawkbit.artifact.url.protocols.download-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-http.protocol=http
hawkbit.artifact.url.protocols.download-http.port=8080
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
hawkbit.artifact.url.protocols.md5sum-http.ip=${hawkbit.artifact.url.protocols.download-http.ip}
hawkbit.artifact.url.protocols.md5sum-http.port=${hawkbit.artifact.url.protocols.download-http.port}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM

View File

@@ -33,6 +33,16 @@
<artifactId>guava</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -9,8 +9,18 @@
package org.eclipse.hawkbit.api;
/**
* Represented the supported protocols for artifact url's.
* hawkBit API type.
*
*/
public enum UrlProtocol {
COAP, HTTP, HTTPS
public enum ApiType {
/**
* Support for Device Management Federation API.
*/
DMF,
/**
* Support for Direct Device Integration API.
*/
DDI;
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
/**
* Container for a generated Artifact URL.
*
*/
public class ArtifactUrl {
private final String protocol;
private final String rel;
private final String ref;
/**
* Constructor.
*
* @param protocol
* string, e.g. ftp, http, https
* @param rel
* hypermedia value
* @param ref
* hypermedia value
*/
public ArtifactUrl(final String protocol, final String rel, final String ref) {
this.protocol = protocol;
this.rel = rel;
this.ref = ref;
}
/**
* @return protocol name used in DMF API messages.
*/
public String getProtocol() {
return protocol;
}
/**
* @return rel links value useful in hypermedia.
*/
public String getRel() {
return rel;
}
/**
* @return generated artifact download URL
*/
public String getRef() {
return ref;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
result = prime * result + ((ref == null) ? 0 : ref.hashCode());
result = prime * result + ((rel == null) ? 0 : rel.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArtifactUrl other = (ArtifactUrl) obj;
if (protocol == null) {
if (other.protocol != null) {
return false;
}
} else if (!protocol.equals(other.protocol)) {
return false;
}
if (ref == null) {
if (other.ref != null) {
return false;
}
} else if (!ref.equals(other.ref)) {
return false;
}
if (rel == null) {
if (other.rel != null) {
return false;
}
} else if (!rel.equals(other.rel)) {
return false;
}
return true;
}
@Override
public String toString() {
return "ArtifactUrl [protocol=" + protocol + ", rel=" + rel + ", ref=" + ref + "]";
}
}

View File

@@ -8,36 +8,26 @@
*/
package org.eclipse.hawkbit.api;
import java.util.List;
/**
* Interface declaration of the {@link ArtifactUrlHandler} which generates the
* URLs to specific artifacts.
*
*/
@FunctionalInterface
public interface ArtifactUrlHandler {
/**
* Returns a generated download URL for a given artifact parameters for a
* specific protocol.
*
* @param controllerId
* the authenticated controller id
* @param softwareModuleId
* the softwareModuleId belonging to the artifact
* @param filename
* the filename of the artifact
* @param sha1Hash
* the sha1Hash of the artifact
* @param protocol
* the protocol the URL should be generated
* @param placeholder
* data for URL generation
* @param api
* given protocol that URL needs to support
*
* @return an URL for the given artifact parameters in a given protocol
*/
String getUrl(String controllerId, final Long softwareModuleId, final String filename, final String sha1Hash,
final UrlProtocol protocol);
/**
* @param protocol
* to check support for
* @return <code>true</code> of the handler supports given protocol.
*/
boolean protocolSupported(UrlProtocol protocol);
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api);
}

View File

@@ -8,90 +8,153 @@
*/
package org.eclipse.hawkbit.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import com.google.common.collect.Lists;
/**
* Artifact handler properties class for holding all supported protocols with
* host, ip, port and download pattern.
*
* @see PropertyBasedArtifactUrlHandler
*/
@ConfigurationProperties("hawkbit.artifact.url")
public class ArtifactUrlHandlerProperties {
private final Http http = new Http();
private final Https https = new Https();
private final Coap coap = new Coap();
public Http getHttp() {
return http;
}
public Https getHttps() {
return https;
}
public Coap getCoap() {
return coap;
}
/**
* Rel as key and complete protocol as value.
*/
private final Map<String, UrlProtocol> protocols = new HashMap<>();
/**
* @param protocol
* the protocol schema to retrieve the properties.
* @return the properties to a protocol or {@code null} if protocol does not
* have properties or protocol not supported
* Protocol specific properties to generate URLs accordingly.
*
*/
public ProtocolProperties getProperties(final String protocol) {
switch (protocol) {
case "http":
return getHttp();
case "https":
return getHttps();
case "coap":
return getCoap();
default:
return null;
}
}
public static class UrlProtocol {
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Http extends DefaultProtocolProperties {
private static final int DEFAULT_HTTP_PORT = 8080;
/**
* Constructor.
* Set to true if enabled.
*/
public Http() {
setPattern(
"{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
}
}
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Https extends DefaultProtocolProperties {
private boolean enabled = true;
/**
* Constructor.
* Hypermedia rel value for this protocol.
*/
public Https() {
setPattern(
"{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
}
}
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Coap extends DefaultProtocolProperties {
private String rel = "download-http";
/**
* Constructor.
* Hypermedia ref pattern for this protocol. Supported place holders are
* protocol,controllerId,targetId,targetIdBase62,ip,port,hostname,
* artifactFileName,artifactSHA1,
* artifactIdBase62,artifactId,tenant,softwareModuleId,
* softwareModuleIdBase62.
*
* The update server itself supports
*/
public Coap() {
setPattern("{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}");
setPort("5683");
private String ref = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* Protocol name placeholder that can be used in ref pattern.
*/
private String protocol = "http";
/**
* Hostname placeholder that can be used in ref pattern.
*/
private String hostname = "localhost";
/**
* IP address placeholder that can be used in ref pattern.
*/
// Exception squid:S1313 - default only, can be configured
@SuppressWarnings("squid:S1313")
private String ip = "127.0.0.1";
/**
* Port placeholder that can be used in ref pattern.
*/
private Integer port = DEFAULT_HTTP_PORT;
/**
* Support for the following hawkBit API.
*/
private List<ApiType> supports = Lists.newArrayList(ApiType.DDI, ApiType.DMF);
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public String getRel() {
return rel;
}
public void setRel(final String rel) {
this.rel = rel;
}
public String getRef() {
return ref;
}
public void setRef(final String ref) {
this.ref = ref;
}
public String getHostname() {
return hostname;
}
public void setHostname(final String hostname) {
this.hostname = hostname;
}
public String getIp() {
return ip;
}
public void setIp(final String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(final Integer port) {
this.port = port;
}
public List<ApiType> getSupports() {
return Collections.unmodifiableList(supports);
}
public void setSupports(final List<ApiType> supports) {
this.supports = Collections.unmodifiableList(supports);
}
public String getProtocol() {
return protocol;
}
public void setProtocol(final String protocol) {
this.protocol = protocol;
}
}
public Map<String, UrlProtocol> getProtocols() {
return protocols;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
/**
* Utility class for Base10 to Base62 conversion and vice versa. Base62 has the
* benefit of being shorter in ASCII representation than Base10.
*/
public final class Base62Util {
private static final String BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int BASE62_BASE = BASE62_ALPHABET.length();
private Base62Util() {
// Utility class
}
/**
* @param base10
* number
* @return converted number into Base62 ASCII string
*/
public static String fromBase10(final long base10) {
if (base10 == 0) {
return "0";
}
long temp = base10;
final StringBuilder sb = new StringBuilder();
while (temp > 0) {
temp = fromBase10(temp, sb);
}
return sb.reverse().toString();
}
/**
* @param base62
* number
* @return converted number into Base10
*/
public static Long toBase10(final String base62) {
return toBase10(new StringBuilder(base62).reverse().toString().toCharArray());
}
private static Long fromBase10(final long base10, final StringBuilder sb) {
final int rem = (int) (base10 % BASE62_BASE);
sb.append(BASE62_ALPHABET.charAt(rem));
return base10 / BASE62_BASE;
}
private static Long toBase10(final char[] chars) {
long base10 = 0L;
for (int i = chars.length - 1; i >= 0; i--) {
base10 += toBase10(BASE62_ALPHABET.indexOf(chars[i]), i);
}
return base10;
}
private static int toBase10(final int n, final int pow) {
return n * (int) Math.pow(BASE62_BASE, pow);
}
}

View File

@@ -1,79 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
/**
* Object to hold the properties for the base protocols.
*/
public class DefaultProtocolProperties implements ProtocolProperties {
// The IP address is not hardcoded. It's the default value, if the IP
// address is not configured.
@SuppressWarnings("squid:S1313")
private static final String DEFAULT_IP_LOCALHOST = "127.0.0.1";
private static final String LOCALHOST = "localhost";
private String hostname = LOCALHOST;
private String ip = DEFAULT_IP_LOCALHOST;
private String port = "";
/**
* An ant-URL pattern with placeholder to build the URL on. The URL can have
* specific artifact placeholder.
*/
private String pattern;
/**
* Enables protocol.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
public String getHostname() {
return hostname;
}
public void setHostname(final String hostname) {
this.hostname = hostname;
}
@Override
public String getIp() {
return ip;
}
public void setIp(final String ip) {
this.ip = ip;
}
@Override
public String getPattern() {
return pattern;
}
public void setPattern(final String urlPattern) {
this.pattern = urlPattern;
}
@Override
public String getPort() {
return port;
}
public void setPort(final String port) {
this.port = port;
}
}

View File

@@ -9,55 +9,80 @@
package org.eclipse.hawkbit.api;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
import com.google.common.base.Strings;
import com.google.common.net.UrlEscapers;
/**
* Implementation for ArtifactUrlHandler for creating urls to download resource
* based on pattern.
* based on patterns configured by {@link ArtifactUrlHandlerProperties}.
*
* This mechanism can be used to generate links to arbitrary file hosting
* infrastructure. However, the hawkBit update server supports hosting files as
* well in the following {@link UrlProtocol#getRef()} patterns:
*
* Default:
* {protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
*
* Default (MD5SUM files):
* {protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
*
*/
@Component
@EnableConfigurationProperties(ArtifactUrlHandlerProperties.class)
public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
private static final String PROTOCOL_PLACEHOLDER = "protocol";
private static final String TARGET_ID_PLACEHOLDER = "targetId";
private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
private static final String TARGET_ID_BASE10_PLACEHOLDER = "targetId";
private static final String TARGET_ID_BASE62_PLACEHOLDER = "targetIdBase62";
private static final String IP_PLACEHOLDER = "ip";
private static final String PORT_PLACEHOLDER = "port";
private static final String HOSTNAME_PLACEHOLDER = "hostname";
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
private static final String ARTIFACT_ID_BASE10_PLACEHOLDER = "artifactId";
private static final String ARTIFACT_ID_BASE62_PLACEHOLDER = "artifactIdBase62";
private static final String TENANT_PLACEHOLDER = "tenant";
private static final String SOFTWARE_MODULE_ID_PLACDEHOLDER = "softwareModuleId";
private static final String TENANT_ID_BASE10_PLACEHOLDER = "tenantId";
private static final String TENANT_ID_BASE62_PLACEHOLDER = "tenantIdBase62";
private static final String SOFTWARE_MODULE_ID_BASE10_PLACDEHOLDER = "softwareModuleId";
private static final String SOFTWARE_MODULE_ID_BASE62_PLACDEHOLDER = "softwareModuleIdBase62";
@Autowired
private ArtifactUrlHandlerProperties urlHandlerProperties;
private final ArtifactUrlHandlerProperties urlHandlerProperties;
@Autowired
private TenantAware tenantAware;
/**
* @param urlHandlerProperties
* for URL generation configuration
*/
public PropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties) {
this.urlHandlerProperties = urlHandlerProperties;
}
@Override
public String getUrl(final String targetId, final Long softwareModuleId, final String filename,
final String sha1Hash, final UrlProtocol protocol) {
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return null;
}
return urlHandlerProperties.getProtocols().entrySet().stream()
.filter(entry -> entry.getValue().getSupports().contains(api))
.filter(entry -> entry.getValue().isEnabled())
.map(entry -> new ArtifactUrl(entry.getValue().getProtocol(), entry.getValue().getRel(),
generateUrl(entry.getValue(), placeholder)))
.collect(Collectors.toList());
}
private static String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder) {
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder).entrySet();
String urlPattern = protocol.getRef();
String urlPattern = properties.getPattern();
final Set<Entry<String, String>> entrySet = getReplaceMap(targetId, softwareModuleId,
UrlEscapers.urlFragmentEscaper().escape(filename), sha1Hash, protocolString, properties).entrySet();
for (final Entry<String, String> entry : entrySet) {
if (entry.getKey().equals(PORT_PLACEHOLDER)) {
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
@@ -69,30 +94,29 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
return urlPattern;
}
private Map<String, String> getReplaceMap(final String targetId, final Long softwareModuleId, final String filename,
final String sha1Hash, final String protocol, final ProtocolProperties properties) {
private static Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder) {
final Map<String, String> replaceMap = new HashMap<>();
replaceMap.put(IP_PLACEHOLDER, properties.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, properties.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, filename);
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, sha1Hash);
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol);
replaceMap.put(PORT_PLACEHOLDER, properties.getPort());
replaceMap.put(TENANT_PLACEHOLDER, tenantAware.getCurrentTenant());
replaceMap.put(TARGET_ID_PLACEHOLDER, targetId);
replaceMap.put(SOFTWARE_MODULE_ID_PLACDEHOLDER, String.valueOf(softwareModuleId));
replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
UrlEscapers.urlFragmentEscaper().escape(placeholder.getSoftwareData().getFilename()));
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
replaceMap.put(PORT_PLACEHOLDER, protocol.getPort() == null ? null : String.valueOf(protocol.getPort()));
replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
replaceMap.put(TENANT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTenantId()));
replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.getControllerId());
replaceMap.put(TARGET_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTargetId()));
replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
replaceMap.put(ARTIFACT_ID_BASE62_PLACEHOLDER,
Base62Util.fromBase10(placeholder.getSoftwareData().getArtifactId()));
replaceMap.put(ARTIFACT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getSoftwareData().getArtifactId()));
replaceMap.put(SOFTWARE_MODULE_ID_BASE10_PLACDEHOLDER,
String.valueOf(placeholder.getSoftwareData().getSoftwareModuleId()));
replaceMap.put(SOFTWARE_MODULE_ID_BASE62_PLACDEHOLDER,
Base62Util.fromBase10(placeholder.getSoftwareData().getSoftwareModuleId()));
return replaceMap;
}
@Override
public boolean protocolSupported(final UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return false;
}
return properties.isEnabled();
}
}

View File

@@ -0,0 +1,247 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
/**
* Container for variables available to the {@link ArtifactUrlHandler}.
*
*/
public class URLPlaceholder {
private final String tenant;
private final Long tenantId;
private final String controllerId;
private final Long targetId;
private final SoftwareData softwareData;
/**
* Constructor.
*
* @param tenant
* of the client
* @param tenantId
* of teh tenant
* @param controllerId
* of the target
* @param targetId
* of the target
* @param softwareData
* information about the artifact and software module that can be
* accessed by the URL.
*/
public URLPlaceholder(final String tenant, final Long tenantId, final String controllerId, final Long targetId,
final SoftwareData softwareData) {
this.tenant = tenant;
this.tenantId = tenantId;
this.controllerId = controllerId;
this.targetId = targetId;
this.softwareData = softwareData;
}
/**
* Information about the artifact and software module that can be accessed
* by the URL.
*
*/
public static class SoftwareData {
private Long softwareModuleId;
private String filename;
private Long artifactId;
private String sha1Hash;
/**
* Constructor.
*
* @param softwareModuleId
* of the module the artifact belongs to
* @param filename
* of the artifact
* @param artifactId
* of the artifact
* @param sha1Hash
* of the artifact
*/
public SoftwareData(final Long softwareModuleId, final String filename, final Long artifactId,
final String sha1Hash) {
this.softwareModuleId = softwareModuleId;
this.filename = filename;
this.artifactId = artifactId;
this.sha1Hash = sha1Hash;
}
public Long getSoftwareModuleId() {
return softwareModuleId;
}
public void setSoftwareModuleId(final Long softwareModuleId) {
this.softwareModuleId = softwareModuleId;
}
public String getFilename() {
return filename;
}
public void setFilename(final String filename) {
this.filename = filename;
}
public Long getArtifactId() {
return artifactId;
}
public void setArtifactId(final Long artifactId) {
this.artifactId = artifactId;
}
public String getSha1Hash() {
return sha1Hash;
}
public void setSha1Hash(final String sha1Hash) {
this.sha1Hash = sha1Hash;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
result = prime * result + ((sha1Hash == null) ? 0 : sha1Hash.hashCode());
result = prime * result + ((softwareModuleId == null) ? 0 : softwareModuleId.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SoftwareData other = (SoftwareData) obj;
if (artifactId == null) {
if (other.artifactId != null) {
return false;
}
} else if (!artifactId.equals(other.artifactId)) {
return false;
}
if (filename == null) {
if (other.filename != null) {
return false;
}
} else if (!filename.equals(other.filename)) {
return false;
}
if (sha1Hash == null) {
if (other.sha1Hash != null) {
return false;
}
} else if (!sha1Hash.equals(other.sha1Hash)) {
return false;
}
if (softwareModuleId == null) {
if (other.softwareModuleId != null) {
return false;
}
} else if (!softwareModuleId.equals(other.softwareModuleId)) {
return false;
}
return true;
}
}
public String getTenant() {
return tenant;
}
public Long getTenantId() {
return tenantId;
}
public String getControllerId() {
return controllerId;
}
public Long getTargetId() {
return targetId;
}
public SoftwareData getSoftwareData() {
return softwareData;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((controllerId == null) ? 0 : controllerId.hashCode());
result = prime * result + ((softwareData == null) ? 0 : softwareData.hashCode());
result = prime * result + ((targetId == null) ? 0 : targetId.hashCode());
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final URLPlaceholder other = (URLPlaceholder) obj;
if (controllerId == null) {
if (other.controllerId != null) {
return false;
}
} else if (!controllerId.equals(other.controllerId)) {
return false;
}
if (softwareData == null) {
if (other.softwareData != null) {
return false;
}
} else if (!softwareData.equals(other.softwareData)) {
return false;
}
if (targetId == null) {
if (other.targetId != null) {
return false;
}
} else if (!targetId.equals(other.targetId)) {
return false;
}
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
if (tenantId == null) {
if (other.tenantId != null) {
return false;
}
} else if (!tenantId.equals(other.tenantId)) {
return false;
}
return true;
}
}

View File

@@ -90,7 +90,7 @@ public enum TenantConfigurationKey {
* @param validator
* Validator which validates, that property is of correct format
*/
private TenantConfigurationKey(final String key, final String defaultKeyName, final Class<?> dataType,
TenantConfigurationKey(final String key, final String defaultKeyName, final Class<?> dataType,
final String defaultValue, final Class<? extends TenantConfigurationValidator> validator) {
this.keyName = key;
this.dataType = dataType;

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
import static org.fest.assertions.api.Assertions.assertThat;
import org.junit.Test;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features("Unit Tests - Artifact URL Handler")
@Stories("Base62 Utility tests")
public class Base62UtilTest {
@Test
@Description("Convert Base10 numbres to Base62 ASCII strings.")
public void fromBase10() {
assertThat(Base62Util.fromBase10(0L)).isEqualTo("0");
assertThat(Base62Util.fromBase10(11L)).isEqualTo("B");
assertThat(Base62Util.fromBase10(36L)).isEqualTo("a");
assertThat(Base62Util.fromBase10(999L)).isEqualTo("G7");
}
@Test
@Description("Convert Base62 ASCII strings to Base10 numbers.")
public void toBase10() {
assertThat(Base62Util.toBase10("0")).isEqualTo(0L);
assertThat(Base62Util.toBase10("B")).isEqualTo(11);
assertThat(Base62Util.toBase10("a")).isEqualTo(36L);
assertThat(Base62Util.toBase10("G7")).isEqualTo(999L);
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.api;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Tests for creating urls to download artifacts.
*/
@Features("Unit Tests - Artifact URL Handler")
@Stories("Test to generate the artifact download URL")
@RunWith(MockitoJUnitRunner.class)
public class PropertyBasedArtifactUrlHandlerTest {
private static final String TEST_PROTO = "coap";
private static final String TEST_REL = "download-udp";
private static final long TENANT_ID = 456789L;
private static final String CONTROLLER_ID = "Test";
private static final String FILENAME = "Afile1234";
private static final long SOFTWAREMODULEID = 87654L;
private static final long TARGETID = 3474366L;
private static final String TARGETID_BASE62 = "EZqA";
private static final String SHA1HASH = "test12345";
private static final long ARTIFACTID = 1345678L;
private static final String ARTIFACTID_BASE62 = "5e4U";
private static final String TENANT = "TEST_TENANT";
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
private ArtifactUrlHandler urlHandlerUnderTest;
private ArtifactUrlHandlerProperties properties;
private static URLPlaceholder placeholder = new URLPlaceholder(TENANT, TENANT_ID, CONTROLLER_ID, TARGETID,
new SoftwareData(SOFTWAREMODULEID, FILENAME, ARTIFACTID, SHA1HASH));
@Before
public void setup() {
properties = new ArtifactUrlHandlerProperties();
urlHandlerUnderTest = new PropertyBasedArtifactUrlHandler(properties);
}
@Test
@Description("Tests the generation of http download url.")
public void urlGenerationWithDefaultConfiguration() {
properties.getProtocols().put("download-http", new UrlProtocol());
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertEquals(
Lists.newArrayList(new ArtifactUrl("http", "download-http", HTTP_LOCALHOST + TENANT + "/controller/v1/"
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
ddiUrls);
final List<ArtifactUrl> dmfUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(ddiUrls, dmfUrls);
}
@Test
@Description("Tests the generation of custom download url with a CoAP example that supports DMF only.")
public void urlGenerationWithCustomConfiguration() {
final UrlProtocol proto = new UrlProtocol();
proto.setIp("127.0.0.1");
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
proto.setSupports(Lists.newArrayList(ApiType.DMF));
proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactSHA1}");
properties.getProtocols().put(TEST_PROTO, proto);
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertThat(urls).isEmpty();
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO, TEST_REL,
"coap://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + SHA1HASH)), urls);
}
@Test
@Description("Tests the generation of custom download url using Base62 references with a CoAP example that supports DMF only.")
public void urlGenerationWithCustomShortConfiguration() {
final UrlProtocol proto = new UrlProtocol();
proto.setIp("127.0.0.1");
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
proto.setSupports(Lists.newArrayList(ApiType.DMF));
proto.setRef("{protocol}://{ip}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
properties.getProtocols().put("ftp", proto);
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertThat(urls).isEmpty();
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO, TEST_REL,
TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGETID_BASE62 + "/" + ARTIFACTID_BASE62)),
urls);
}
}

View File

@@ -25,7 +25,6 @@ public class DdiCancelActionToStop {
* ID of the action to be stoppedW
*/
public DdiCancelActionToStop(final String stopId) {
super();
this.stopId = stopId;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -65,7 +66,11 @@ public class DdiChunk {
}
public List<DdiArtifact> getArtifacts() {
return artifacts;
if (artifacts == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(artifacts);
}
}

View File

@@ -30,7 +30,6 @@ public class DdiConfig {
* configuration of the SP target
*/
public DdiConfig(final DdiPolling polling) {
super();
this.polling = polling;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonValue;
@@ -41,7 +42,6 @@ public class DdiDeployment {
* to handle.
*/
public DdiDeployment(final HandlingType download, final HandlingType update, final List<DdiChunk> chunks) {
super();
this.download = download;
this.update = update;
this.chunks = chunks;
@@ -56,7 +56,11 @@ public class DdiDeployment {
}
public List<DdiChunk> getChunks() {
return chunks;
if (chunks == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(chunks);
}
/**
@@ -81,7 +85,7 @@ public class DdiDeployment {
private String name;
private HandlingType(final String name) {
HandlingType(final String name) {
this.name = name;
}

View File

@@ -71,7 +71,7 @@ public class DdiResult {
private String name;
private FinalResult(final String name) {
FinalResult(final String name) {
this.name = name;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -57,7 +58,11 @@ public class DdiStatus {
}
public List<String> getDetails() {
return details;
if (details == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(details);
}
/**
@@ -98,7 +103,7 @@ public class DdiStatus {
private String name;
private ExecutionStatus(final String name) {
ExecutionStatus(final String name) {
this.name = name;
}

View File

@@ -39,17 +39,17 @@ public interface DdiRootControllerRestApi {
* Returns all artifacts of a given software module and target.
*
* @param tenant
* of the request
* @param targetid
* of the client
* @param controllerId
* of the target that matches to controller id
* @param softwareModuleId
* of the software module
* @return the response
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts", produces = {
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid,
ResponseEntity<List<DdiArtifact>> getSoftwareModulesArtifacts(@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId);
/**
@@ -57,16 +57,16 @@ public interface DdiRootControllerRestApi {
*
* @param tenant
* of the request
* @param targetid
* @param controllerId
* of the target that matches to controller id
* @param request
* the HTTP request injected by spring
* @return the response
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}", produces = { "application/hal+json",
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid);
@PathVariable("controllerId") final String controllerId);
/**
* Handles GET {@link DdiArtifact} download request. This could be full or
@@ -74,8 +74,8 @@ public interface DdiRootControllerRestApi {
*
* @param tenant
* of the request
* @param targetid
* of the related target
* @param controllerId
* of the target
* @param softwareModuleId
* of the parent software module
* @param fileName
@@ -89,9 +89,9 @@ public interface DdiRootControllerRestApi {
* {@link HttpStatus#OK} or in case of partial download
* {@link HttpStatus#PARTIAL_CONTENT}.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}")
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{fileName}")
ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName);
@@ -100,8 +100,8 @@ public interface DdiRootControllerRestApi {
*
* @param tenant
* of the request
* @param targetid
* of the related target
* @param controllerId
* of the target
* @param softwareModuleId
* of the parent software module
* @param fileName
@@ -114,10 +114,10 @@ public interface DdiRootControllerRestApi {
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}"
@RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{fileName}"
+ DdiRestConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = MediaType.TEXT_PLAIN_VALUE)
ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName);
@@ -126,8 +126,8 @@ public interface DdiRootControllerRestApi {
*
* @param tenant
* of the request
* @param targetid
* of the target that matches to controller id
* @param controllerId
* of the target
* @param actionId
* of the {@link DdiDeploymentBase} that matches to active
* actions.
@@ -139,10 +139,10 @@ public interface DdiRootControllerRestApi {
* the HTTP request injected by spring
* @return the response
*/
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION
@RequestMapping(value = "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION
+ "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource);
@@ -150,10 +150,10 @@ public interface DdiRootControllerRestApi {
* This is the feedback channel for the {@link DdiDeploymentBase} action.
*
* @param tenant
* of the request
* of the client
* @param feedback
* to provide
* @param targetid
* @param controllerId
* of the target that matches to controller id
* @param actionId
* of the action we have feedback for
@@ -162,37 +162,37 @@ public interface DdiRootControllerRestApi {
*
* @return the response
*/
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/"
@RequestMapping(value = "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/"
+ DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid,
ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId);
/**
* This is the feedback channel for the config data action.
*
* @param tenant
* of the request
* of the client
* @param configData
* as body
* @param targetid
* @param controllerId
* to provide data for
* @param request
* the HTTP request injected by spring
*
* @return status of the request
*/
@RequestMapping(value = "/{targetid}/"
@RequestMapping(value = "/{controllerId}/"
+ DdiRestConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant,
@Valid final DdiConfigData configData, @PathVariable("targetid") final String targetid);
ResponseEntity<Void> putConfigData(@Valid final DdiConfigData configData,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId);
/**
* RequestMethod.GET method for the {@link DdiCancel} action.
*
* @param tenant
* of the request
* @param targetid
* @param controllerId
* ID of the calling target
* @param actionId
* of the action
@@ -201,10 +201,10 @@ public interface DdiRootControllerRestApi {
*
* @return the {@link DdiCancel} response
*/
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.CANCEL_ACTION
@RequestMapping(value = "/{controllerId}/" + DdiRestConstants.CANCEL_ACTION
+ "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId);
/**
@@ -212,10 +212,10 @@ public interface DdiRootControllerRestApi {
* the target.
*
* @param tenant
* of the request
* of the client
* @param feedback
* the {@link DdiActionFeedback} from the target.
* @param targetid
* @param controllerId
* the ID of the calling target
* @param actionId
* of the action we have feedback for
@@ -225,10 +225,11 @@ public interface DdiRootControllerRestApi {
* @return the {@link DdiActionFeedback} response
*/
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.CANCEL_ACTION + "/{actionId}/"
@RequestMapping(value = "/{controllerId}/" + DdiRestConstants.CANCEL_ACTION + "/{actionId}/"
+ DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") @NotEmpty final String targetid,
ResponseEntity<Void> postCancelActionFeedback(@Valid final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId);
}

View File

@@ -12,7 +12,7 @@ import java.io.InputStream;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -27,7 +27,9 @@ public interface DdiDlArtifactStoreControllerRestApi {
/**
* Handles GET download request. This could be full or partial download
* request.
*
*
* @param tenant
* name of the client
* @param fileName
* to search for
* @param targetid
@@ -40,12 +42,14 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME
+ "/{fileName}")
@ResponseBody
public ResponseEntity<InputStream> downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
ResponseEntity<InputStream> downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName, @AuthenticationPrincipal final String targetid);
/**
* Handles GET MD5 checksum file download request.
*
* @param tenant
* name of the client
* @param fileName
* to search for
*
@@ -54,7 +58,7 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME + "/{fileName}"
+ DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX)
@ResponseBody
public ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName);
}

View File

@@ -18,8 +18,10 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol;
import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlRestConstants;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifactHash;
@@ -28,6 +30,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiConfig;
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
import org.eclipse.hawkbit.ddi.json.model.DdiPolling;
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
@@ -45,11 +48,11 @@ public final class DataConversionHelper {
}
static List<DdiChunk> createChunks(final String targetid, final Action uAction,
final ArtifactUrlHandler artifactUrlHandler) {
static List<DdiChunk> createChunks(final Target target, final Action uAction,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
return uAction.getDistributionSet().getModules().stream()
.map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
module.getName(), createArtifacts(targetid, module, artifactUrlHandler)))
module.getName(), createArtifacts(target, module, artifactUrlHandler, systemManagement)))
.collect(Collectors.toList());
}
@@ -68,43 +71,42 @@ public final class DataConversionHelper {
/**
* Creates all (rest) artifacts for a given software module.
*
* @param targetid
* of the target
* @param target
* for create URLs for
* @param module
* the software module
* @param artifactUrlHandler
* for creating download URLs
* @param systemManagement
* for access to tenant meta data
* @return a list of artifacts or a empty list. Cannot be <null>.
*/
public static List<DdiArtifact> createArtifacts(final String targetid,
public static List<DdiArtifact> createArtifacts(final Target target,
final org.eclipse.hawkbit.repository.model.SoftwareModule module,
final ArtifactUrlHandler artifactUrlHandler) {
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
return module.getLocalArtifacts().stream()
.map(artifact -> createArtifact(targetid, artifactUrlHandler, artifact)).collect(Collectors.toList());
.map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement))
.collect(Collectors.toList());
}
private static DdiArtifact createArtifact(final String targetid, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact) {
private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact, final SystemManagement systemManagement) {
final DdiArtifact file = new DdiArtifact();
file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
file.setFilename(artifact.getFilename());
file.setSize(artifact.getSize());
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP);
file.add(new Link(linkHttp).withRel("download-http"));
file.add(new Link(linkHttp + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http"));
}
artifactUrlHandler
.getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(),
artifact.getSha1Hash())),
ApiType.DDI)
.forEach(entry -> file.add(new Link(entry.getRef()).withRel(entry.getRel())));
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
final String linkHttps = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTPS);
file.add(new Link(linkHttps).withRel("download"));
file.add(new Link(linkHttps + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum"));
}
return file;
}
static DdiControllerBase fromTarget(final Target target, final Optional<Action> action,
@@ -132,8 +134,8 @@ public final class DataConversionHelper {
}
if (target.getTargetInfo().isRequestControllerAttributes()) {
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
.putConfigData(tenantAware.getCurrentTenant(), null, target.getControllerId()))
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()).putConfigData(null,
tenantAware.getCurrentTenant(), target.getControllerId()))
.withRel(DdiRestConstants.CONFIG_DATA_ACTION));
}
return result;

View File

@@ -33,6 +33,7 @@ import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -88,6 +89,9 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Autowired
private TenantAware tenantAware;
@Autowired
private SystemManagement systemManagement;
@Autowired
private ArtifactUrlHandler artifactUrlHandler;
@@ -99,9 +103,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
public ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId) {
LOG.debug("getSoftwareModulesArtifacts({})", targetid);
LOG.debug("getSoftwareModulesArtifacts({})", controllerId);
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -111,20 +118,21 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
return new ResponseEntity<>(DataConversionHelper.createArtifacts(targetid, softwareModule, artifactUrlHandler),
return new ResponseEntity<>(
DataConversionHelper.createArtifacts(target, softwareModule, artifactUrlHandler, systemManagement),
HttpStatus.OK);
}
@Override
public ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid) {
LOG.debug("getControllerBase({})", targetid);
@PathVariable("controllerId") final String controllerId) {
LOG.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid, IpUtil
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (target.getTargetInfo().getUpdateStatus() == TargetUpdateStatus.UNKNOWN) {
LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", targetid);
LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", controllerId);
controllerManagement.updateTargetStatus(target.getTargetInfo(), TargetUpdateStatus.REGISTERED,
System.currentTimeMillis(), IpUtil.getClientIpFromRequest(
requestResponseContextHolder.getHttpServletRequest(), securityProperties));
@@ -138,12 +146,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
public ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName) {
ResponseEntity<InputStream> result;
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -205,10 +213,10 @@ public class DdiRootController implements DdiRootControllerRestApi {
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil
controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -231,12 +239,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
public ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource) {
LOG.debug("getControllerBasedeploymentAction({},{})", targetid, resource);
LOG.debug("getControllerBasedeploymentAction({},{})", controllerId, resource);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -247,14 +255,15 @@ public class DdiRootController implements DdiRootControllerRestApi {
if (!action.isCancelingOrCanceled()) {
final List<DdiChunk> chunks = DataConversionHelper.createChunks(targetid, action, artifactUrlHandler);
final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
systemManagement);
final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()),
new DdiDeployment(handlingType, handlingType, chunks));
LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", targetid, base);
LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", controllerId, base);
controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX
+ "Target retrieved update action and should start now the download.");
@@ -266,12 +275,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid,
public ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", targetid, actionId, feedback);
LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", controllerId, actionId, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (!actionId.equals(feedback.getId())) {
@@ -293,13 +302,14 @@ public class DdiRootController implements DdiRootControllerRestApi {
return new ResponseEntity<>(HttpStatus.GONE);
}
controllerManagement.addUpdateActionStatus(generateUpdateStatus(feedback, targetid, feedback.getId(), action));
controllerManagement
.addUpdateActionStatus(generateUpdateStatus(feedback, controllerId, feedback.getId(), action));
return new ResponseEntity<>(HttpStatus.OK);
}
private ActionStatus generateUpdateStatus(final DdiActionFeedback feedback, final String targetid,
private ActionStatus generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final Action action) {
final ActionStatus actionStatus = entityFactory.generateActionStatus();
@@ -308,22 +318,22 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) {
case CANCELED:
LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.debug("Controller confirmed cancel (actionid: {}, controllerId: {}) as we got {} report.", actionid,
controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.CANCELED);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed cancelation.");
break;
case REJECTED:
LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.info("Controller reported internal error (actionid: {}, controllerId: {}) as we got {} report.",
actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target REJECTED update.");
break;
case CLOSED:
handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus);
handleClosedUpdateStatus(feedback, controllerId, actionid, actionStatus);
break;
default:
handleDefaultUpdateStatus(feedback, targetid, actionid, actionStatus);
handleDefaultUpdateStatus(feedback, controllerId, actionid, actionStatus);
break;
}
@@ -339,19 +349,19 @@ public class DdiRootController implements DdiRootControllerRestApi {
return actionStatus;
}
private static void handleDefaultUpdateStatus(final DdiActionFeedback feedback, final String targetid,
private static void handleDefaultUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final ActionStatus actionStatus) {
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.debug("Controller reported intermediate status (actionid: {}, controllerId: {}) as we got {} report.",
actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage(
RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported " + feedback.getStatus().getExecution());
}
private static void handleClosedUpdateStatus(final DdiActionFeedback feedback, final String targetid,
private static void handleClosedUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final ActionStatus actionStatus) {
LOG.debug("Controller reported closed (actionid: {}, targetid: {}) as we got {} report.", actionid, targetid,
feedback.getStatus().getExecution());
LOG.debug("Controller reported closed (actionid: {}, controllerId: {}) as we got {} report.", actionid,
controllerId, feedback.getStatus().getExecution());
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with ERROR!");
@@ -362,23 +372,23 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiConfigData configData, @PathVariable("targetid") final String targetid) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil
public ResponseEntity<Void> putConfigData(@Valid @RequestBody final DdiConfigData configData,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) {
controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
controllerManagement.updateControllerAttributes(targetid, configData.getData());
controllerManagement.updateControllerAttributes(controllerId, configData.getData());
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
public ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("getControllerCancelAction({})", targetid);
LOG.debug("getControllerCancelAction({})", controllerId);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -391,7 +401,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
final DdiCancel cancel = new DdiCancel(String.valueOf(action.getId()),
new DdiCancelActionToStop(String.valueOf(action.getId())));
LOG.debug("Found an active CancelAction for target {}. returning cancel: {}", targetid, cancel);
LOG.debug("Found an active CancelAction for target {}. returning cancel: {}", controllerId, cancel);
controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX
+ "Target retrieved cancel action and should start now the cancelation.");
@@ -403,13 +413,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("targetid") @NotEmpty final String targetid,
public ResponseEntity<Void> postCancelActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("provideCancelActionFeedback for target [{}]: {}", targetid, feedback);
LOG.debug("provideCancelActionFeedback for target [{}]: {}", controllerId, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (!actionId.equals(feedback.getId())) {
@@ -440,13 +450,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) {
case CANCELED:
LOG.error(
"Controller reported cancel for a cancel which is not supported by the server (actionid: {}, targetid: {}) as we got {} report.",
"Controller reported cancel for a cancel which is not supported by the server (actionid: {}, controllerId: {}) as we got {} report.",
actionid, target.getControllerId(), feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING);
break;
case REJECTED:
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, targetid: {}).", actionid,
target.getControllerId());
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, controllerId: {}).",
actionid, target.getControllerId());
actionStatus.setStatus(Status.WARNING);
break;
case CLOSED:

View File

@@ -61,8 +61,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("Deployment Action Resource")
public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoDB {
private static final String HTTP_LOCALHOST = "http://localhost/";
private static final String HTTPS_LOCALHOST = "https://localhost/";
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
@Test()
@Description("Ensures that artifacts are not found, when softare module does not exists.")
@@ -174,25 +173,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -203,27 +191,17 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
contains("test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.md5",
contains(artifactSignature.getMd5Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -359,16 +337,18 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024)))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename",
contains("test1.signature")))
@@ -377,24 +357,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -486,31 +456,22 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].filename", contains("test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.md5",
contains(artifact.getMd5Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024)))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename",
contains("test1.signature")))
@@ -519,25 +480,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()

View File

@@ -0,0 +1,43 @@
#
# Copyright (c) 2015 Bosch Software Innovations GmbH and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value}
spring.data.mongodb.port=28017
hawkbit.server.ddi.security.authentication.header.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.name=TestToken
multipart.max-file-size=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
spring.jpa.database=H2
spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
flyway.enabled=true
flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
#spring.jpa.show-sql=true
# DDI configuration
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
hawkbit.artifact.url.protocols[0].rel=download
hawkbit.artifact.url.protocols[0].protocol=http
hawkbit.artifact.url.protocols[0].supports=DMF,DDI
hawkbit.artifact.url.protocols[0].ref={protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols[1].rel=md5sum
hawkbit.artifact.url.protocols[1].protocol=${hawkbit.artifact.url.protocols[0].protocol}
hawkbit.artifact.url.protocols[1].supports=${hawkbit.artifact.url.protocols[0].supports}
hawkbit.artifact.url.protocols[1].ref=${hawkbit.artifact.url.protocols[0].ref}.MD5SUM

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.amqp;
import java.net.URISyntaxException;
import java.util.UUID;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
* {@link AmqpMessageHandlerService} handles all incoming target authentication
* AMQP messages that can be used by 3rd party CDN services to check if a target
* is permitted to download certain artifact. This is handled by the queue that
* is configured for the property
* hawkbit.dmf.rabbitmq.authenticationReceiverQueue.
*
*/
public class AmqpAuthenticationMessageHandler extends BaseAmqpService {
private static final Logger LOG = LoggerFactory.getLogger(AmqpAuthenticationMessageHandler.class);
private final AmqpControllerAuthentication authenticationManager;
private final ArtifactManagement artifactManagement;
private final Cache cache;
private final HostnameResolver hostnameResolver;
private final ControllerManagement controllerManagement;
/**
* @param rabbitTemplate
* the configured amqp template.
* @param artifactManagement
* for artifact URI generation
* @param cache
* for download Ids
* @param hostnameResolver
* for resolving the host for downloads
* @param authenticationManager
* for target authentication
* @param controllerManagement
* for target repo access
*/
public AmqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
final Cache cache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement) {
super(rabbitTemplate);
this.authenticationManager = authenticationManager;
this.artifactManagement = artifactManagement;
this.cache = cache;
this.hostnameResolver = hostnameResolver;
this.controllerManagement = controllerManagement;
}
/**
* Executed on a authentication request.
*
* @param message
* the amqp message
* @return the rpc message back to supplier.
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory")
public Message onAuthenticationRequest(final Message message) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
return handleAuthenticationMessage(message);
} catch (final RuntimeException ex) {
throw new AmqpRejectAndDontRequeueException(ex);
} finally {
SecurityContextHolder.setContext(oldContext);
}
}
/**
* check action for this download purposes, the method will throw an
* EntityNotFoundException in case the controller is not allowed to download
* this file because it's not assigned to an action and not assigned to this
* controller. Otherwise no controllerId is set = anonymous download
*
* @param secruityToken
* the security token which holds the target ID to check on
* @param localArtifact
* the local artifact to verify if the given target is allowed to
* download this artifact
*/
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken,
final LocalArtifact localArtifact) {
if (secruityToken.getControllerId() != null) {
checkByControllerId(localArtifact, secruityToken.getControllerId());
} else if (secruityToken.getTargetId() != null) {
checkByTargetId(localArtifact, secruityToken.getTargetId());
} else {
LOG.info("anonymous download no authentication check for artifact {}", localArtifact);
return;
}
}
private void checkByTargetId(final LocalArtifact localArtifact, final Long targetId) {
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}", targetId,
localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(targetId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", targetId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", targetId, localArtifact);
}
private void checkByControllerId(final LocalArtifact localArtifact, final String controllerId) {
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", controllerId, localArtifact);
}
private LocalArtifact findLocalArtifactByFileResource(final FileResource fileResource) {
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstLocalArtifactsBySHA1(fileResource.getSha1());
} else if (fileResource.getFilename() != null) {
return artifactManagement.findLocalArtifactByFilename(fileResource.getFilename()).stream().findFirst()
.orElse(null);
} else if (fileResource.getArtifactId() != null) {
return artifactManagement.findLocalArtifact(fileResource.getArtifactId());
} else if (fileResource.getSoftwareModuleFilenameResource() != null) {
return artifactManagement
.findByFilenameAndSoftwareModule(fileResource.getSoftwareModuleFilenameResource().getFilename(),
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId())
.stream().findFirst().orElse(null);
}
return null;
}
private static Artifact convertDbArtifact(final DbArtifact dbArtifact) {
final Artifact artifact = new Artifact();
artifact.setSize(dbArtifact.getSize());
final DbArtifactHash dbArtifactHash = dbArtifact.getHashes();
artifact.setHashes(new ArtifactHash(dbArtifactHash.getSha1(), dbArtifactHash.getMd5()));
return artifact;
}
private Message handleAuthenticationMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class);
final FileResource fileResource = secruityToken.getFileResource();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = findLocalArtifactByFileResource(fileResource);
if (localArtifact == null) {
LOG.info("target {} requested file resource {} which does not exists to download",
secruityToken.getControllerId(), fileResource);
throw new EntityNotFoundException();
}
checkIfArtifactIsAssignedToTarget(secruityToken, localArtifact);
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
throw new EntityNotFoundException();
}
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1,
localArtifact.getSha1Hash());
cache.put(downloadId, downloadCache);
authentificationResponse
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
authentificationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
LOG.error("Login failed", e);
authentificationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
authentificationResponse.setMessage("Login failed");
} catch (final URISyntaxException e) {
LOG.error("URI build exception", e);
authentificationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
authentificationResponse.setMessage("Building download URI failed");
} catch (final EntityNotFoundException e) {
final String errorMessage = "Artifact for resource " + fileResource + "not found ";
LOG.warn(errorMessage, e);
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authentificationResponse.setMessage(errorMessage);
}
return getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
}

View File

@@ -14,8 +14,13 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -40,6 +45,7 @@ import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
@@ -49,7 +55,7 @@ import org.springframework.util.ErrorHandler;
import com.google.common.collect.Maps;
/**
* Spring configuration for AMQP 0.9 based DMF communication for indirect device
* Spring configuration for AMQP based DMF communication for indirect device
* integration.
*
*/
@@ -252,17 +258,51 @@ public class AmqpConfiguration {
}
/**
* Create amqp handler service bean.
* Create AMQP handler service bean.
*
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService
* to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
*
* @return handler service bean
*/
@Bean
public AmqpMessageHandlerService amqpMessageHandlerService(
final AmqpMessageDispatcherService amqpMessageDispatcherService) {
return new AmqpMessageHandlerService(rabbitTemplate(), amqpMessageDispatcherService);
public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
entityFactory);
}
/**
* Create AMQP handler service bean for authentication messages.
*
* @param rabbitTemplate
* for converting messages
* @param authenticationManager
* for target authentication
* @param artifactManagement
* for artifact URI generation
* @param cache
* for download IDs
* @param hostnameResolver
* for resolving the host for downloads
* @param controllerManagement
* for target repo access
* @return handler service bean
*/
@Bean
public AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE) final Cache cache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement) {
return new AmqpAuthenticationMessageHandler(rabbitTemplate, authenticationManager, artifactManagement, cache,
hostnameResolver, controllerManagement);
}
/**
@@ -292,18 +332,21 @@ public class AmqpConfiguration {
@Bean
@ConditionalOnMissingBean(AmqpControllerAuthentication.class)
public AmqpControllerAuthentication amqpControllerAuthentication(final ControllerManagement controllerManagement,
public AmqpControllerAuthentication amqpControllerAuthentication(final SystemManagement systemManagement,
final ControllerManagement controllerManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
return new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement, tenantAware,
ddiSecruityProperties, systemSecurityContext);
return new AmqpControllerAuthentication(systemManagement, controllerManagement, tenantConfigurationManagement,
tenantAware, ddiSecruityProperties, systemSecurityContext);
}
@Bean
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
public AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler);
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemSecurityContext, systemManagement);
}
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {

View File

@@ -8,7 +8,6 @@
*/
package org.eclipse.hawkbit.amqp;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
@@ -16,6 +15,7 @@ import javax.annotation.PostConstruct;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousDownload;
@@ -32,6 +32,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import com.google.common.collect.Lists;
/**
*
* A controller which handles the DMF AMQP authentication.
@@ -42,10 +44,12 @@ public class AmqpControllerAuthentication {
private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider();
private final List<PreAuthentificationFilter> filterChain = new ArrayList<>();
private List<PreAuthentificationFilter> filterChain;
private final ControllerManagement controllerManagement;
private final SystemManagement systemManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final TenantAware tenantAware;
@@ -57,6 +61,7 @@ public class AmqpControllerAuthentication {
/**
* Constructor.
*
* @param systemManagement
* @param controllerManagement
* @param tenantConfigurationManagement
* @param tenantAware
@@ -66,10 +71,12 @@ public class AmqpControllerAuthentication {
* @param systemSecurityContext
* security context
*/
public AmqpControllerAuthentication(final ControllerManagement controllerManagement,
public AmqpControllerAuthentication(final SystemManagement systemManagement,
final ControllerManagement controllerManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
this.controllerManagement = controllerManagement;
this.systemManagement = systemManagement;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.tenantAware = tenantAware;
this.ddiSecruityProperties = ddiSecruityProperties;
@@ -85,6 +92,8 @@ public class AmqpControllerAuthentication {
}
private void addFilter() {
filterChain = Lists.newArrayListWithExpectedSize(5);
final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
filterChain.add(gatewaySecurityTokenFilter);
@@ -106,13 +115,15 @@ public class AmqpControllerAuthentication {
}
/**
* Performs authentication with the secruity token.
* Performs authentication with the security token.
*
* @param secruityToken
* the authentication request object
* @return the authentfication object
* @return the authentication object
*/
public Authentication doAuthenticate(final TenantSecurityToken secruityToken) {
resolveTenant(secruityToken);
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null);
for (final PreAuthentificationFilter filter : filterChain) {
final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, secruityToken);
@@ -126,6 +137,14 @@ public class AmqpControllerAuthentication {
}
private void resolveTenant(final TenantSecurityToken securityToken) {
if (securityToken.getTenant() == null) {
securityToken.setTenant(systemSecurityContext
.runAsSystem(() -> systemManagement.getTenantMetadata(securityToken.getTenantId()).getTenant()));
}
}
private static PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthentificationFilter filter,
final TenantSecurityToken secruityToken) {

View File

@@ -14,8 +14,10 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol;
import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
@@ -25,8 +27,11 @@ import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.eventbus.EventSubscriber;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
@@ -47,6 +52,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private final ArtifactUrlHandler artifactUrlHandler;
private final AmqpSenderService amqpSenderService;
private final SystemSecurityContext systemSecurityContext;
private final SystemManagement systemManagement;
/**
* Constructor.
@@ -57,12 +64,19 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* to send AMQP message
* @param artifactUrlHandler
* for generating download URLs
* @param systemSecurityContext
* for execution with system permissions
* @param systemManagement
* to access to tenant metadata
*/
public AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpSenderService amqpSenderService,
final ArtifactUrlHandler artifactUrlHandler) {
final ArtifactUrlHandler artifactUrlHandler, final SystemSecurityContext systemSecurityContext,
final SystemManagement systemManagement) {
super(rabbitTemplate);
this.artifactUrlHandler = artifactUrlHandler;
this.amqpSenderService = amqpSenderService;
this.systemSecurityContext = systemSecurityContext;
this.systemManagement = systemManagement;
}
/**
@@ -74,20 +88,24 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
*/
@Subscribe
public void targetAssignDistributionSet(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
final URI targetAdress = targetAssignDistributionSetEvent.getTargetAdress();
final URI targetAdress = targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress();
if (!IpUtil.isAmqpUri(targetAdress)) {
return;
}
final String controllerId = targetAssignDistributionSetEvent.getControllerId();
final String controllerId = targetAssignDistributionSetEvent.getTarget().getControllerId();
final Collection<org.eclipse.hawkbit.repository.model.SoftwareModule> modules = targetAssignDistributionSetEvent
.getSoftwareModules();
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId());
downloadAndUpdateRequest.setTargetSecurityToken(targetAssignDistributionSetEvent.getTargetToken());
final String targetSecurityToken = systemSecurityContext
.runAsSystem(targetAssignDistributionSetEvent.getTarget()::getSecurityToken);
downloadAndUpdateRequest.setTargetSecurityToken(targetSecurityToken);
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule);
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(
targetAssignDistributionSetEvent.getTarget(), softwareModule);
downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule);
}
@@ -133,51 +151,42 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return messageProperties;
}
private SoftwareModule convertToAmqpSoftwareModule(final String targetId,
private SoftwareModule convertToAmqpSoftwareModule(final Target target,
final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule) {
final SoftwareModule amqpSoftwareModule = new SoftwareModule();
amqpSoftwareModule.setModuleId(softwareModule.getId());
amqpSoftwareModule.setModuleType(softwareModule.getType().getKey());
amqpSoftwareModule.setModuleVersion(softwareModule.getVersion());
final List<Artifact> artifacts = convertArtifacts(targetId, softwareModule.getLocalArtifacts());
final List<Artifact> artifacts = convertArtifacts(target, softwareModule.getLocalArtifacts());
amqpSoftwareModule.setArtifacts(artifacts);
return amqpSoftwareModule;
}
private List<Artifact> convertArtifacts(final String targetId, final List<LocalArtifact> localArtifacts) {
private List<Artifact> convertArtifacts(final Target target, final List<LocalArtifact> localArtifacts) {
if (localArtifacts.isEmpty()) {
return Collections.emptyList();
}
return localArtifacts.stream().map(localArtifact -> convertArtifact(targetId, localArtifact))
return localArtifacts.stream().map(localArtifact -> convertArtifact(target, localArtifact))
.collect(Collectors.toList());
}
private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) {
private Artifact convertArtifact(final Target target, final LocalArtifact localArtifact) {
final Artifact artifact = new Artifact();
if (artifactUrlHandler.protocolSupported(UrlProtocol.COAP)) {
artifact.getUrls().put(Artifact.UrlProtocol.COAP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS));
}
artifact.setUrls(artifactUrlHandler
.getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
localArtifact.getId(), localArtifact.getSha1Hash())),
ApiType.DMF)
.stream().collect(Collectors.toMap(e -> e.getProtocol(), e -> e.getRef())));
artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
artifact.setSize(localArtifact.getSize());
return artifact;
}
}

View File

@@ -9,7 +9,6 @@
package org.eclipse.hawkbit.amqp;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@@ -18,68 +17,45 @@ import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.exception.TooManyStatusEntriesException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
* {@link AmqpMessageHandlerService} handles all incoming AMQP messages for the
* queue which is configure for the property hawkbit.dmf.rabbitmq.receiverQueue.
* {@link AmqpMessageHandlerService} handles all incoming target interaction
* AMQP messages (e.g. create target, check for updates etc.) for the queue
* which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
*
*/
public class AmqpMessageHandlerService extends BaseAmqpService {
@@ -88,40 +64,29 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
@Autowired
private ControllerManagement controllerManagement;
private final ControllerManagement controllerManagement;
@Autowired
private AmqpControllerAuthentication authenticationManager;
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE)
private Cache cache;
@Autowired
private HostnameResolver hostnameResolver;
@Autowired
private EntityFactory entityFactory;
@Autowired
private SystemSecurityContext systemSecurityContext;
private final EntityFactory entityFactory;
/**
* Constructor.
*
* @param defaultTemplate
* the configured amqp template.
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService
* to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
*/
public AmqpMessageHandlerService(final RabbitTemplate defaultTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService) {
super(defaultTemplate);
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
super(rabbitTemplate);
this.amqpMessageDispatcherService = amqpMessageDispatcherService;
this.controllerManagement = controllerManagement;
this.entityFactory = entityFactory;
}
/**
@@ -142,28 +107,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
}
/**
* Executed on a authentication request.
*
* @param message
* the amqp message
* @return the rpc message back to supplier.
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory")
public Message onAuthenticationRequest(final Message message) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
return handleAuthentifiactionMessage(message);
} catch (final IllegalArgumentException ex) {
throw new AmqpRejectAndDontRequeueException("Invalid message!", ex);
} catch (final TenantNotExistException | TooManyStatusEntriesException e) {
throw new AmqpRejectAndDontRequeueException(e);
} finally {
SecurityContextHolder.setContext(oldContext);
}
}
/**
* * Executed if a amqp message arrives.
*
@@ -206,108 +149,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return null;
}
private Message handleAuthentifiactionMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class);
final FileResource fileResource = secruityToken.getFileResource();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = findLocalArtifactByFileResource(fileResource);
if (localArtifact == null) {
LOG.info("target {} requested file resource {} which does not exists to download",
secruityToken.getControllerId(), fileResource);
throw new EntityNotFoundException();
}
checkIfArtifactIsAssignedToTarget(secruityToken, localArtifact);
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
throw new EntityNotFoundException();
}
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1,
localArtifact.getSha1Hash());
cache.put(downloadId, downloadCache);
authentificationResponse
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
authentificationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
LOG.error("Login failed", e);
authentificationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
authentificationResponse.setMessage("Login failed");
} catch (final URISyntaxException e) {
LOG.error("URI build exception", e);
authentificationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
authentificationResponse.setMessage("Building download URI failed");
} catch (final EntityNotFoundException e) {
final String errorMessage = "Artifact for resource " + fileResource + "not found ";
LOG.warn(errorMessage, e);
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authentificationResponse.setMessage(errorMessage);
}
return getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
/**
* check action for this download purposes, the method will throw an
* EntityNotFoundException in case the controller is not allowed to download
* this file because it's not assigned to an action and not assigned to this
* controller. Otherwise no controllerId is set = anonymous download
*
* @param secruityToken
* the security token which holds the target ID to check on
* @param localArtifact
* the local artifact to verify if the given target is allowed to
* download this artifact
*/
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken,
final LocalArtifact localArtifact) {
final String controllerId = secruityToken.getControllerId();
if (controllerId == null) {
LOG.info("anonymous download no authentication check for artifact {}", localArtifact);
return;
}
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", controllerId, localArtifact);
}
private LocalArtifact findLocalArtifactByFileResource(final FileResource fileResource) {
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstLocalArtifactsBySHA1(fileResource.getSha1());
} else if (fileResource.getFilename() != null) {
return artifactManagement.findLocalArtifactByFilename(fileResource.getFilename()).stream().findFirst()
.orElse(null);
} else if (fileResource.getSoftwareModuleFilenameResource() != null) {
return artifactManagement
.findByFilenameAndSoftwareModule(fileResource.getSoftwareModuleFilenameResource().getFilename(),
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId())
.stream().findFirst().orElse(null);
}
return null;
}
private static Artifact convertDbArtifact(final DbArtifact dbArtifact) {
final Artifact artifact = new Artifact();
artifact.setSize(dbArtifact.getSize());
final DbArtifactHash dbArtifactHash = dbArtifact.getHashes();
artifact.setHashes(new ArtifactHash(dbArtifactHash.getSha1(), dbArtifactHash.getMd5()));
return artifact;
}
private static void setSecurityContext(final Authentication authentication) {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(authentication);
@@ -361,10 +202,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DistributionSet distributionSet = action.get().getDistributionSet();
final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet);
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
amqpMessageDispatcherService.targetAssignDistributionSet(new TargetAssignDistributionSetEvent(
target.getOptLockRevision(), target.getTenant(), target.getControllerId(), action.get().getId(),
softwareModuleList, target.getTargetInfo().getAddress(), targetSecurityToken));
target.getOptLockRevision(), target.getTenant(), target, action.get().getId(), softwareModuleList));
}
@@ -481,7 +320,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return action;
}
private void handleCancelRejected(final Message message, final Action action, final ActionStatus actionStatus) {
private static void handleCancelRejected(final Message message, final Action action,
final ActionStatus actionStatus) {
if (action.isCancelingOrCanceled()) {
actionStatus.setStatus(Status.WARNING);
@@ -495,39 +335,4 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
}
private static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
}
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
}
void setControllerManagement(final ControllerManagement controllerManagement) {
this.controllerManagement = controllerManagement;
}
void setHostnameResolver(final HostnameResolver hostnameResolver) {
this.hostnameResolver = hostnameResolver;
}
void setAuthenticationManager(final AmqpControllerAuthentication authenticationManager) {
this.authenticationManager = authenticationManager;
}
void setArtifactManagement(final ArtifactManagement artifactManagement) {
this.artifactManagement = artifactManagement;
}
void setCache(final Cache cache) {
this.cache = cache;
}
void setEntityFactory(final EntityFactory entityFactory) {
this.entityFactory = entityFactory;
}
void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) {
this.systemSecurityContext = systemSecurityContext;
}
}

View File

@@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.MessageConverter;
@@ -39,6 +40,14 @@ public class BaseAmqpService {
this.rabbitTemplate = rabbitTemplate;
}
protected static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
}
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
}
/**
* Is needed to convert a incoming message to is originally object type.
*
@@ -98,7 +107,7 @@ public class BaseAmqpService {
return value.toString();
}
protected final void logAndThrowMessageError(final Message message, final String error) {
protected static final void logAndThrowMessageError(final Message message, final String error) {
LOGGER.warn("Warning! \"{}\" reported by message: {}", error, message);
throw new AmqpRejectAndDontRequeueException(error);
}

View File

@@ -16,6 +16,11 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.net.URL;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
@@ -23,8 +28,16 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.JpaEntityFactory;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymous;
import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp;
@@ -33,11 +46,15 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
@@ -54,15 +71,44 @@ import ru.yandex.qatools.allure.annotations.Stories;
*/
@Features("Component Tests - Device Management Federation API")
@Stories("AmqpController Authentication Test")
@RunWith(MockitoJUnitRunner.class)
public class AmqpControllerAuthenticationTest {
private static final String SHA1 = "12345";
private static final Long ARTIFACT_ID = 1123L;
private static final Long ARTIFACT_SIZE = 6666L;
private static final String TENANT = "DEFAULT";
private static String CONTROLLLER_ID = "123";
private static final Long TENANT_ID = 123L;
private static final String CONTROLLER_ID = "123";
private static final Long TARGET_ID = 123L;
private AmqpMessageHandlerService amqpMessageHandlerService;
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter;
private TenantConfigurationManagement tenantConfigurationManagement;
private AmqpControllerAuthentication authenticationManager;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private SystemManagement systemManagement;
@Mock
private Cache cacheMock;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock
private ControllerManagement controllerManagementMock;
@Mock
private Target targteMock;
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
.<Boolean> builder().value(Boolean.FALSE).build();
@@ -74,8 +120,6 @@ public class AmqpControllerAuthenticationTest {
messageConverter = new Jackson2JsonMessageConverter();
final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class));
final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class);
final Rp rp = mock(Rp.class);
@@ -88,30 +132,57 @@ public class AmqpControllerAuthenticationTest {
when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
when(anonymous.isEnabled()).thenReturn(false);
tenantConfigurationManagement = mock(TenantConfigurationManagement.class);
when(tenantConfigurationManagement.getConfigurationValue(any(), eq(Boolean.class)))
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE);
final ControllerManagement controllerManagement = mock(ControllerManagement.class);
when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID);
amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class));
when(controllerManagement.findByControllerId(anyString())).thenReturn(targteMock);
when(controllerManagement.findByTargetId(any(Long.class))).thenReturn(targteMock);
when(targteMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targteMock.getControllerId()).thenReturn(CONTROLLER_ID);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement,
tenantAware, secruityProperties, systemSecurityContext);
final TenantMetaData tenantMetaData = mock(TenantMetaData.class);
when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
tenantConfigurationManagementMock, tenantAware, secruityProperties, systemSecurityContext);
authenticationManager.postConstruct();
amqpMessageHandlerService.setAuthenticationManager(authenticationManager);
final LocalArtifact testArtifact = new JpaLocalArtifact("afilename", "afilename", new JpaSoftwareModule(
new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", null, null, null));
when(artifactManagementMock.findLocalArtifact(ARTIFACT_ID)).thenReturn(testArtifact);
when(artifactManagementMock.findFirstLocalArtifactsBySHA1(SHA1)).thenReturn(testArtifact);
final DbArtifact artifact = new DbArtifact();
artifact.setSize(ARTIFACT_SIZE);
artifact.setHashes(new DbArtifactHash("sha1 test", "md5 test"));
when(artifactManagementMock.loadLocalArtifactBinary(testArtifact)).thenReturn(artifact);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory());
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock,
controllerManagementMock);
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
when(controllerManagementMock.hasTargetArtifactAssigned(TARGET_ID, testArtifact)).thenReturn(true);
when(controllerManagementMock.hasTargetArtifactAssigned(CONTROLLER_ID, testArtifact)).thenReturn(true);
}
@Test
@Description("Tests authentication manager without principal")
public void testAuthenticationeBadCredantialsWithoutPricipal() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
try {
authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted since principal was missing");
@@ -124,12 +195,12 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication manager without wrong credential")
public void testAuthenticationBadCredantialsWithWrongCredential() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
try {
authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted due to wrong credential");
@@ -142,12 +213,12 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication successfull")
public void testSuccessfullAuthentication() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
assertThat(authentication).isNotNull();
}
@@ -157,13 +228,13 @@ public class AmqpControllerAuthenticationTest {
public void testAuthenticationMessageBadCredantialsWithoutPricipal() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -175,17 +246,17 @@ public class AmqpControllerAuthenticationTest {
@Description("Tests authentication message without wrong credential")
public void testAuthenticationMessageBadCredantialsWithWrongCredential() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -195,24 +266,81 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication message successfull")
public void testSuccessfullMessageAuthentication() {
public void successfullMessageAuthentication() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.NOT_FOUND.value());
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
}
@Test
@Description("Tests authentication message successfull with targetId intead of controllerId provided and artifactId instead of SHA1.")
public void successfullMessageAuthenticationWithTargetIdAndArtifactId() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, null, TARGET_ID,
FileResource.createFileResourceByArtifactId(ARTIFACT_ID));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
}
@Test
@Description("Tests authentication message successfull")
public void successfullMessageAuthenticationWithTenantid() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(null, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())

View File

@@ -13,9 +13,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -24,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
@@ -31,11 +30,14 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.util.IpUtil;
import org.junit.Test;
@@ -49,6 +51,8 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@@ -60,6 +64,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private static final String TENANT = "default";
private static final Long TENANT_ID = 4711L;
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
@@ -71,31 +76,47 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private DefaultAmqpSenderService senderService;
private SystemManagement systemManagement;
private static final String CONTROLLER_ID = "1";
private Target testTarget;
@Override
public void before() throws Exception {
super.before();
testTarget = entityFactory.generateTarget(CONTROLLER_ID, TEST_TOKEN);
testTarget.getTargetInfo().setAddress(AMQP_URI.toString());
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter());
senderService = Mockito.mock(DefaultAmqpSenderService.class);
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
when(artifactUrlHandlerMock.getUrl(anyString(), anyLong(), anyString(), anyString(), anyObject()))
.thenReturn("http://mockurl");
when(artifactUrlHandlerMock.getUrls(anyObject(), anyObject()))
.thenReturn(Lists.newArrayList(new ArtifactUrl("http", "download", "http://mockurl")));
systemManagement = Mockito.mock(SystemManagement.class);
final TenantMetaData tenantMetaData = Mockito.mock(TenantMetaData.class);
when(tenantMetaData.getId()).thenReturn(TENANT_ID);
when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData);
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
artifactUrlHandlerMock);
artifactUrlHandlerMock, systemSecurityContext, systemManagement);
}
@Test
@Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, new ArrayList<SoftwareModule>(), AMQP_URI, TEST_TOKEN);
1L, TENANT, testTarget, 1L, new ArrayList<SoftwareModule>());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
assertTrue("No softwaremmodule should be contained in the request",
@@ -107,9 +128,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
1L, TENANT, testTarget, 1L, dsA.getModules());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("Expecting a size of 3 software modules in the reuqest", 3,
downloadAndUpdateRequest.getSoftwareModules().size());
@@ -146,9 +168,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
1L, TENANT, testTarget, 1L, dsA.getModules());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3,
downloadAndUpdateRequest.getSoftwareModules().size());

View File

@@ -54,7 +54,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,8 +80,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageHandlerServiceTest {
private static final String TENANT = "DEFAULT";
private static final Long TENANT_ID = 123L;
private static String CONTROLLLER_ID = "123";
private static final Long TARGET_ID = 123L;
private AmqpMessageHandlerService amqpMessageHandlerService;
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter;
@@ -113,22 +116,16 @@ public class AmqpMessageHandlerServiceTest {
@Mock
private RabbitTemplate rabbitTemplate;
@Mock
private SystemSecurityContext systemSecurityContextMock;
@Before
public void before() throws Exception {
messageConverter = new Jackson2JsonMessageConverter();
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock);
amqpMessageHandlerService.setControllerManagement(controllerManagementMock);
amqpMessageHandlerService.setAuthenticationManager(authenticationManagerMock);
amqpMessageHandlerService.setArtifactManagement(artifactManagementMock);
amqpMessageHandlerService.setCache(cacheMock);
amqpMessageHandlerService.setHostnameResolver(hostnameResolverMock);
amqpMessageHandlerService.setEntityFactory(entityFactoryMock);
amqpMessageHandlerService.setSystemSecurityContext(systemSecurityContextMock);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManagerMock, artifactManagementMock, cacheMock, hostnameResolverMock,
controllerManagementMock);
}
@Test
@@ -279,13 +276,13 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which does not exists")
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123",
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -298,7 +295,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which is not assigned to the requested target")
public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123",
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -309,7 +306,7 @@ public class AmqpMessageHandlerServiceTest {
.thenThrow(EntityNotFoundException.class);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -322,7 +319,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target")
public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123",
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -340,7 +337,7 @@ public class AmqpMessageHandlerServiceTest {
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -364,15 +361,12 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.addUpdateActionStatus(Matchers.any())).thenReturn(action);
when(entityFactoryMock.generateActionStatus()).thenReturn(new JpaActionStatus());
// for the test the same action can be used
when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any()))
.thenReturn(Optional.of(action));
when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any())).thenReturn(Optional.of(action));
final List<SoftwareModule> softwareModuleList = createSoftwareModuleList();
when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any()))
.thenReturn(softwareModuleList);
when(systemSecurityContextMock.runAsSystem(anyObject())).thenReturn("securityToken");
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L);
@@ -393,10 +387,10 @@ public class AmqpMessageHandlerServiceTest {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent
.getValue();
assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id")
assertThat(targetAssignDistributionSetEvent.getTarget().getControllerId()).as("event has wrong controller id")
.isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getTargetToken()).as("targetoken not filled correctly")
.isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getTarget().getSecurityToken())
.as("targetoken not filled correctly").isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules")
.isEqualTo(softwareModuleList);

View File

@@ -52,8 +52,8 @@ public class BaseAmqpServiceTest {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(1L);
actionUpdateStatus.setSoftwareModuleId(2L);
actionUpdateStatus.getMessage().add("Message 1");
actionUpdateStatus.getMessage().add("Message 2");
actionUpdateStatus.addMessage("Message 1");
actionUpdateStatus.addMessage("Message 2");
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
new MessageProperties());

View File

@@ -1,95 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.util;
import static org.junit.Assert.assertEquals;
import org.eclipse.hawkbit.AmqpTestConfiguration;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Tests for creating urls to download artifacts.
*/
@Features("Component Tests - Artifact URL Handler")
@Stories("Test to generate the artifact download URL")
@SpringApplicationConfiguration(classes = { AmqpTestConfiguration.class,
org.eclipse.hawkbit.RepositoryApplicationConfiguration.class })
public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTest {
private static final String HTTPS_LOCALHOST = "https://localhost/";
private static final String HTTP_LOCALHOST = "http://localhost/";
@Autowired
private ArtifactUrlHandler urlHandlerProperties;
private LocalArtifact localArtifact;
private static final String CONTROLLER_ID = "Test";
private String fileName;
private Long softwareModuleId;
private String sha1Hash;
@Before
public void setup() {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final SoftwareModule module = dsA.getModules().iterator().next();
localArtifact = testdataFactory.createLocalArtifacts(module.getId()).stream().findAny().get();
softwareModuleId = localArtifact.getSoftwareModule().getId();
fileName = localArtifact.getFilename();
sha1Hash = localArtifact.getSha1Hash();
}
@Test
@Description("Tests the generation of http download url.")
public void testHttpUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.HTTP);
assertEquals("http is build incorrect",
HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + CONTROLLER_ID
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(),
url);
}
@Test
@Description("Tests the generation of https download url.")
public void testHttpsUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.HTTPS);
assertEquals("https is build incorrect",
HTTPS_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + CONTROLLER_ID
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(),
url);
}
@Test
@Description("Tests the generation of coap download url.")
public void testCoapUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.COAP);
assertEquals("coap is build incorrect", "coap://127.0.0.1:5683/fw/" + tenantAware.getCurrentTenant() + "/"
+ CONTROLLER_ID + "/sha1/" + localArtifact.getSha1Hash(), url);
}
}

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.dmf.json.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -18,9 +19,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* JSON representation of action update status.
*
*
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -32,7 +30,7 @@ public class ActionUpdateStatus {
@JsonProperty(required = true)
private ActionStatus actionStatus;
@JsonProperty
private final List<String> message = new ArrayList<>();
private List<String> message;
public Long getActionId() {
return actionId;
@@ -59,7 +57,19 @@ public class ActionUpdateStatus {
}
public List<String> getMessage() {
return message;
if (message == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(message);
}
public boolean addMessage(final String message) {
if (this.message == null) {
this.message = new ArrayList<>();
}
return this.message.add(message);
}
}

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.EnumMap;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -25,15 +25,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Artifact {
/**
* Represented the supported protocols for artifact url's.
*
*/
public enum UrlProtocol {
COAP, HTTP, HTTPS
}
@JsonProperty
private String filename;
@@ -44,13 +35,17 @@ public class Artifact {
private Long size;
@JsonProperty
private Map<UrlProtocol, String> urls = new EnumMap<>(UrlProtocol.class);
private Map<String, String> urls;
public Map<UrlProtocol, String> getUrls() {
return urls;
public Map<String, String> getUrls() {
if (urls == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(urls);
}
public void setUrls(final Map<UrlProtocol, String> urls) {
public void setUrls(final Map<String, String> urls) {
this.urls = urls;
}

View File

@@ -8,7 +8,8 @@
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -30,7 +31,7 @@ public class DownloadAndUpdateRequest {
private String targetSecurityToken;
@JsonProperty
private final List<SoftwareModule> softwareModules = new LinkedList<>();
private List<SoftwareModule> softwareModules;
public Long getActionId() {
return actionId;
@@ -49,7 +50,11 @@ public class DownloadAndUpdateRequest {
}
public List<SoftwareModule> getSoftwareModules() {
return softwareModules;
if (softwareModules == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(softwareModules);
}
/**
@@ -59,6 +64,10 @@ public class DownloadAndUpdateRequest {
* the module
*/
public void addSoftwareModule(final SoftwareModule createSoftwareModule) {
if (softwareModules == null) {
softwareModules = new ArrayList<>();
}
softwareModules.add(createSoftwareModule);
}

View File

@@ -8,7 +8,7 @@
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.LinkedList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -35,7 +35,7 @@ public class SoftwareModule {
@JsonProperty
private String moduleVersion;
@JsonProperty
private List<Artifact> artifacts = new LinkedList<>();
private List<Artifact> artifacts;
public String getModuleType() {
return moduleType;
@@ -54,7 +54,11 @@ public class SoftwareModule {
}
public List<Artifact> getArtifacts() {
return artifacts;
if (artifacts == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(artifacts);
}
public Long getModuleId() {

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
@@ -27,16 +28,47 @@ public class TenantSecurityToken {
public static final String AUTHORIZATION_HEADER = "Authorization";
@JsonProperty
private final String tenant;
@JsonProperty
@JsonProperty(required = false)
private String tenant;
@JsonProperty(required = false)
private final Long tenantId;
@JsonProperty(required = false)
private final String controllerId;
@JsonProperty(required = false)
private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Long targetId;
@JsonProperty(required = false)
private Map<String, String> headers;
@JsonProperty(required = false)
private final FileResource fileResource;
/**
* Constructor.
*
* @param tenant
* the tenant for the security token
* @param tenantId
* alternative tenant identification by technical ID
* @param controllerId
* the ID of the controller for the security token
* @param targetId
* alternative target identification by technical ID
* @param fileResource
* the file to obtain
*/
@JsonCreator
public TenantSecurityToken(@JsonProperty("tenant") final String tenant,
@JsonProperty("tenantId") final Long tenantId, @JsonProperty("controllerId") final String controllerId,
@JsonProperty("targetId") final Long targetId,
@JsonProperty("fileResource") final FileResource fileResource) {
this.tenant = tenant;
this.tenantId = tenantId;
this.controllerId = controllerId;
this.targetId = targetId;
this.fileResource = fileResource;
}
/**
* Constructor.
*
@@ -47,13 +79,26 @@ public class TenantSecurityToken {
* @param fileResource
* the file to obtain
*/
@JsonCreator
public TenantSecurityToken(@JsonProperty("tenant") final String tenant,
@JsonProperty("controllerId") final String controllerId,
@JsonProperty("fileResource") final FileResource fileResource) {
public TenantSecurityToken(final String tenant, final String controllerId, final FileResource fileResource) {
this(tenant, null, controllerId, null, fileResource);
}
/**
* Constructor.
*
* @param tenantId
* the tenant for the security token
* @param targetId
* target identification by technical ID
* @param fileResource
* the file to obtain
*/
public TenantSecurityToken(final Long tenantId, final Long targetId, final FileResource fileResource) {
this(null, tenantId, null, targetId, fileResource);
}
public void setTenant(final String tenant) {
this.tenant = tenant;
this.controllerId = controllerId;
this.fileResource = fileResource;
}
public String getTenant() {
@@ -65,13 +110,25 @@ public class TenantSecurityToken {
}
public Map<String, String> getHeaders() {
return headers;
if (headers == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(headers);
}
public FileResource getFileResource() {
return fileResource;
}
public Long getTenantId() {
return tenantId;
}
public Long getTargetId() {
return targetId;
}
/**
* Gets a header value.
*
@@ -80,6 +137,10 @@ public class TenantSecurityToken {
* @return the value
*/
public String getHeader(final String name) {
if (headers == null) {
return null;
}
return headers.get(name);
}
@@ -88,6 +149,24 @@ public class TenantSecurityToken {
this.headers.putAll(headers);
}
/**
* Associates the specified header value with the specified name.
*
* @param name
* of the header
* @param value
* of the header
*
* @return the previous value associated with the <tt>name</tt>, or
* <tt>null</tt> if there was no mapping for <tt>name</tt>.
*/
public String putHeader(final String name, final String value) {
if (headers == null) {
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
return headers.put(name, value);
}
/**
* File resource descriptor which is used to ask for the resource to
* download e.g. The lookup of the file can be different e.g. by SHA1 hash
@@ -99,6 +178,8 @@ public class TenantSecurityToken {
@JsonProperty(required = false)
private String sha1;
@JsonProperty(required = false)
private Long artifactId;
@JsonProperty(required = false)
private String filename;
@JsonProperty(required = false)
private SoftwareModuleFilenameResource softwareModuleFilenameResource;
@@ -128,6 +209,14 @@ public class TenantSecurityToken {
this.softwareModuleFilenameResource = softwareModuleFilenameResource;
}
public Long getArtifactId() {
return artifactId;
}
public void setArtifactId(final Long artifactId) {
this.artifactId = artifactId;
}
/**
* factory method to create a file resource for an SHA1 lookup.
*
@@ -141,6 +230,19 @@ public class TenantSecurityToken {
return resource;
}
/**
* factory method to create a file resource for an artifact ID lookup.
*
* @param artifactId
* the artifact IF key of the file to obtain
* @return the {@link FileResource} with SHA1 key set
*/
public static FileResource createFileResourceByArtifactId(final Long artifactId) {
final FileResource resource = new FileResource();
resource.artifactId = artifactId;
return resource;
}
/**
* factory method to create a file resource for an filename lookup.
*
@@ -173,7 +275,7 @@ public class TenantSecurityToken {
@Override
public String toString() {
return "FileResource [sha1=" + sha1 + ", filename=" + filename + "]";
return "FileResource [sha1=" + sha1 + ", artifactId=" + artifactId + ", filename=" + filename + "]";
}
/**

View File

@@ -168,10 +168,10 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac
private TenantSecurityToken createTenantSecruityTokenVariables(final HttpServletRequest request,
final String tenant, final String controllerId) {
final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, controllerId,
final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, null, controllerId, null,
FileResource.createFileResourceBySha1(""));
final UnmodifiableIterator<String> forEnumeration = Iterators.forEnumeration(request.getHeaderNames());
forEnumeration.forEachRemaining(header -> secruityToken.getHeaders().put(header, request.getHeader(header)));
forEnumeration.forEachRemaining(header -> secruityToken.putHeader(header, request.getHeader(header)));
return secruityToken;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.mgmt.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -72,7 +73,7 @@ public class PagedList<T> extends ResourceSupport {
}
public List<T> getContent() {
return content;
return Collections.unmodifiableList(content);
}
}

View File

@@ -208,15 +208,16 @@ public interface ArtifactManagement {
void deleteLocalArtifact(@NotNull Long id);
/**
* Searches for {@link Artifact} with given {@link Identifiable}.
* Searches for {@link LocalArtifact} with given {@link Identifiable}.
*
* @param id
* to search for
* @return found {@link Artifact} or <code>null</code> is it could not be
* found.
* @return found {@link LocalArtifact} or <code>null</code> is it could not
* be found.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Artifact findArtifact(@NotNull Long id);
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
LocalArtifact findLocalArtifact(@NotNull Long id);
/**
* Find by artifact by software module id and filename.

View File

@@ -183,22 +183,6 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
String getPollingTime();
/**
* An direct access to the security token of an
* {@link Target#getSecurityToken()} without authorization. This is
* necessary to be able to access the security-token without any
* security-context information because the security-token is used for
* authentication.
*
* @param controllerId
* the ID of the controller to retrieve the security token for
* @return the security context of the target, in case no target exists for
* the given controllerId {@code null} is returned
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET_SEC_TOKEN)
String getSecurityTokenByControllerId(@NotEmpty String controllerId);
/**
* Checks if a given target has currently or has even been assigned to the
* given artifact through the action history list. This can e.g. indicate if
@@ -218,6 +202,25 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotNull String controllerId, @NotNull LocalArtifact localArtifact);
/**
* Checks if a given target has currently or has even been assigned to the
* given artifact through the action history list. This can e.g. indicate if
* a target is allowed to download a given artifact because it has currently
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
*
* @param targetId
* the ID of the target to check
* @param localArtifact
* the artifact to verify if the given target had even been
* assigned to
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotNull Long targetId, @NotNull LocalArtifact localArtifact);
/**
* Registers retrieved status for given {@link Target} and {@link Action} if
* it does not exist yet.
@@ -300,4 +303,32 @@ public interface ControllerManagement {
TargetInfo updateTargetStatus(@NotNull TargetInfo targetInfo, TargetUpdateStatus status, Long lastTargetQuery,
URI address);
/**
* Finds {@link Target} based on given controller ID returns found Target
* without details, i.e. NO {@link Target#getTags()} and
* {@link Target#getActions()} possible.
*
* @param controllerId
* to look for.
* @return {@link Target} or {@code null} if it does not exist
* @see Target#getControllerId()
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Target findByControllerId(@NotEmpty final String controllerId);
/**
* Finds {@link Target} based on given ID returns found Target without
* details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()}
* possible.
*
* @param targetId
* to look for.
* @return {@link Target} or {@code null} if it does not exist
* @see Target#getId()
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Target findByTargetId(final long targetId);
}

View File

@@ -57,7 +57,11 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
* @return the actionIds
*/
public List<Long> getActions() {
return actions;
if (actions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actions);
}
@Override

View File

@@ -205,8 +205,10 @@ public interface DistributionSetManagement {
/**
* deletes a distribution set meta data entry.
*
* @param id
* the ID of the distribution set meta data to delete
* @param distributionSet
* where meta data has to be deleted
* @param key
* of the meta data element
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteDistributionSetMetadata(@NotNull final DistributionSet distributionSet, @NotNull final String key);
@@ -429,7 +431,7 @@ public interface DistributionSetManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSetType findDistributionSetTypeByKey(@NotNull String key);
DistributionSetType findDistributionSetTypeByKey(@NotEmpty String key);
/**
* @param name
@@ -469,15 +471,16 @@ public interface DistributionSetManagement {
/**
* finds a single distribution set meta data by its id.
*
* @param id
* the id of the distribution set meta data containing the meta
* data key and the ID of the distribution set
* @param distributionSet
* where meta data has to rind
* @param key
* of the meta data element
* @return the found DistributionSetMetadata or {@code null} if not exits
* @throws EntityNotFoundException
* in case the meta data does not exists for the given key
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSetMetadata findOne(@NotNull DistributionSet distributionSet, @NotNull String key);
DistributionSetMetadata findOne(@NotNull DistributionSet distributionSet, @NotEmpty String key);
/**
* Checks if a {@link DistributionSet} is currently in use by a target in

View File

@@ -155,11 +155,13 @@ public interface SoftwareManagement {
/**
* deletes a software module meta data entry.
*
* @param id
* the ID of the software module meta data to delete
* @param softwareModule
* where meta data has to be deleted
* @param key
* of the metda data element
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteSoftwareModuleMetadata(@NotNull SoftwareModule softwareModule, @NotNull String key);
void deleteSoftwareModuleMetadata(@NotNull SoftwareModule softwareModule, @NotEmpty String key);
/**
* Deletes {@link SoftwareModule}s which is any if the given ids.
@@ -251,9 +253,10 @@ public interface SoftwareManagement {
/**
* finds a single software module meta data by its id.
*
* @param id
* the id of the software module meta data containing the meta
* data key and the ID of the software module
* @param softwareModule
* where meta data has to be found
* @param key
* of the meta data element
* @return the found SoftwareModuleMetadata or {@code null} if not exits
* @throws EntityNotFoundException
* in case the meta data does not exists for the given key
@@ -280,8 +283,8 @@ public interface SoftwareManagement {
*
* @param softwareModuleId
* the software module id to retrieve the meta data from
* @param spec
* the specification to filter the result
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* the page request to page the result
* @return a paged result of all meta data entries for a given software
@@ -346,8 +349,8 @@ public interface SoftwareManagement {
/**
* Retrieves all {@link SoftwareModule}s with a given specification.
*
* @param spec
* the specification to filter the software modules
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* pagination parameter
* @return the found {@link SoftwareModule}s
@@ -392,7 +395,7 @@ public interface SoftwareManagement {
* {@link SoftwareModuleType#getKey()}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
SoftwareModuleType findSoftwareModuleTypeByKey(@NotNull String key);
SoftwareModuleType findSoftwareModuleTypeByKey(@NotEmpty String key);
/**
*
@@ -415,8 +418,8 @@ public interface SoftwareManagement {
/**
* Retrieves all {@link SoftwareModuleType}s with a given specification.
*
* @param spec
* the specification to filter the software modules types
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* pagination parameter
* @return the found {@link SoftwareModuleType}s

View File

@@ -63,7 +63,8 @@ public interface SystemManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
TenantMetaData getTenantMetadata();
/**
@@ -93,4 +94,14 @@ public interface SystemManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
TenantMetaData updateTenantMetadata(@NotNull TenantMetaData metaData);
/**
* Returns {@link TenantMetaData} of given tenant ID.
*
* @param tenantId
* to retrieve data for
* @return {@link TenantMetaData} of given tenant
*/
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
TenantMetaData getTenantMetadata(@NotNull Long tenantId);
}

View File

@@ -34,6 +34,8 @@ public class RolloutGroupCreatedEvent extends AbstractDistributedEvent {
* the revision of the event
* @param rolloutId
* the ID of the rollout the group has been created
* @param rolloutGroupId
* identifier of this group
* @param totalRolloutGroup
* the total number of rollout groups for this rollout
* @param createdRolloutGroup

View File

@@ -8,11 +8,11 @@
*/
package org.eclipse.hawkbit.repository.eventbus.event;
import java.net.URI;
import java.util.Collection;
import org.eclipse.hawkbit.eventbus.event.DefaultEvent;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
/**
* Event that gets sent when a distribution set gets assigned to a target.
@@ -21,10 +21,8 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
public class TargetAssignDistributionSetEvent extends DefaultEvent {
private final Collection<SoftwareModule> softwareModules;
private final String controllerId;
private final Target target;
private final Long actionId;
private final URI targetAdress;
private final String targetToken;
/**
* Creates a new {@link TargetAssignDistributionSetEvent}.
@@ -33,26 +31,19 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
* the revision of the event
* @param tenant
* the tenant of the event
* @param controllerId
* the ID of the controller
* @param target
* the assigned {@link Target}
* @param actionId
* the action id of the assignment
* @param softwareModules
* the software modules which have been assigned to the target
* @param targetAdress
* the targetAdress of the target
* @param targetToken
* the authentication token of the target
*/
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId,
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress,
final String targetToken) {
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final Target target,
final Long actionId, final Collection<SoftwareModule> softwareModules) {
super(revision, tenant);
this.controllerId = controllerId;
this.target = target;
this.actionId = actionId;
this.softwareModules = softwareModules;
this.targetAdress = targetAdress;
this.targetToken = targetToken;
}
/**
@@ -63,11 +54,11 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
}
/**
* @return the controllerId of the Target which has been assigned to the
* distribution set
* @return the {@link Target} which has been assigned to the distribution
* set
*/
public String getControllerId() {
return controllerId;
public Target getTarget() {
return target;
}
/**
@@ -76,12 +67,4 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
public Collection<SoftwareModule> getSoftwareModules() {
return softwareModules;
}
public URI getTargetAdress() {
return targetAdress;
}
public String getTargetToken() {
return targetToken;
}
}

View File

@@ -60,7 +60,7 @@ public class AssignedSoftwareModule implements Serializable {
final int prime = 31;
int result = 1;
result = prime * result + (assigned ? 1231 : 1237);
result = prime * result + (softwareModule == null ? 0 : softwareModule.hashCode());
result = prime * result + ((softwareModule == null) ? 0 : softwareModule.hashCode());
return result;
}
@@ -72,7 +72,7 @@ public class AssignedSoftwareModule implements Serializable {
if (obj == null) {
return false;
}
if (!(obj instanceof AssignedSoftwareModule)) {
if (getClass() != obj.getClass()) {
return false;
}
final AssignedSoftwareModule other = (AssignedSoftwareModule) obj;

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.model;
import java.util.Collections;
import java.util.List;
/**
@@ -82,14 +83,22 @@ public class AssignmentResult<T extends BaseEntity> {
* @return {@link List} of assigned entity.
*/
public List<T> getAssignedEntity() {
return assignedEntity;
if (assignedEntity == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(assignedEntity);
}
/**
* @return {@link List} of unassigned entity.
*/
public List<T> getUnassignedEntity() {
return unassignedEntity;
if (unassignedEntity == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(unassignedEntity);
}
}

View File

@@ -11,6 +11,9 @@ package org.eclipse.hawkbit.repository.model;
/**
* Interface for the entity interceptor lifecycle.
*/
// Exception squid:EmptyStatementUsageCheck - don't want to force users to
// impelemnt all methods
@SuppressWarnings("squid:EmptyStatementUsageCheck")
public interface EntityInterceptor {
/**

View File

@@ -29,7 +29,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifactProvider;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
@@ -194,7 +193,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
}
@Override
public Artifact findArtifact(final Long id) {
public LocalArtifact findLocalArtifact(final Long id) {
return localArtifactRepository.findOne(id);
}

View File

@@ -158,6 +158,15 @@ public class JpaControllerManagement implements ControllerManagement {
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
}
@Override
public boolean hasTargetArtifactAssigned(final Long targetId, final LocalArtifact localArtifact) {
final Target target = targetRepository.findOne(targetId);
if (target == null) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
}
@Override
public List<Action> findActiveActionByTarget(final Target target) {
return actionRepository.findByTargetAndActiveOrderByIdAsc((JpaTarget) target, true);
@@ -456,12 +465,6 @@ public class JpaControllerManagement implements ControllerManagement {
return actionStatusRepository.save((JpaActionStatus) statusMessage);
}
@Override
public String getSecurityTokenByControllerId(final String controllerId) {
final Target target = targetRepository.findByControllerId(controllerId);
return target != null ? target.getSecurityToken() : null;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@@ -475,4 +478,14 @@ public class JpaControllerManagement implements ControllerManagement {
cacheWriteNotify.downloadProgress(statusId, requestedBytes, shippedBytesSinceLast, shippedBytesOverall);
}
@Override
public Target findByControllerId(final String controllerId) {
return targetRepository.findByControllerId(controllerId);
}
@Override
public Target findByTargetId(final long targetId) {
return targetRepository.findOne(targetId);
}
}

View File

@@ -352,14 +352,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private void assignDistributionSetEvent(final JpaTarget target, final Long actionId,
final List<JpaSoftwareModule> modules) {
((JpaTargetInfo) target.getTargetInfo()).setUpdateStatus(TargetUpdateStatus.PENDING);
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<SoftwareModule> softwareModules = (Collection) modules;
afterCommit.afterCommit(() -> {
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
targetSecurityToken));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), target,
actionId, softwareModules));
});
}

View File

@@ -299,4 +299,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
Constants.DST_DEFAULT_OS_WITH_APPS_NAME, "Default type with Firmware/OS and optional app(s).")
.addMandatoryModuleType(os).addOptionalModuleType(app));
}
@Override
public TenantMetaData getTenantMetadata(final Long tenantId) {
return tenantMetaDataRepository.findOne(tenantId);
}
}

View File

@@ -125,7 +125,7 @@ public class JpaTagManagement implements TagManagement {
final List<JpaTarget> changed = new LinkedList<>();
for (final JpaTarget target : targetRepository.findByTag(tag)) {
target.getTags().remove(tag);
target.removeTag(tag);
changed.add(target);
}

View File

@@ -75,24 +75,4 @@ public class JpaDistributionSetTag extends AbstractJpaTag implements Distributio
return Collections.unmodifiableList(assignedToDistributionSet);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof DistributionSetTag)) {
return false;
}
return true;
}
}

View File

@@ -162,7 +162,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
return Collections.emptySet();
}
return tags;
return Collections.unmodifiableSet(tags);
}
public List<RolloutTargetGroup> getRolloutTargetGroup() {
@@ -210,7 +210,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
public boolean addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>(4);
actions = new ArrayList<>();
}
return actions.add(action);

View File

@@ -73,24 +73,4 @@ public class JpaTargetTag extends AbstractJpaTag implements TargetTag {
return Collections.unmodifiableList(assignedToTargets);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof TargetTag)) {
return false;
}
return true;
}
}

View File

@@ -279,23 +279,23 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTestWithMongoD
/**
* Test method for
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#findArtifact(java.lang.Long)}
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#findLocalArtifact(java.lang.Long)}
* .
*
* @throws IOException
* @throws NoSuchAlgorithmException
*/
@Test
@Description("Loads an artifact based on given ID.")
public void findArtifact() throws NoSuchAlgorithmException, IOException {
@Description("Loads an local artifact based on given ID.")
public void findLocalArtifact() throws NoSuchAlgorithmException, IOException {
SoftwareModule sm = new JpaSoftwareModule(softwareManagement.findSoftwareModuleTypeByKey("os"), "name 1",
"version 1", null, null);
sm = softwareManagement.createSoftwareModule(sm);
final Artifact result = artifactManagement.createLocalArtifact(new RandomGeneratedInputStream(5 * 1024),
final LocalArtifact result = artifactManagement.createLocalArtifact(new RandomGeneratedInputStream(5 * 1024),
sm.getId(), "file1", false);
assertThat(artifactManagement.findArtifact(result.getId())).isEqualTo(result);
assertThat(artifactManagement.findLocalArtifact(result.getId())).isEqualTo(result);
}
/**

View File

@@ -937,7 +937,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
for (final Target myt : targets) {
boolean found = false;
for (final TargetAssignDistributionSetEvent event : events) {
if (event.getControllerId().equals(myt.getControllerId())) {
if (event.getTarget().getControllerId().equals(myt.getControllerId())) {
found = true;
final List<Action> activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(myt);
assertThat(activeActionsByTarget).as("size of active actions for target is wrong").isNotEmpty();

View File

@@ -11,6 +11,8 @@ package org.eclipse.hawkbit;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties;
import org.eclipse.hawkbit.api.PropertyBasedArtifactUrlHandler;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
@@ -49,7 +51,8 @@ import com.mongodb.MongoClientOptions;
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY, proxyTargetClass = false, securedEnabled = true)
@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class })
@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class,
ArtifactUrlHandlerProperties.class })
@Profile("test")
@EnableAutoConfiguration
public class TestConfiguration implements AsyncConfigurer {
@@ -63,6 +66,12 @@ public class TestConfiguration implements AsyncConfigurer {
return new TestdataFactory();
}
@Bean
public PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler(
final ArtifactUrlHandlerProperties urlHandlerProperties) {
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties);
}
@Bean
public MongoClientOptions options() {
return MongoClientOptions.builder().connectTimeout(500).maxWaitTime(500).connectionsPerHost(2)

View File

@@ -68,4 +68,35 @@ public class TenantUserPasswordAuthenticationToken extends UsernamePasswordAuthe
public Object getTenant() {
return tenant;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TenantUserPasswordAuthenticationToken other = (TenantUserPasswordAuthenticationToken) obj;
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
return true;
}
}

View File

@@ -8,21 +8,16 @@
*/
package org.eclipse.hawkbit.security;
import java.util.Optional;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
/**
* An pre-authenticated processing filter which extracts (if enabled through
@@ -68,11 +63,12 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro
@Override
public HeaderAuthentication getPreAuthenticatedPrincipal(final TenantSecurityToken secruityToken) {
final String controllerId = resolveControllerId(secruityToken);
final String authHeader = secruityToken.getHeader(TenantSecurityToken.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(secruityToken.getControllerId(), authHeader.substring(OFFSET_TARGET_TOKEN));
return new HeaderAuthentication(controllerId, authHeader.substring(OFFSET_TARGET_TOKEN));
}
LOGGER.debug(
"security token filter is enabled but requst does not contain either the necessary path variables {} or the authorization header with scheme {}",
@@ -81,51 +77,36 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro
}
@Override
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
final String securityToken = tenantAware.runAsTenant(secruityToken.getTenant(),
new GetSecurityTokenTenantRunner(secruityToken.getTenant(), secruityToken.getControllerId()));
return new HeaderAuthentication(secruityToken.getControllerId(), securityToken);
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken securityToken) {
final Target target = systemSecurityContext.runAsSystemAsTenant(() -> {
if (securityToken.getTargetId() != null) {
return controllerManagement.findByTargetId(securityToken.getTargetId());
}
return controllerManagement.findByControllerId(securityToken.getControllerId());
}, securityToken.getTenant());
if (target == null) {
return null;
}
final String targetSecurityToken = systemSecurityContext.runAsSystemAsTenant(() -> target.getSecurityToken(),
securityToken.getTenant());
return new HeaderAuthentication(target.getControllerId(), targetSecurityToken);
}
private String resolveControllerId(final TenantSecurityToken securityToken) {
if (securityToken.getControllerId() != null) {
return securityToken.getControllerId();
}
final Optional<Target> foundTarget = Optional.ofNullable(systemSecurityContext.runAsSystemAsTenant(
() -> controllerManagement.findByTargetId(securityToken.getTargetId()), securityToken.getTenant()));
if (!foundTarget.isPresent()) {
return null;
}
return foundTarget.get().getControllerId();
}
@Override
protected TenantConfigurationKey getTenantConfigurationKey() {
return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED;
}
private final class GetSecurityTokenTenantRunner implements TenantAware.TenantRunner<String> {
private final String controllerId;
private final String tenant;
private GetSecurityTokenTenantRunner(final String tenant, final String controllerId) {
this.tenant = tenant;
this.controllerId = controllerId;
}
@Override
public String run() {
LOGGER.trace("retrieving security token for controllerId {}", controllerId);
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
SecurityContextHolder.setContext(getSecurityTokenReadContext());
return controllerManagement.getSecurityTokenByControllerId(controllerId);
} finally {
SecurityContextHolder.setContext(oldContext);
}
}
private SecurityContext getSecurityTokenReadContext() {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(getSecurityTokenReadAuthentication());
return securityContextImpl;
}
private Authentication getSecurityTokenReadAuthentication() {
final AnonymousAuthenticationToken anonymousAuthenticationToken = new AnonymousAuthenticationToken(
"anonymous-read-security-token", "anonymous", com.google.common.collect.Lists
.newArrayList(new SimpleGrantedAuthority(SpPermission.READ_TARGET_SEC_TOKEN)));
anonymousAuthenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenant, true));
return anonymousAuthenticationToken;
}
}
}

View File

@@ -594,7 +594,9 @@ public class UploadLayout extends VerticalLayout {
// delete file system zombies
artifactUploadState.getFileSelected().forEach(customFile -> {
final File file = new File(customFile.getFilePath());
file.delete();
if (!file.delete()) {
LOG.warn("Failed to delete file {} in upload dialog", customFile.getFilePath());
}
});
artifactUploadState.getFileSelected().clear();

View File

@@ -79,7 +79,6 @@ public class DsMetadataPopupLayout extends AbstractMetadataPopupLayout<Distribut
@Override
protected void deleteMetadata(final DistributionSet entity, final String key, final String value) {
final DistributionSetMetadata dsMetaData = entityFactory.generateDistributionSetMetadata(entity, key, value);
distributionSetManagement.deleteDistributionSetMetadata(entity, key);
}