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,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 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.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
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.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
@@ -281,20 +276,6 @@ public class AmqpConfiguration {
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.
*
@@ -322,33 +303,6 @@ public class AmqpConfiguration {
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
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
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 java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Map;
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.MessageHeaderKey;
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.DmfAutoConfirmation;
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.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
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.helper.SecurityTokenGeneratorHolder;
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.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
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.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.junit.jupiter.api.BeforeEach;
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.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.http.HttpStatus;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -97,15 +84,10 @@ public class AmqpMessageHandlerServiceTest {
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class
.getSimpleName() + " was expected, ";
private static final String SHA1 = "12345";
private static final String VIRTUAL_HOST = "vHost";
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 MessageConverter messageConverter;
@@ -117,67 +99,37 @@ public class AmqpMessageHandlerServiceTest {
@Mock
private ConfirmationManagement confirmationManagementMock;
@Mock
private EntityFactory entityFactoryMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock
private TenantConfigurationManagement tenantConfigurationManagement;
@Mock
private AmqpControllerAuthentication authenticationManagerMock;
@Mock
private ArtifactRepository artifactRepositoryMock;
@Mock
private DownloadIdCache downloadIdCache;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock
private RabbitTemplate rabbitTemplate;
@Mock
private TenantAware tenantAwareMock;
@Mock
private UserAuthoritiesResolver authoritiesResolver;
@Mock
private SecurityContextSerializer securityContextSerializer;
@Captor
private ArgumentCaptor<Map<String, String>> attributesCaptor;
@Captor
private ArgumentCaptor<String> targetIdCaptor;
@Captor
private ArgumentCaptor<String> initiatorCaptor;
@Captor
private ArgumentCaptor<String> remarkCaptor;
@Captor
private ArgumentCaptor<String> targetNameCaptor;
@Captor
private ArgumentCaptor<String> targetTypeNameCaptor;
@Captor
private ArgumentCaptor<URI> uriCaptor;
@Captor
private ArgumentCaptor<UpdateMode> modeCaptor;
@BeforeEach
@SuppressWarnings({ "rawtypes", "unchecked" })
public void before() throws Exception {
public void before() {
messageConverter = new Jackson2JsonMessageConverter();
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE)
@@ -191,9 +143,6 @@ public class AmqpMessageHandlerServiceTest {
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock, systemSecurityContext, tenantConfigurationManagement,
confirmationManagementMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock,
controllerManagementMock, tenantAwareMock);
}
@Test
@@ -549,85 +498,12 @@ public class AmqpMessageHandlerServiceTest {
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
@Description("Test next update is provided on finished action")
public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException {
// Mock
final Action action = createActionWithTarget(22L, Status.FINISHED);
final Action action = createActionWithTarget(22L);
when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action));
when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action);
final ActionStatusBuilder builder = mock(ActionStatusBuilder.class);
@@ -650,8 +526,8 @@ public class AmqpMessageHandlerServiceTest {
final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class);
final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class);
verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(actionPropertiesCaptor.capture(),
targetCaptor.capture(), any(Map.class));
verify(amqpMessageDispatcherServiceMock, times(1))
.sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), targetCaptor.capture(), any(Map.class));
final ActionProperties actionProperties = actionPropertiesCaptor.getValue();
assertThat(actionProperties).isNotNull();
assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT");
@@ -664,7 +540,7 @@ public class AmqpMessageHandlerServiceTest {
public void feedBackCodeIsPersistedInMessages() throws IllegalAccessException {
// Mock
final Action action = createActionWithTarget(22L, Status.FINISHED);
final Action action = createActionWithTarget(22L);
when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.of(action));
when(controllerManagementMock.addUpdateActionStatus(any())).thenReturn(action);
final ActionStatusBuilder builder = new JpaActionStatusBuilder();
@@ -770,7 +646,7 @@ public class AmqpMessageHandlerServiceTest {
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
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;
}