Remove download by downloadId functionality (#1820)

This functionallity seems to get via AMQP (after some authentication)
a private (wihtout need of authentication) url to an artifact assigned
to the controller.

By default, DDI or DMF shall provide proper urls (for direct download)
to devices and if they have to be without authentication this shall be
solved in different ways - for instance separate download server providing
dedicated private / signed urls.

This functinallity is not a real hawkBit part but more like something
intended to solve some edge cases.
Since it is complicated, heeds support, doesn't solve wide spread use
cases, and could be achieved with other means - better to be removed.

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-08-14 17:28:46 +03:00
committed by GitHub
parent 12928a5939
commit d958d8e82c
26 changed files with 36 additions and 2448 deletions

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.autoconfigure.cache;
import org.eclipse.hawkbit.cache.DefaultDownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* A configuration for configuring a cache for the download id's.
*
* This is done by providing a named cache.
*/
@Configuration
public class DownloadIdCacheAutoConfiguration {
/**
* Bean for the downloadId cache that returns the DefaultDownloadIdCache.
* The DefaultDownloadIdCache cannot be used within a cluster because the
* downloadId cache is not shared among notes. This means, a downloadId
* which is stored on note A for downloading an artifact can only be used
* for downloading the artifact form node A.
*
* @return the DefaultDownloadIdCache
*/
@Bean
@ConditionalOnMissingBean
public DownloadIdCache downloadIdCache(final CacheManager cacheManager) {
return new DefaultDownloadIdCache(cacheManager);
}
}

View File

@@ -16,7 +16,6 @@ import java.util.List;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration; import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
@@ -34,7 +33,6 @@ import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateAnonymousDownlo
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFilter;
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter;
import org.eclipse.hawkbit.security.MDCHandler; import org.eclipse.hawkbit.security.MDCHandler;
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -76,8 +74,7 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
/** /**
* All configurations related to HawkBit's authentication and authorization * All configurations related to HawkBit's authentication and authorization layer.
* layer.
*/ */
@Slf4j @Slf4j
@Configuration @Configuration
@@ -359,40 +356,6 @@ public class SecurityManagedConfiguration {
return filterRegBean; return filterRegBean;
} }
/**
* Security config to handle and filter the download ids.
*/
@Configuration
@EnableWebSecurity
@ConditionalOnClass(MgmtApiConfiguration.class)
public static class IdRestSecurityConfigurationAdapter {
@Bean
@Order(320)
protected SecurityFilterChain filterChainDLID(
final HttpSecurity http,
final DdiSecurityProperties ddiSecurityConfiguration, final DownloadIdCache downloadIdCache)
throws Exception {
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
final HttpDownloadAuthenticationFilter downloadIdAuthenticationFilter = new HttpDownloadAuthenticationFilter(
downloadIdCache);
downloadIdAuthenticationFilter.setAuthenticationManager(authenticationManager);
http
.securityMatcher(MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + "/downloadId/*/*")
.authorizeHttpRequests(armrRepository -> armrRepository.anyRequest().authenticated())
.csrf(AbstractHttpConfigurer::disable)
.anonymous(AbstractHttpConfigurer::disable)
.addFilterBefore(downloadIdAuthenticationFilter, AuthorizationFilter.class)
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
MDCHandler.Filter.addLoggingFilter(http);
return http.build();
}
}
/** /**
* Security configuration for the REST management API. * Security configuration for the REST management API.
*/ */
@@ -420,8 +383,7 @@ public class SecurityManagedConfiguration {
securityProperties.getDos().getFilter(), securityProperties.getClients()); securityProperties.getDos().getFilter(), securityProperties.getClients());
filterRegBean.setUrlPatterns(List.of( filterRegBean.setUrlPatterns(List.of(
MgmtRestConstants.BASE_REST_MAPPING + "/*", MgmtRestConstants.BASE_REST_MAPPING + "/*",
MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/*", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/*"));
MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + "/*"));
filterRegBean.setOrder(DOS_FILTER_ORDER); filterRegBean.setOrder(DOS_FILTER_ORDER);
filterRegBean.setName("dosMgmtFilter"); filterRegBean.setName("dosMgmtFilter");

View File

@@ -1,5 +1,4 @@
org.eclipse.hawkbit.autoconfigure.cache.CacheAutoConfiguration org.eclipse.hawkbit.autoconfigure.cache.CacheAutoConfiguration
org.eclipse.hawkbit.autoconfigure.cache.DownloadIdCacheAutoConfiguration
org.eclipse.hawkbit.autoconfigure.ddi.DDiApiAutoConfiguration org.eclipse.hawkbit.autoconfigure.ddi.DDiApiAutoConfiguration
org.eclipse.hawkbit.autoconfigure.dmf.amqp.DmfApiAutoConfiguration org.eclipse.hawkbit.autoconfigure.dmf.amqp.DmfApiAutoConfiguration
org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration

View File

@@ -1,57 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.cache;
import java.util.Objects;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
/**
* A default implementation of the {@link DownloadIdCache} which uses the
* {@link CacheManager} implementation to store the download-ids.
*/
public class DefaultDownloadIdCache implements DownloadIdCache {
static final String DOWNLOAD_ID_CACHE = "DownloadIdCache";
private final CacheManager cacheManager;
/**
* @param cacheManager the underlying cache-manager to store the download-ids
*/
public DefaultDownloadIdCache(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public void put(final String downloadId, final DownloadArtifactCache object) {
getCache().put(downloadId, object);
}
@Override
public DownloadArtifactCache get(final String downloadId) {
final ValueWrapper valueWrapper = getCache().get(downloadId);
return (valueWrapper == null) ? null : (DownloadArtifactCache) valueWrapper.get();
}
@Override
public void evict(final String downloadId) {
getCache().evict(downloadId);
}
private Cache getCache() {
final Cache cache = (cacheManager instanceof TenancyCacheManager)
? ((TenancyCacheManager) cacheManager).getDirectCache(DOWNLOAD_ID_CACHE)
: cacheManager.getCache(DOWNLOAD_ID_CACHE);
return Objects.requireNonNull(cache, "Cache(s) returned by cache-manager must not be null!");
}
}

View File

@@ -1,74 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.cache;
/**
* Cache Object for downloading an artifact.
*/
public class DownloadArtifactCache {
private final DownloadType downloadType;
private final String id;
/**
* Constructor.
*
* @param downloadType
* the type for searching the artifact.
* @param id
* the searching id e.g. sha1, md5
*/
public DownloadArtifactCache(final DownloadType downloadType, final String id) {
this.downloadType = downloadType;
this.id = id;
}
public String getId() {
return id;
}
public DownloadType getDownloadType() {
return downloadType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((downloadType == null) ? 0 : downloadType.hashCode());
result = prime * result + ((id == null) ? 0 : id.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 DownloadArtifactCache other = (DownloadArtifactCache) obj;
if (downloadType != other.downloadType) {
return false;
}
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
}

View File

@@ -1,58 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.cache;
/**
* A interface declaration of the download-id-cache which allows to store
* volatile generated download-IDs used to have a single unique download-request
* e.g. for distributed download-server.
*
* A valid download-id is created during a successful download-authorization
* request e.g. from a download-server. In the DMF response a unique
* download-URL containing a generated download-id which can be used to download
* the artifact using this single download-url.
*
* The {@link DownloadIdCache} handles storing this unique download-id from the
* DMF authorization request until the actual artifact download-request via HTTP
* with the unique ID is performed.
*
*/
public interface DownloadIdCache {
/**
* Puts a given artifact cache object with the given downloadId key into the
* cache.
*
* @param downloadId
* the ID to store the cache object to look it up later on
* @param downloadArtifactCacheObject
* the object to store into the cache
*/
void put(final String downloadId, final DownloadArtifactCache downloadArtifactCacheObject);
/**
* Retrieves a {@link DownloadArtifactCache} by a given downloadId.
*
* @param downloadId
* the ID to retrieve the artifact cache object
* @return the found {@link DownloadArtifactCache} or {@code null} if none
* exists for the given ID
*/
DownloadArtifactCache get(final String downloadId);
/**
* Evicts a {@link DownloadArtifactCache} for the given downloadId
*
* @param downloadId
* the ID to be evicted
*/
void evict(String downloadId);
}

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.cache;
/**
* The type of the id which is saved.
*/
public enum DownloadType {
BY_SHA1
}

View File

@@ -1,127 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.cache;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@Feature("Unit Tests - Cache")
@Story("Download ID Cache")
@ExtendWith(MockitoExtension.class)
class DefaultDownloadIdCacheTest {
@Mock
private CacheManager cacheManagerMock;
@Mock
private TenancyCacheManager tenancyCacheManagerMock;
@Mock
private Cache cacheMock;
@Captor
private ArgumentCaptor<String> cacheManagerKeyCaptor;
@Captor
private ArgumentCaptor<DownloadArtifactCache> cacheManagerValueCaptor;
private DefaultDownloadIdCache underTest;
private final String knownKey = "12345";
@BeforeEach
public void before() {
underTest = new DefaultDownloadIdCache(cacheManagerMock);
lenient().when(cacheManagerMock.getCache(DefaultDownloadIdCache.DOWNLOAD_ID_CACHE)).thenReturn(cacheMock);
}
@Test
@Description("Verifies that putting key and value is delegated to the CacheManager implementation")
void putKeyAndValueIsDelegatedToCacheManager() {
final DownloadArtifactCache value = new DownloadArtifactCache(DownloadType.BY_SHA1, knownKey);
underTest.put(knownKey, value);
verify(cacheMock).put(cacheManagerKeyCaptor.capture(), cacheManagerValueCaptor.capture());
assertThat(cacheManagerKeyCaptor.getValue()).isEqualTo(knownKey);
assertThat(cacheManagerValueCaptor.getValue()).isEqualTo(value);
}
@Test
@Description("Verifies that evicting a key is delegated to the CacheManager implementation")
void evictKeyIsDelegatedToCacheManager() {
underTest.evict(knownKey);
verify(cacheMock).evict(cacheManagerKeyCaptor.capture());
assertThat(cacheManagerKeyCaptor.getValue()).isEqualTo(knownKey);
}
@Test
@Description("Verifies that retrieving a value for a specific key is delegated to the CacheManager implementation")
void getValueReturnsTheAssociatedValueForKey() {
final String knownKey = "12345";
final DownloadArtifactCache knownValue = new DownloadArtifactCache(DownloadType.BY_SHA1, knownKey);
when(cacheMock.get(knownKey)).thenReturn(new SimpleValueWrapper(knownValue));
final DownloadArtifactCache downloadArtifactCache = underTest.get(knownKey);
assertThat(downloadArtifactCache).isEqualTo(knownValue);
}
@Test
@Description("Verifies that retrieving a null value for a specific key is delegated to the CacheManager implementation")
void getValueReturnsNullIfNoKeyIsAssociated() {
when(cacheMock.get(knownKey)).thenReturn(new SimpleValueWrapper(null));
final DownloadArtifactCache downloadArtifactCache = underTest.get(knownKey);
assertThat(downloadArtifactCache).isNull();
}
@Test
@Description("Verifies that TenancyCacheManager is using direct cache because download-ids are global unique and don't need to run as tenant aware")
void tenancyCacheManagerIsUsingDirectCache() {
when(tenancyCacheManagerMock.getDirectCache(DefaultDownloadIdCache.DOWNLOAD_ID_CACHE)).thenReturn(cacheMock);
underTest = new DefaultDownloadIdCache(tenancyCacheManagerMock);
final DownloadArtifactCache value = new DownloadArtifactCache(DownloadType.BY_SHA1, knownKey);
underTest.put(knownKey, value);
verify(cacheMock).put(cacheManagerKeyCaptor.capture (), cacheManagerValueCaptor.capture());
verify(tenancyCacheManagerMock).getDirectCache(DefaultDownloadIdCache.DOWNLOAD_ID_CACHE);
assertThat(cacheManagerKeyCaptor.getValue()).isEqualTo(knownKey);
assertThat(cacheManagerValueCaptor.getValue()).isEqualTo(value);
}
}

View File

@@ -1,240 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.amqp;
import java.net.URISyntaxException;
import java.util.Optional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifact;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
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.Artifact;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
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.
*/
@Slf4j
public class AmqpAuthenticationMessageHandler extends BaseAmqpService {
private final AmqpControllerAuthentication authenticationManager;
private final ArtifactManagement artifactManagement;
private final DownloadIdCache cache;
private final HostnameResolver hostnameResolver;
private final ControllerManagement controllerManagement;
private final TenantAware tenantAware;
/**
* @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
* @param tenantAware
* to access current tenant
*/
public AmqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
final DownloadIdCache cache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement, final TenantAware tenantAware) {
super(rabbitTemplate);
this.authenticationManager = authenticationManager;
this.artifactManagement = artifactManagement;
this.cache = cache;
this.hostnameResolver = hostnameResolver;
this.controllerManagement = controllerManagement;
this.tenantAware = tenantAware;
}
/**
* Executed on an authentication request.
*
* @param message
* the amqp message
* @return the rpc message back to supplier.
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue:authentication_receiver}", 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 securityToken
* the security token which holds the target ID to check on
* @param sha1Hash
* of the artifact to verify if the given target is allowed to
* download it
*/
private void checkIfArtifactIsAssignedToTarget(final DmfTenantSecurityToken securityToken, final String sha1Hash) {
if (securityToken.getControllerId() != null) {
checkByControllerId(sha1Hash, securityToken.getControllerId());
} else if (securityToken.getTargetId() != null) {
checkByTargetId(sha1Hash, securityToken.getTargetId());
} else {
log.info("anonymous download no authentication check for artifact {}", sha1Hash);
}
}
private void checkByTargetId(final String sha1Hash, final Long targetId) {
log.debug("no anonymous download request, doing authentication check for target {} and artifact {}", targetId,
sha1Hash);
if (!controllerManagement.hasTargetArtifactAssigned(targetId, sha1Hash)) {
log.info("target {} tried to download artifact {} which is not assigned to the target", targetId, sha1Hash);
throw new EntityNotFoundException();
}
log.info("download security check for target {} and artifact {} granted", targetId, sha1Hash);
}
private void checkByControllerId(final String sha1Hash, final String controllerId) {
log.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, sha1Hash);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, sha1Hash)) {
log.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
sha1Hash);
throw new EntityNotFoundException();
}
log.info("download security check for target {} and artifact {} granted", controllerId, sha1Hash);
}
private Optional<Artifact> findArtifactByFileResource(final FileResource fileResource) {
if (fileResource == null) {
return Optional.empty();
}
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstBySHA1(fileResource.getSha1());
}
if (fileResource.getFilename() != null) {
return artifactManagement.getByFilename(fileResource.getFilename());
}
if (fileResource.getArtifactId() != null) {
return artifactManagement.get(fileResource.getArtifactId());
}
if (fileResource.getSoftwareModuleFilenameResource() != null) {
return artifactManagement.getByFilenameAndSoftwareModule(
fileResource.getSoftwareModuleFilenameResource().getFilename(),
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId());
}
return Optional.empty();
}
private static DmfArtifact convertDbArtifact(final Artifact dbArtifact) {
final DmfArtifact artifact = new DmfArtifact();
artifact.setSize(dbArtifact.getSize());
artifact.setLastModified(dbArtifact.getCreatedAt());
artifact.setHashes(new DmfArtifactHash(dbArtifact.getSha1Hash(), dbArtifact.getMd5Hash()));
return artifact;
}
// suppress warning, EntityNotFoundException has not to be logged or
// rethrown as the exception has no valuable information
@SuppressWarnings("squid:S1166")
private Message handleAuthenticationMessage(final Message message) {
final DmfDownloadResponse authenticationResponse = new DmfDownloadResponse();
final DmfTenantSecurityToken securityToken = convertMessage(message, DmfTenantSecurityToken.class);
final FileResource fileResource = securityToken.getFileResource();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(securityToken));
final Artifact artifact = findArtifactByFileResource(fileResource)
.orElseThrow(EntityNotFoundException::new);
checkIfArtifactIsAssignedToTarget(securityToken, artifact.getSha1Hash());
final DmfArtifact dmfArtifact = convertDbArtifact(artifact);
authenticationResponse.setArtifact(dmfArtifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1,
artifact.getSha1Hash());
cache.put(downloadId, downloadCache);
authenticationResponse.setDownloadUrl(UriComponentsBuilder
.fromUri(hostnameResolver.resolveHostname().toURI()).path("/api/v1/downloadserver/downloadId/")
.path(tenantAware.getCurrentTenant()).path("/").path(downloadId).build().toUriString());
authenticationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
log.error("Login failed", e);
authenticationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
authenticationResponse.setMessage("Login failed");
} catch (final URISyntaxException e) {
log.error("URI build exception", e);
authenticationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
authenticationResponse.setMessage("Building download URI failed");
} catch (final EntityNotFoundException e) {
final String errorMessage = "Artifact for resource " + fileResource + " not found ";
log.info(errorMessage);
authenticationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authenticationResponse.setMessage(errorMessage);
}
return getMessageConverter().toMessage(authenticationResponse, message.getMessageProperties());
}
}

View File

@@ -11,10 +11,7 @@ package org.eclipse.hawkbit.amqp;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement; import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -24,9 +21,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.FanoutExchange;
@@ -281,20 +276,6 @@ public class AmqpConfiguration {
entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement); entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement);
} }
/**
* Create AMQP handler service bean for authentication messages.
*
* @return handler service bean
*/
@Bean
AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
final DownloadIdCache downloadIdCache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement, final TenantAware tenantAware) {
return new AmqpAuthenticationMessageHandler(rabbitTemplate, authenticationManager, artifactManagement,
downloadIdCache, hostnameResolver, controllerManagement, tenantAware);
}
/** /**
* Create default amqp sender service bean. * Create default amqp sender service bean.
* *
@@ -322,33 +303,6 @@ public class AmqpConfiguration {
return factory; return factory;
} }
/**
* create the authentication bean for controller over amqp.
*
* @param systemManagement
* the systemManagement
* @param controllerManagement
* the controllerManagement
* @param tenantConfigurationManagement
* the tenantConfigurationManagement
* @param tenantAware
* the tenantAware
* @param ddiSecruityProperties
* the ddiSecruityProperties
* @param systemSecurityContext
* the systemSecurityContext
* @return the bean
*/
@Bean
@ConditionalOnMissingBean(AmqpControllerAuthentication.class)
public AmqpControllerAuthentication amqpControllerAuthentication(final SystemManagement systemManagement,
final ControllerManagement controllerManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
return new AmqpControllerAuthentication(systemManagement, controllerManagement, tenantConfigurationManagement,
tenantAware, ddiSecruityProperties, systemSecurityContext);
}
@Bean @Bean
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class) @ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,

View File

@@ -1,165 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.amqp;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
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;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
/**
* A controller which handles the DMF AMQP authentication.
*/
@Slf4j
public class AmqpControllerAuthentication {
private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider();
private List<PreAuthenticationFilter> filterChain;
private final ControllerManagement controllerManagement;
private final SystemManagement systemManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final TenantAware tenantAware;
private final DdiSecurityProperties ddiSecruityProperties;
private final SystemSecurityContext systemSecurityContext;
/**
* Constructor.
*
* @param systemManagement
* @param controllerManagement
* @param tenantConfigurationManagement
* @param tenantAware
* current tenant
* @param ddiSecruityProperties
* security configurations
* @param systemSecurityContext
* security context
*/
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;
this.systemSecurityContext = systemSecurityContext;
}
/**
* Called by spring when bean instantiated and autowired.
*/
@PostConstruct
public void postConstruct() {
addFilter();
}
private void addFilter() {
filterChain = new ArrayList<>(5);
final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
filterChain.add(gatewaySecurityTokenFilter);
final ControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new ControllerPreAuthenticatedSecurityHeaderFilter(
ddiSecruityProperties.getRp().getCnHeader(), ddiSecruityProperties.getRp().getSslIssuerHashHeader(),
tenantConfigurationManagement, tenantAware, systemSecurityContext);
filterChain.add(securityHeaderFilter);
final ControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new ControllerPreAuthenticateSecurityTokenFilter(
tenantConfigurationManagement, controllerManagement, tenantAware, systemSecurityContext);
filterChain.add(securityTokenFilter);
final ControllerPreAuthenticatedAnonymousDownload anonymousDownloadFilter = new ControllerPreAuthenticatedAnonymousDownload(
tenantConfigurationManagement, tenantAware, systemSecurityContext);
filterChain.add(anonymousDownloadFilter);
filterChain.add(new ControllerPreAuthenticatedAnonymousFilter(ddiSecruityProperties));
}
/**
* Performs authentication with the security token.
*
* @param securityToken
* the authentication request object
* @return the authentication object
*/
public Authentication doAuthenticate(final DmfTenantSecurityToken securityToken) {
resolveTenant(securityToken);
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null);
for (final PreAuthenticationFilter filter : filterChain) {
final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, securityToken);
if (authenticationRest != null) {
authentication = authenticationRest;
authentication.setDetails(new TenantAwareAuthenticationDetails(securityToken.getTenant(), true));
break;
}
}
return preAuthenticatedAuthenticationProvider.authenticate(authentication);
}
private void resolveTenant(final DmfTenantSecurityToken securityToken) {
if (securityToken.getTenant() == null) {
securityToken.setTenant(systemSecurityContext
.runAsSystem(() -> systemManagement.getTenantMetadata(securityToken.getTenantId()).getTenant()));
}
}
private static PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthenticationFilter filter,
final DmfTenantSecurityToken securityToken) {
if (!filter.isEnable(securityToken)) {
return null;
}
final Object principal = filter.getPreAuthenticatedPrincipal(securityToken);
final Object credentials = filter.getPreAuthenticatedCredentials(securityToken);
if (principal == null) {
log.debug("No pre-authenticated principal found in message");
return null;
}
log.debug("preAuthenticatedPrincipal = {} trying to authenticate", principal);
return new PreAuthenticatedAuthenticationToken(principal, credentials,
filter.getSuccessfulAuthenticationAuthorities());
}
}

View File

@@ -1,402 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.amqp;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
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.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
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;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
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.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
*
* Test Amqp controller authentication.
*/
@Feature("Component Tests - Device Management Federation API")
@Story("AmqpController Authentication Test")
@ExtendWith(MockitoExtension.class)
public class AmqpControllerAuthenticationTest {
private static final String SHA1 = "12345";
private static final Long ARTIFACT_ID = 1123L;
private static final String TENANT = "DEFAULT";
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 final MessageConverter messageConverter = new Jackson2JsonMessageConverter();
private AmqpControllerAuthentication authenticationManager;
private JpaArtifact testArtifact;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private SystemManagement systemManagement;
@Mock
private DownloadIdCache cacheMock;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock
private Target targetMock;
@Mock
private UserAuthoritiesResolver authoritiesResolver;
@Mock
private SecurityContextSerializer securityContextSerializer;
@Mock
private RabbitTemplate rabbitTemplate;
@Mock
private ControllerManagement controllerManagement;
@Mock
private ConfirmationManagement confirmationManagement;
@Mock
private DdiSecurityProperties securityProperties;
@Mock
private Rp rp;
@Mock
private DdiSecurityProperties.Authentication ddiAuthentication;
@Mock
private Anonymous anonymous;
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
.<Boolean> builder().value(Boolean.FALSE).build();
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_TRUE = TenantConfigurationValue
.<Boolean> builder().value(Boolean.TRUE).build();
@BeforeEach
public void before() {
when(securityProperties.getRp()).thenReturn(rp);
when(rp.getSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d");
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
tenantConfigurationManagementMock, tenantAware, securityProperties, systemSecurityContext);
authenticationManager.postConstruct();
testArtifact = new JpaArtifact(SHA1, "afilename",
new JpaSoftwareModule(new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", "a version"));
testArtifact.setId(1L);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagement, new JpaEntityFactory(),
systemSecurityContext, tenantConfigurationManagementMock, confirmationManagement);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock, controllerManagement,
tenantAware);
}
private void mockAuthenticationWithoutPrincipal() {
lenient().when(securityProperties.getAuthentication()).thenReturn(ddiAuthentication);
lenient().when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
lenient().when(anonymous.isEnabled()).thenReturn(false);
}
private void mockSuccessfulAuthentication() throws MalformedURLException {
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID);
}
@Test
@Description("Tests authentication manager without principal")
public void testAuthenticationBadCredentialsWithoutPrincipal() {
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1(SHA1));
mockAuthenticationWithoutPrincipal();
assertThatExceptionOfType(BadCredentialsException.class)
.as("BadCredentialsException was expected since principal was missing")
.isThrownBy(() -> authenticationManager.doAuthenticate(securityToken));
}
@Test
@Description("Tests authentication manager without wrong credential")
public void testAuthenticationBadCredentialsWithWrongCredential() {
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(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.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
assertThatExceptionOfType(BadCredentialsException.class)
.as("BadCredentialsException was expected due to wrong credential")
.isThrownBy(() -> authenticationManager.doAuthenticate(securityToken));
}
@Test
@Description("Tests authentication successful")
public void testSuccessfulAuthentication() {
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(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);
when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID);
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
assertThat(authentication).isNotNull();
}
@Test
@Description("Tests authentication message without principal")
public void testAuthenticationMessageBadCredentialsWithoutPrincipal() {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1(SHA1));
mockAuthenticationWithoutPrincipal();
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.FORBIDDEN.value());
}
@Test
@Description("Tests authentication message without wrong credential")
public void testAuthenticationMessageBadCredentialsWithWrongCredential() {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(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);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.FORBIDDEN.value());
}
@Test
@Description("Tests authentication message successful")
public void successfulMessageAuthentication() throws Exception {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
FileResource.createFileResourceBySha1(SHA1));
mockSuccessfulAuthentication();
when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targetMock));
when(controllerManagement.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) 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 successful with targetId intead of controllerId provided and artifactId instead of SHA1.")
public void successfulMessageAuthenticationWithTargetIdAndArtifactId() throws Exception {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, null, null, TARGET_ID,
FileResource.createFileResourceByArtifactId(ARTIFACT_ID));
mockSuccessfulAuthentication();
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(controllerManagement.hasTargetArtifactAssigned(TARGET_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.get(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact));
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) 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 successful")
public void successfulMessageAuthenticationWithTenantId() throws Exception {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(null, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1(SHA1));
final TenantMetaData tenantMetaData = mock(TenantMetaData.class);
mockSuccessfulAuthentication();
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(controllerManagement.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) 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());
}
private MessageProperties createMessageProperties(final MessageType type) {
return createMessageProperties(type, "MyTest");
}
private MessageProperties createMessageProperties(final MessageType type, final String replyTo) {
final MessageProperties messageProperties = new MessageProperties();
if (type != null) {
messageProperties.setHeader(MessageHeaderKey.TYPE, type.name());
}
messageProperties.setHeader(MessageHeaderKey.TENANT, TENANT);
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
messageProperties.setReplyTo(replyTo);
return messageProperties;
}
}

View File

@@ -22,15 +22,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
@@ -39,9 +34,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate; import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
import org.eclipse.hawkbit.dmf.json.model.DmfAutoConfirmation; import org.eclipse.hawkbit.dmf.json.model.DmfAutoConfirmation;
import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing; import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode; import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement; import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
@@ -54,19 +47,14 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionProperties; import org.eclipse.hawkbit.repository.model.ActionProperties;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextSerializer; import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -82,7 +70,6 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.http.HttpStatus;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -97,15 +84,10 @@ public class AmqpMessageHandlerServiceTest {
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class
.getSimpleName() + " was expected, "; .getSimpleName() + " was expected, ";
private static final String SHA1 = "12345";
private static final String VIRTUAL_HOST = "vHost"; private static final String VIRTUAL_HOST = "vHost";
private static final String TENANT = "DEFAULT"; private static final String TENANT = "DEFAULT";
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 AmqpMessageHandlerService amqpMessageHandlerService;
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter; private MessageConverter messageConverter;
@@ -117,67 +99,37 @@ public class AmqpMessageHandlerServiceTest {
@Mock @Mock
private ConfirmationManagement confirmationManagementMock; private ConfirmationManagement confirmationManagementMock;
@Mock @Mock
private EntityFactory entityFactoryMock; private EntityFactory entityFactoryMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock @Mock
private TenantConfigurationManagement tenantConfigurationManagement; private TenantConfigurationManagement tenantConfigurationManagement;
@Mock
private AmqpControllerAuthentication authenticationManagerMock;
@Mock
private ArtifactRepository artifactRepositoryMock;
@Mock
private DownloadIdCache downloadIdCache;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock @Mock
private RabbitTemplate rabbitTemplate; private RabbitTemplate rabbitTemplate;
@Mock
private TenantAware tenantAwareMock;
@Mock @Mock
private UserAuthoritiesResolver authoritiesResolver; private UserAuthoritiesResolver authoritiesResolver;
@Mock @Mock
private SecurityContextSerializer securityContextSerializer; private SecurityContextSerializer securityContextSerializer;
@Captor @Captor
private ArgumentCaptor<Map<String, String>> attributesCaptor; private ArgumentCaptor<Map<String, String>> attributesCaptor;
@Captor @Captor
private ArgumentCaptor<String> targetIdCaptor; private ArgumentCaptor<String> targetIdCaptor;
@Captor @Captor
private ArgumentCaptor<String> initiatorCaptor; private ArgumentCaptor<String> initiatorCaptor;
@Captor @Captor
private ArgumentCaptor<String> remarkCaptor; private ArgumentCaptor<String> remarkCaptor;
@Captor @Captor
private ArgumentCaptor<String> targetNameCaptor; private ArgumentCaptor<String> targetNameCaptor;
@Captor @Captor
private ArgumentCaptor<String> targetTypeNameCaptor; private ArgumentCaptor<String> targetTypeNameCaptor;
@Captor @Captor
private ArgumentCaptor<URI> uriCaptor; private ArgumentCaptor<URI> uriCaptor;
@Captor @Captor
private ArgumentCaptor<UpdateMode> modeCaptor; private ArgumentCaptor<UpdateMode> modeCaptor;
@BeforeEach @BeforeEach
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
public void before() throws Exception { public void before() {
messageConverter = new Jackson2JsonMessageConverter(); messageConverter = new Jackson2JsonMessageConverter();
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE) final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE)
@@ -191,9 +143,6 @@ public class AmqpMessageHandlerServiceTest {
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock, amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock, systemSecurityContext, tenantConfigurationManagement, controllerManagementMock, entityFactoryMock, systemSecurityContext, tenantConfigurationManagement,
confirmationManagementMock); confirmationManagementMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock,
controllerManagementMock, tenantAwareMock);
} }
@Test @Test
@@ -549,85 +498,12 @@ public class AmqpMessageHandlerServiceTest {
VIRTUAL_HOST)); VIRTUAL_HOST));
} }
@Test
@Description("Tests that an download request is denied for an artifact which does not exists")
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = createMessage(securityToken, messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).as("Message body should not null").isNotNull();
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
.isEqualTo(HttpStatus.NOT_FOUND.value());
}
@Test
@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 DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = createMessage(securityToken, messageProperties);
final Artifact localArtifactMock = mock(Artifact.class);
when(artifactManagementMock.findFirstBySHA1(anyString())).thenReturn(Optional.of(localArtifactMock));
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).as("Message body should not null").isNotNull();
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
.isEqualTo(HttpStatus.NOT_FOUND.value());
}
@Test
@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 DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = createMessage(securityToken, messageProperties);
// mock
final Artifact localArtifactMock = mock(Artifact.class);
when(localArtifactMock.getSha1Hash()).thenReturn(SHA1);
when(localArtifactMock.getMd5Hash()).thenReturn("md5");
when(localArtifactMock.getSize()).thenReturn(1L);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(localArtifactMock));
when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), SHA1))
.thenReturn(true);
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DmfDownloadResponse downloadResponse = (DmfDownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).as("Message body should not null").isNotNull();
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
.isEqualTo(HttpStatus.OK.value());
assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L);
assertThat(downloadResponse.getArtifact().getHashes().getSha1()).as("Wrong sha1 hash").isEqualTo(SHA1);
assertThat(downloadResponse.getArtifact().getHashes().getMd5()).as("Wrong md5 hash").isEqualTo("md5");
assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong")
.startsWith("http://localhost/api/v1/downloadserver/downloadId/");
}
@Test @Test
@Description("Test next update is provided on finished action") @Description("Test next update is provided on finished action")
public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException { public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException {
// Mock // Mock
final Action action = createActionWithTarget(22L, Status.FINISHED); final Action action = createActionWithTarget(22L);
when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action)); when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action));
when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action); when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action);
final ActionStatusBuilder builder = mock(ActionStatusBuilder.class); final ActionStatusBuilder builder = mock(ActionStatusBuilder.class);
@@ -650,8 +526,8 @@ public class AmqpMessageHandlerServiceTest {
final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class); final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class);
final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class); final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class);
verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), verify(amqpMessageDispatcherServiceMock, times(1))
targetCaptor.capture(), any(Map.class)); .sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), targetCaptor.capture(), any(Map.class));
final ActionProperties actionProperties = actionPropertiesCaptor.getValue(); final ActionProperties actionProperties = actionPropertiesCaptor.getValue();
assertThat(actionProperties).isNotNull(); assertThat(actionProperties).isNotNull();
assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT"); assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT");
@@ -664,7 +540,7 @@ public class AmqpMessageHandlerServiceTest {
public void feedBackCodeIsPersistedInMessages() throws IllegalAccessException { public void feedBackCodeIsPersistedInMessages() throws IllegalAccessException {
// Mock // Mock
final Action action = createActionWithTarget(22L, Status.FINISHED); final Action action = createActionWithTarget(22L);
when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action)); when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action));
when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action); when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action);
final ActionStatusBuilder builder = new JpaActionStatusBuilder(); final ActionStatusBuilder builder = new JpaActionStatusBuilder();
@@ -770,7 +646,7 @@ public class AmqpMessageHandlerServiceTest {
return messageProperties; return messageProperties;
} }
private Action createActionWithTarget(final Long targetId, final Status status) throws IllegalAccessException { private Action createActionWithTarget(final Long targetId) throws IllegalAccessException {
// is needed for the creation of targets // is needed for the creation of targets
initializeSecurityTokenGenerator(); initializeSecurityTokenGenerator();

View File

@@ -1,462 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.integration;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.amqp.AmqpProperties;
import org.eclipse.hawkbit.amqp.DmfApiConfiguration;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@Feature("Component Tests - Device Management Federation API")
@Story("Amqp Authentication Message Handler")
@SpringBootTest(classes = { DmfApiConfiguration.class })
public class AmqpAuthenticationMessageHandlerIntegrationTest extends AbstractAmqpIntegrationTest {
private static final String TARGET_SECRUITY_TOKEN = "12345";
private static final String TARGET_TOKEN_HEADER = "TargetToken " + TARGET_SECRUITY_TOKEN;
private static final String TENANT_EXIST = "DEFAULT";
private static final String TARGET = "NewDmfTarget";
@Autowired
private AmqpProperties amqpProperties;
@BeforeEach
public void testSetup() {
enableTargetTokenAuthentication();
}
@Test
@Description("Tests wrong content type. This message is invalid and should not be requeued. Additional the receive message is null")
public void wrongContentType() {
final Message createAndSendMessage = getDmfClient()
.sendAndReceive(new Message("".getBytes(), new MessageProperties()));
assertThat(createAndSendMessage).isNull();
assertEmptyAuthenticationMessageCount();
}
@Test
@Description("Tests a null message. This message is invalid and should not be requeued. Additional the receive message is null")
public void securityTokenIsNull() {
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(null);
assertThat(createAndSendMessage).isNull();
assertEmptyAuthenticationMessageCount();
}
@Test
@Description("Tenant in the message is null. This message is invalid and should not be requeued. Additional the receive message is null")
public void securityTokenTenantIsNull() {
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(null, TARGET,
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken);
assertThat(createAndSendMessage).isNull();
assertEmptyAuthenticationMessageCount();
}
@Test
@Description("Target in the message is null.This message is invalid and should not requeued. Additional the receive message is null")
public void securityTokenFileResourceIsNull() {
enableAnonymousAuthentication();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, null);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND, null);
}
@Test
@Description("Verify that login fails if the given credential not match.")
public void loginFailedBadCredentials() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
false);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(createAndSendMessage, HttpStatus.FORBIDDEN, "Login failed");
}
@Test
@Description("Verify that the receive message contains a 404 code,if the artifact could not found")
public void fileResourceGetSha1InSecurityTokenIsNull() {
enableAnonymousAuthentication();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(null));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 404 code , if the distributionSet is not assigned to the target")
public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsButIsNotAssigned() {
final Target target = createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final String sha1Hash = artifacts.get(0).getSha1Hash();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(target.getTenant(), target.getId(), null,
FileResource.createFileResourceBySha1(sha1Hash));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND, "Artifact for resource FileResource [sha1=" + sha1Hash
+ ", artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 404 code, if there is no artifact for the given sha1")
public void artifactForFileResourceSHA1NotFound() {
enableAnonymousAuthentication();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=12345, artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 404 code, if there is no existing target for the given controller id")
public void artifactForFileResourceSHA1FoundTargetNotExists() {
enableAnonymousAuthentication();
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final String sha1Hash = artifacts.get(0).getSha1Hash();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(sha1Hash));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND, "Artifact for resource FileResource [sha1=" + sha1Hash
+ ", artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 404 code, if there is no existing artifact for the target")
public void artifactForFileResourceSHA1FoundTargetExistsButNotAssigned() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(artifacts.get(0).getSha1Hash()));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=2f532b93ed23b4341a81dc9b1ee8a1c44b5526ab, artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 200 code and a artifact for the existing controller id ")
public void artifactForFileResourceSHA1FoundTargetExistsIsAssigned() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final Artifact artifact = artifacts.get(0);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
assignDistributionSet(distributionSet.getId(), TARGET);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
@Test
@Description("Verify that the receive message contains a 200 code and a artifact for the existing target id ")
public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsIsAssigned() {
final Target target = createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final Artifact artifact = artifacts.get(0);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, target.getId(), null,
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
assignDistributionSet(distributionSet.getId(), TARGET);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
@Test
@Description("Verify that the receive message contains a 200 code and a artifact without a controller id (anonymous enabled)")
public void anonymousAuthentification() {
enableAnonymousAuthentication();
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final Artifact artifact = artifacts.get(0);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, null, null,
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
@Test
@Description("Artifact is downloaded anonymous as there is not target id or controller id assigned to the target.")
public void targetTokenAuthentification() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final Artifact artifact = artifacts.get(0);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceBySha1(artifact.getSha1Hash()));
assignDistributionSet(distributionSet.getId(), TARGET);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
@Test
@Description("Verify that the receive message contains a 404, if there is no artifact to the given filename")
public void artifactForFileResourceFileNameNotFound() {
enableAnonymousAuthentication();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceByFilename("Test.txt"));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=Test.txt] not found ");
}
@Test
@Description("Verify that the receive message contains a 404, if there is no distribution set assigned to the target")
public void artifactForFileResourceFileNameFoundTargetExistsButNotAssigned() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.createFileResourceByFilename(artifacts.get(0).getFilename()));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=filename0] not found ");
}
@Test
@Description("Verify that the receive message contains a 404, if there is no exisiting target")
public void artifactForFileResourceArtifactIdFoundTargetNotExists() {
enableAnonymousAuthentication();
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifacts.get(0).getId());
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND, "Artifact for resource FileResource [sha1=null, artifactId="
+ artifacts.get(0).getId() + ", filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 200 and a artifact for the given artifact id")
public void artifactForFileResourceArtifactIdFoundTargetExistsIsAssigned() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final Artifact artifact = artifacts.get(0);
final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifact.getId());
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
assignDistributionSet(distributionSet.getId(), TARGET);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
@Test
@Description("Verify that the receive message contains a 404, if there is no artifact to the given softwareModuleFilename")
public void artifactForFileResourceSoftwareModuleFilenameNotFound() {
enableAnonymousAuthentication();
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET,
FileResource.softwareModuleFilename(1L, "Test.txt"));
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 404, if there is no existing target for the file resource")
public void artifactForFileResourceSoftwareModuleFilenameFoundTargetNotExists() {
enableAnonymousAuthentication();
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get();
final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule);
final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(),
softwareModule.getArtifact(artifact.getId()).get().getFilename());
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyResult(returnMessage, HttpStatus.NOT_FOUND,
"Artifact for resource FileResource [sha1=null, artifactId=null, filename=null] not found ");
}
@Test
@Description("Verify that the receive message contains a 200 and a artifact, if there is a existing artifct fpt the for the given softwareModuleFilename")
public void artifactForFileResourceSoftwareModuleFilenameFoundTargetExistsIsAssigned() {
createTarget(TARGET);
final DistributionSet distributionSet = createDistributionSet();
final List<Artifact> artifacts = createArtifacts(distributionSet);
final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get();
final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule);
final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(),
softwareModule.getArtifact(artifact.getId()).get().getFilename());
final DmfTenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource);
assignDistributionSet(distributionSet.getId(), TARGET);
final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken);
verifyOkResult(returnMessage, artifact);
}
private void verifyOkResult(final Message returnMessage, final Artifact artifact) {
final DmfDownloadResponse convertedMessage = verifyResult(returnMessage, HttpStatus.OK, null);
assertThat(convertedMessage.getDownloadUrl()).isNotNull();
assertThat(convertedMessage.getArtifact()).isNotNull();
assertThat(convertedMessage.getArtifact().getLastModified())
.isEqualTo(artifactManagement.findFirstBySHA1(artifact.getSha1Hash()).get().getCreatedAt());
assertThat(convertedMessage.getArtifact().getHashes().getSha1()).isEqualTo(artifact.getSha1Hash());
}
private void enableAnonymousAuthentication() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
true);
tenantConfigurationManagement.addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, false);
}
private void enableTargetTokenAuthentication() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED,
false);
tenantConfigurationManagement.addOrUpdateConfiguration(
TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, true);
}
private Target createTarget(final String controllerId) {
return targetManagement.create(
entityFactory.target().create().controllerId(controllerId).securityToken(TARGET_SECRUITY_TOKEN));
}
private DmfTenantSecurityToken createTenantSecurityToken(final String tenant, final String controllerId,
final FileResource fileResource) {
final DmfTenantSecurityToken tenantSecurityToken = new DmfTenantSecurityToken(tenant, controllerId,
fileResource);
tenantSecurityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER);
return tenantSecurityToken;
}
private DmfTenantSecurityToken createTenantSecurityToken(final String tenant, final Long targetId,
final String controllerId, final FileResource fileResource) {
final DmfTenantSecurityToken tenantSecurityToken = new DmfTenantSecurityToken(tenant, null, controllerId,
targetId, fileResource);
tenantSecurityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER);
return tenantSecurityToken;
}
private DistributionSet createDistributionSet() {
return testdataFactory.createDistributionSet("one");
}
private List<Artifact> createArtifacts(final DistributionSet distributionSet) {
final List<Artifact> artifacts = new ArrayList<>();
for (final SoftwareModule module : distributionSet.getModules()) {
artifacts.addAll(testdataFactory.createArtifacts(module.getId()));
}
return artifacts;
}
private DmfDownloadResponse verifyResult(final Message returnMessage, final HttpStatus expectedStatus,
final String expectedMessage) {
final DmfDownloadResponse convertedMessage = (DmfDownloadResponse) getDmfClient().getMessageConverter()
.fromMessage(returnMessage);
assertThat(convertedMessage.getResponseCode()).isEqualTo(expectedStatus.value());
if (!StringUtils.isEmpty(expectedMessage)) {
assertThat(convertedMessage.getMessage()).isEqualTo(expectedMessage);
}
return convertedMessage;
}
private Message sendAndReceiveAuthenticationMessage(final DmfTenantSecurityToken securityToken) {
return getDmfClient().sendAndReceive(createMessage(securityToken, new MessageProperties()));
}
private Artifact findArtifactOfSoftwareModule(final List<Artifact> artifacts, final SoftwareModule softwareModule) {
return artifacts.stream().filter(space -> space.getSoftwareModule().getId().equals(softwareModule.getId()))
.findFirst().get();
}
@Override
protected String getExchange() {
return AmqpSettings.AUTHENTICATION_EXCHANGE;
}
private int getAuthenticationMessageCount() {
return Integer.parseInt(getRabbitAdmin().getQueueProperties(amqpProperties.getAuthenticationReceiverQueue())
.get(RabbitAdmin.QUEUE_MESSAGE_COUNT).toString());
}
private void assertEmptyAuthenticationMessageCount() {
assertThat(getAuthenticationMessageCount()).isEqualTo(0);
}
}

View File

@@ -1,34 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.dmf.json.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* The download response JSON representation.
*/
@Data
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DmfDownloadResponse {
@JsonProperty
private String downloadUrl;
@JsonProperty
private DmfArtifact artifact;
@JsonProperty
private int responseCode;
@JsonProperty
private String message;
}

View File

@@ -22,9 +22,7 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.util.UrlUtils; import org.eclipse.hawkbit.util.UrlUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -159,8 +157,7 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac
private DmfTenantSecurityToken createTenantSecurityTokenVariables(final HttpServletRequest request, private DmfTenantSecurityToken createTenantSecurityTokenVariables(final HttpServletRequest request,
final String tenant, final String controllerId) { final String tenant, final String controllerId) {
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(tenant, null, controllerId, null, final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(tenant, null, controllerId, null);
FileResource.createFileResourceBySha1(""));
Collections.list(request.getHeaderNames()) Collections.list(request.getHeaderNames())
.forEach(header -> securityToken.putHeader(header, request.getHeader(header))); .forEach(header -> securityToken.putHeader(header, request.getHeader(header)));

View File

@@ -1,68 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.security;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
/**
* Extracts download or upload id from the request URI security token and set
* the security context.
*/
@Slf4j
public class HttpDownloadAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
public static final String REQUEST_ID_REGEX_PATTERN = ".*\\/downloadId\\/.*";
private final Pattern pattern;
private final DownloadIdCache downloadIdCache;
/**
* Constructor.
*
* @param downloadIdCache
* the cache
*/
public HttpDownloadAuthenticationFilter(final DownloadIdCache downloadIdCache) {
this.downloadIdCache = downloadIdCache;
this.pattern = Pattern.compile(REQUEST_ID_REGEX_PATTERN);
}
private Object getDownloadByUri(final String requestURI) {
final Matcher matcher = pattern.matcher(requestURI);
if (!matcher.matches()) {
return null;
}
log.debug("retrieving id from URI request {}", requestURI);
final String[] groups = requestURI.split("\\/");
final String id = groups[groups.length - 1];
if (id == null) {
return null;
}
return downloadIdCache.get(id);
}
@Override
protected Object getPreAuthenticatedPrincipal(final HttpServletRequest request) {
return getDownloadByUri(request.getRequestURI());
}
@Override
protected Object getPreAuthenticatedCredentials(final HttpServletRequest request) {
return getDownloadByUri(request.getRequestURI());
}
}

View File

@@ -24,8 +24,6 @@ import org.eclipse.hawkbit.api.PropertyBasedArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemProperties; import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemProperties;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemRepository; import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.cache.DefaultDownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager; import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter; import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter;
import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.im.authentication.SpRole;
@@ -150,18 +148,6 @@ public class TestConfiguration implements AsyncConfigurer {
return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware); return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware);
} }
/**
* Bean for the download id cache.
*
* @param cacheManager
* The {@link CacheManager}
* @return the cache
*/
@Bean
DownloadIdCache downloadIdCache(final CacheManager cacheManager) {
return new DefaultDownloadIdCache(cacheManager);
}
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
SimpleApplicationEventMulticaster applicationEventMulticaster(final ApplicationEventFilter applicationEventFilter) { SimpleApplicationEventMulticaster applicationEventMulticaster(final ApplicationEventFilter applicationEventFilter) {
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new FilterEnabledApplicationEventPublisher( final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new FilterEnabledApplicationEventPublisher(

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.api;
import java.io.InputStream;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* A resource for download artifacts.
*/
@FunctionalInterface
@Tag(name = "Download artifact by ID", description = "API to download artifacts by download Id.")
// no request mapping specified here to avoid CVE-2021-22044 in Feign client
public interface MgmtDownloadRestApi {
/**
* Handles the GET request for downloading an artifact.
*
* @param tenant the download belongs to
* @param downloadId the generated download id
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if successful
*/
@GetMapping(value = MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE
+ MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING)
@ResponseBody
ResponseEntity<InputStream> downloadArtifactByDownloadId(@PathVariable("tenant") String tenant,
@PathVariable("downloadId") String downloadId);
}

View File

@@ -53,10 +53,6 @@ public final class MgmtRestConstants {
*/ */
public static final String SOFTWAREMODULE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/softwaremodules"; public static final String SOFTWAREMODULE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/softwaremodules";
public static final String DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE = "/api/" + API_VERSION + "/downloadserver";
public static final String DOWNLOAD_ID_V1_REQUEST_MAPPING = "/downloadId/{tenant}/{downloadId}";
/** /**
* The base URL mapping for the spring acuator management context path. * The base URL mapping for the spring acuator management context path.
*/ */

View File

@@ -1,83 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.InputStream;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadRestApi;
import org.eclipse.hawkbit.rest.util.FileStreamingUtil;
import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder;
import org.springframework.context.annotation.Scope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
/**
* A resource for download artifacts.
*/
@Slf4j
@RestController
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class MgmtDownloadResource implements MgmtDownloadRestApi {
private final ArtifactRepository artifactRepository;
private final DownloadIdCache downloadIdCache;
MgmtDownloadResource(final ArtifactRepository artifactRepository, final DownloadIdCache downloadIdCache) {
this.artifactRepository = artifactRepository;
this.downloadIdCache = downloadIdCache;
}
@Override
@ResponseBody
public ResponseEntity<InputStream> downloadArtifactByDownloadId(@PathVariable("tenant") final String tenant,
@PathVariable("downloadId") final String downloadId) {
try {
final DownloadArtifactCache artifactCache = downloadIdCache.get(downloadId);
if (artifactCache == null) {
log.warn("Download Id {} could not be found", downloadId);
return ResponseEntity.notFound().build();
}
AbstractDbArtifact artifact = null;
if (DownloadType.BY_SHA1 == artifactCache.getDownloadType()) {
artifact = artifactRepository.existsByTenantAndSha1(tenant, artifactCache.getId())
? artifactRepository.getArtifactBySha1(tenant, artifactCache.getId())
: null;
} else {
log.warn("Download Type {} not supported", artifactCache.getDownloadType());
}
if (artifact == null) {
log.warn("Artifact with cached id {} and download type {} could not be found.",
artifactCache.getId(), artifactCache.getDownloadType());
return ResponseEntity.notFound().build();
}
return FileStreamingUtil.writeFileResponse(artifact, downloadId, 0L,
RequestResponseContextHolder.getHttpServletResponse(),
RequestResponseContextHolder.getHttpServletRequest(), null);
} finally {
downloadIdCache.evict(downloadId);
}
}
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@Feature("Component Tests - Management API")
@Story("Download Resource")
public class MgmtDownloadResourceTest extends AbstractManagementApiIntegrationTest {
@Autowired
private DownloadIdCache downloadIdCache;
private static final String DOWNLOAD_ID_SHA1 = "downloadIdSha1";
private static final String DOWNLOAD_ID_NOT_AVAILABLE = "downloadIdNotAvailable";
@BeforeEach
public void setupCache() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("Test");
final SoftwareModule softwareModule = distributionSet.getModules().stream().findAny().get();
final Artifact artifact = testdataFactory.createArtifacts(softwareModule.getId()).stream().findAny().get();
downloadIdCache.put(DOWNLOAD_ID_SHA1, new DownloadArtifactCache(DownloadType.BY_SHA1, artifact.getSha1Hash()));
}
@Test
@Description("This test verifies the call of download artifact without a valid download id fails.")
public void testNoDownloadIdAvailable() throws Exception {
mvc.perform(get(
MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE
+ MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
tenantAware.getCurrentTenant(), DOWNLOAD_ID_NOT_AVAILABLE)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
}
@Test
@Description("This test verifies the call of download artifact works and the download id will be removed.")
public void testDownload() throws Exception {
mvc.perform(get(
MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE
+ MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
tenantAware.getCurrentTenant(), DOWNLOAD_ID_SHA1)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// because cache is empty
mvc.perform(get(
MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE
+ MgmtRestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
tenantAware.getCurrentTenant(), DOWNLOAD_ID_SHA1)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
}
}

View File

@@ -17,11 +17,9 @@ import java.util.ListIterator;
import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.RepresentationModel;
/** /**
* List that extends ResourceSupport to ensure that links in content are in HAL * List that extends RepresentationModel to ensure that links in content are in HAL format.
* format.
* *
* @param <T> * @param <T> of the response content
* of the response content
*/ */
public class ResponseList<T> extends RepresentationModel<ResponseList<T>> implements List<T> { public class ResponseList<T> extends RepresentationModel<ResponseList<T>> implements List<T> {

View File

@@ -9,7 +9,6 @@
*/ */
package org.eclipse.hawkbit.security; package org.eclipse.hawkbit.security;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@@ -18,116 +17,66 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/** /**
* JSON representation to authenticate a tenant. * JSON representation to authenticate a tenant.
*/ */
@Data
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class DmfTenantSecurityToken { public class DmfTenantSecurityToken {
public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_HEADER = "Authorization";
@JsonProperty(required = false) @JsonProperty
private String tenant; private String tenant;
@JsonProperty(required = false) @JsonProperty
private final Long tenantId; private final Long tenantId;
@JsonProperty(required = false) @JsonProperty
private final String controllerId; private final String controllerId;
@JsonProperty(required = false) @JsonProperty
private final Long targetId; private final Long targetId;
@JsonProperty(required = false) @JsonProperty
private Map<String, String> headers; private Map<String, String> headers;
@JsonProperty(required = false)
private final FileResource fileResource;
/** /**
* Constructor. * Constructor.
* *
* @param tenant * @param tenant the tenant for the security token
* the tenant for the security token * @param tenantId alternative tenant identification by technical ID
* @param tenantId * @param controllerId the ID of the controller for the security token
* alternative tenant identification by technical ID * @param targetId alternative target 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 @JsonCreator
public DmfTenantSecurityToken(@JsonProperty("tenant") final String tenant, public DmfTenantSecurityToken(@JsonProperty("tenant") final String tenant,
@JsonProperty("tenantId") final Long tenantId, @JsonProperty("controllerId") final String controllerId, @JsonProperty("tenantId") final Long tenantId, @JsonProperty("controllerId") final String controllerId,
@JsonProperty("targetId") final Long targetId, @JsonProperty("targetId") final Long targetId) {
@JsonProperty("fileResource") final FileResource fileResource) {
this.tenant = tenant; this.tenant = tenant;
this.tenantId = tenantId; this.tenantId = tenantId;
this.controllerId = controllerId; this.controllerId = controllerId;
this.targetId = targetId; this.targetId = targetId;
this.fileResource = fileResource;
} }
/** /**
* Constructor. * Constructor.
* *
* @param tenant * @param tenant the tenant for the security token
* the tenant for the security token * @param controllerId the ID of the controller for the security token
* @param controllerId
* the ID of the controller for the security token
* @param fileResource
* the file to obtain
*/ */
public DmfTenantSecurityToken(final String tenant, final String controllerId, final FileResource fileResource) { public DmfTenantSecurityToken(final String tenant, final String controllerId) {
this(tenant, null, controllerId, null, fileResource); this(tenant, null, controllerId, null);
} }
/** /**
* Constructor. * Constructor.
* *
* @param tenantId * @param tenantId the tenant for the security token
* the tenant for the security token * @param targetId target identification by technical ID
* @param targetId
* target identification by technical ID
* @param fileResource
* the file to obtain
*/ */
public DmfTenantSecurityToken(final Long tenantId, final Long targetId, final FileResource fileResource) { public DmfTenantSecurityToken(final Long tenantId, final Long targetId) {
this(null, tenantId, null, targetId, fileResource); this(null, tenantId, null, targetId);
}
public void setTenant(final String tenant) {
this.tenant = tenant;
}
public String getTenant() {
return tenant;
}
public String getControllerId() {
return controllerId;
}
public Map<String, String> getHeaders() {
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;
} }
/** /**
@@ -145,21 +94,13 @@ public class DmfTenantSecurityToken {
return headers.get(name); return headers.get(name);
} }
public void setHeaders(final Map<String, String> headers) {
this.headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
this.headers.putAll(headers);
}
/** /**
* Associates the specified header value with the specified name. * Associates the specified header value with the specified name.
* *
* @param name * @param name of the header
* of the header * @param value of the header
* @param value
* of the header
* *
* @return the previous value associated with the <tt>name</tt>, or * @return the previous value associated with the <tt>name</tt>, or <tt>null</tt> if there was no mapping for <tt>name</tt>.
* <tt>null</tt> if there was no mapping for <tt>name</tt>.
*/ */
public String putHeader(final String name, final String value) { public String putHeader(final String name, final String value) {
if (headers == null) { if (headers == null) {
@@ -167,161 +108,4 @@ public class DmfTenantSecurityToken {
} }
return headers.put(name, value); 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
* or by filename.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public static class FileResource {
@JsonProperty(required = false)
private String sha1;
@JsonProperty(required = false)
private Long artifactId;
@JsonProperty(required = false)
private String filename;
@JsonProperty(required = false)
private SoftwareModuleFilenameResource softwareModuleFilenameResource;
public String getSha1() {
return sha1;
}
public void setSha1(final String sha1) {
this.sha1 = sha1;
}
public String getFilename() {
return filename;
}
public void setFilename(final String filename) {
this.filename = filename;
}
public SoftwareModuleFilenameResource getSoftwareModuleFilenameResource() {
return softwareModuleFilenameResource;
}
public void setSoftwareModuleFilenameResource(
final SoftwareModuleFilenameResource softwareModuleFilenameResource) {
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.
*
* @param sha1
* the SHA1 key of the file to obtain
* @return the {@link FileResource} with SHA1 key set
*/
public static FileResource createFileResourceBySha1(final String sha1) {
final FileResource resource = new FileResource();
resource.sha1 = sha1;
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.
*
* @param filename
* the filename of the file to obtain
* @return the {@link FileResource} with filename set
*/
public static FileResource createFileResourceByFilename(final String filename) {
final FileResource resource = new FileResource();
resource.filename = filename;
return resource;
}
/**
* factory method to create a file resource for an softwaremodule +
* filename lookup, because an filename is not globally unique but
* within a softwaremodule.
*
* @param softwareModuleId
* the ID of the software module which contains the artifact
* @param filename
* the name of file to obtain within the software module
* @return the {@link FileResource} with artifactId set
*/
public static FileResource softwareModuleFilename(final Long softwareModuleId, final String filename) {
final FileResource resource = new FileResource();
resource.softwareModuleFilenameResource = new SoftwareModuleFilenameResource(softwareModuleId, filename);
return resource;
}
@Override
public String toString() {
return "FileResource [sha1=" + sha1 + ", artifactId=" + artifactId + ", filename=" + filename + "]";
}
/**
* Inner class which holds the pointer to an artifact based on the
* softwaremoduleId and the filename.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public static class SoftwareModuleFilenameResource {
@JsonProperty(required = false)
private Long softwareModuleId;
@JsonProperty(required = false)
private String filename;
/**
* Constructor.
*
* @param softwareModuleId
* the ID of the softwaremodule
* @param filename
* the name of the file of the artifact within the
* softwaremodule
*/
@JsonCreator
public SoftwareModuleFilenameResource(@JsonProperty("softwareModuleId") final Long softwareModuleId,
@JsonProperty("filename") final String filename) {
this.softwareModuleId = softwareModuleId;
this.filename = filename;
}
public Long getSoftwareModuleId() {
return softwareModuleId;
}
public String getFilename() {
return filename;
}
public void setSoftwareModuleId(final Long softwareModuleId) {
this.softwareModuleId = softwareModuleId;
}
public void setFilename(final String filename) {
this.filename = filename;
}
}
}
}

View File

@@ -16,7 +16,6 @@ import java.util.Collection;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -132,8 +131,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilterTest {
} }
private static DmfTenantSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) { private static DmfTenantSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE, final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE);
FileResource.createFileResourceBySha1("12345"));
securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE); securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue); securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
return securityToken; return securityToken;

View File

@@ -32,16 +32,6 @@ Every node has multiple schedulers which run after a defined period of time. All
## Known constraints ## Known constraints
### UI sessions
As of today hawkBit is not storing user sessions in a shared, cluster wide cache. Session is only bound to the node where the login took place. If this node is going down for whatever reason, the session is lost and the user is forced to login again.
In case that's not an option, you can help yourself by introducing a shared session cache based on e.g. Redis.
Furthermore hawkBit is not supporting session stickiness out of the box either. However most of the well known load balancer out there can solve this issue.
### Caching of download IDs
The downloadId is generated and stored in the DownloadIdCache. It is used for downloading an artifact.
In hawkBit exists an interface called "[DownloadIdCache](https://github.com/eclipse/hawkbit/blob/master/hawkbit-core/src/main/java/org/eclipse/hawkbit/cache/DownloadIdCache.java)" and one implementation of it: "DefaultDownloadIdCache". This default implementation can't be used within a cluster. Its containing data is only available inside one node and can't be shared with other nodes. E.g. the downloadId which is stored in this cache after authentication on node A can only be used for downloading the artifact by node A.
In a cluster-capable environment this fact can lead to issues as it could happen, that the downloadId is stored on node A and node B would like to download the artifact by means of the downloadId which is not available on node B. To solve this issue you can use a cluster-shared cache e.g. Redis or create a new cluster-aware implementation of the interface "DownloadIdCache".
### Denial-of-Service (DoS) filter ### Denial-of-Service (DoS) filter
hawkBit owns the feature of guarding itself from DoS attacks, a [DoS filter](https://github.com/eclipse/hawkbit/blob/master/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java). It reduces the maximum number of requests per seconds which can be configured for read and write requests. hawkBit owns the feature of guarding itself from DoS attacks, a [DoS filter](https://github.com/eclipse/hawkbit/blob/master/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java). It reduces the maximum number of requests per seconds which can be configured for read and write requests.
This mechanism is only working for every node separately, i.e. in a cluster environment the worst-case behaviour would be that the maximum number of requests per seconds will be increased to its product if every request is handled by a different node. This mechanism is only working for every node separately, i.e. in a cluster environment the worst-case behaviour would be that the maximum number of requests per seconds will be increased to its product if every request is handled by a different node.