Support user consent flow (#1293)

* Introduce user consent flow
* Add permissions to confirmation management
* rename from consent to confirmation
* Reformat code. Remove unused imports. Change and add permission checks when configuring auto-confirmation.
* Do not include null values for DDI confirmation base endpoint
* fix confirmation required checkbox id
* Remove unused import. Fix consume/produce type of new API's.
* Change term processing to proceeding when activating user consent flow
* Align formatting and extend integration test cases for DMF and DDI.
* Extend DMF test cases to consider auto-confirmation
* Refactor action management to fix problem of handling action status updates on closed actions.
* remove unsupported validation
* use new confirmation api for DMF. Extend test cases.,
* Remove unnecessary fields.
* Extend API documentation for DDI and MGMT API.
* adapt ddi api docs adoc file
* Fixed the duplicate migration version for db files
* fix method to support confirmation
* Fixed PR comments
* Addressed PR comments
* Fixed after merge compilation issue
* Fixed after merge compilation issue
* Fix failing tests in MgmtRolloutResourceTest
* Fixed the permissions issue reflected by integration tests
* Added back the missing line of code lost during merge
* Fix the failing test on Jenkins

Signed-off-by: Stanislav Trailov <stanislav.trailov@bosch.io>
Signed-off-by: Dimitar Shterev <dimitar.shterev@bosch.io>
Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>
Signed-off-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
Co-authored-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
This commit is contained in:
Michael Herdt
2023-01-25 12:11:05 +01:00
committed by GitHub
parent b919ceda5c
commit 21f1569881
208 changed files with 7830 additions and 831 deletions

View File

@@ -14,6 +14,7 @@ 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;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
@@ -275,9 +276,10 @@ public class AmqpConfiguration {
final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
final TenantConfigurationManagement tenantConfigurationManagement,
final ConfirmationManagement confirmationManagement) {
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
entityFactory, systemSecurityContext, tenantConfigurationManagement);
entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement);
}
/**

View File

@@ -36,6 +36,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifact;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DmfBatchDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfConfirmRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest;
@@ -273,6 +274,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
if (action.isCancelingOrCanceled()) {
return createPlainActionRequest(action);
} else if (action.isWaitingConfirmation()) {
return createConfirmRequest(target, action.getId(), softwareModules);
}
return createDownloadAndUpdateRequest(target, action.getId(), softwareModules);
}
@@ -310,6 +313,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* @return {@link EventTopic} to use for message.
*/
private static EventTopic getEventTypeForTarget(final ActionProperties action) {
if (action.isWaitingConfirmation()) {
return EventTopic.CONFIRM;
}
return (Action.ActionType.DOWNLOAD_ONLY == action.getActionType() || !action.isMaintenanceWindowAvailable())
? EventTopic.DOWNLOAD
: EventTopic.DOWNLOAD_AND_INSTALL;
@@ -415,9 +421,16 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return;
}
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = createDownloadAndUpdateRequest(target,
action.getId(), modules);
final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest,
DmfActionRequest request;
if (action.isWaitingConfirmation()) {
// For the moment the confirmation request is the same as download and update request.
// It can be modified not to expose all the software modules in the future.
request = createConfirmRequest(target, action.getId(), modules);
} else {
request = createDownloadAndUpdateRequest(target, action.getId(), modules);
}
final Message message = getMessageConverter().toMessage(request,
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(action)));
amqpSenderService.sendMessage(message, targetAddress);
}
@@ -624,4 +637,25 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
? EventTopic.BATCH_DOWNLOAD
: EventTopic.BATCH_DOWNLOAD_AND_INSTALL;
}
/**
* Creates a Confirmation request.
* @param target the target
* @param actionId the actionId
* @param softwareModules the software modules
* @return
*/
protected DmfConfirmRequest createConfirmRequest(final Target target, final Long actionId, final Map<SoftwareModule,
List<SoftwareModuleMetadata>> softwareModules) {
final DmfConfirmRequest request = new DmfConfirmRequest();
request.setActionId(actionId);
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
//Software modules can be filtered in the future exposing only the needed.
if (softwareModules != null) {
softwareModules.entrySet()
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
}
return request;
}
}

View File

@@ -24,12 +24,15 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
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.DmfUpdateMode;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
@@ -75,6 +78,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
private ControllerManagement controllerManagement;
private ConfirmationManagement confirmationManagement;
private final EntityFactory entityFactory;
@@ -101,18 +105,21 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* the system Security Context
* @param tenantConfigurationManagement
* the tenant configuration Management
* @param confirmationManagement
* the confirmation management
*/
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
final TenantConfigurationManagement tenantConfigurationManagement, final ConfirmationManagement confirmationManagement) {
super(rabbitTemplate);
this.amqpMessageDispatcherService = amqpMessageDispatcherService;
this.controllerManagement = controllerManagement;
this.entityFactory = entityFactory;
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.confirmationManagement = confirmationManagement;
}
/**
@@ -314,6 +321,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
case UPDATE_ATTRIBUTES:
updateAttributes(message);
break;
case UPDATE_AUTO_CONFIRM:
setAutoConfirmationState(message);
break;
default:
logAndThrowMessageError(message, "Got event without appropriate topic.");
break;
@@ -331,7 +341,23 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
getUpdateMode(attributeUpdate));
getUpdateMode(attributeUpdate));
}
private void setAutoConfirmationState(final Message message) {
final DmfAutoConfirmation autoConfirmation = convertMessage(message, DmfAutoConfirmation.class);
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
if (autoConfirmation.isEnabled()) {
LOG.debug("Activate auto-confirmation for device {} using DMF. Initiator: {}. Remark: {}", thingId,
autoConfirmation.getInitiator(), autoConfirmation.getRemark());
final String remark = autoConfirmation.getRemark() == null
? "Activated using Device Management Federation API."
: autoConfirmation.getRemark();
controllerManagement.activateAutoConfirmation(thingId, autoConfirmation.getInitiator(), remark);
} else {
LOG.debug("Deactivate auto-confirmation for device {} using DMF.", thingId);
controllerManagement.deactivateAutoConfirmation(thingId);
}
}
/**
@@ -352,19 +378,27 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
final Status status = mapStatus(message, actionUpdateStatus, action);
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status)
.messages(messages);
actionUpdateStatus.getCode().ifPresent(code -> {
actionStatus.code(code);
actionStatus.message("Device reported status code: " + code);
});
final Action updatedAction;
final Action updatedAction = (Status.CANCELED == status)
? controllerManagement.addCancelActionStatus(actionStatus)
: controllerManagement.addUpdateActionStatus(actionStatus);
if (actionUpdateStatus.getActionStatus() == DmfActionStatus.CONFIRMED) {
updatedAction = confirmationManagement.confirmAction(action.getId(),
actionUpdateStatus.getCode().orElse(null), messages);
} else if (actionUpdateStatus.getActionStatus() == DmfActionStatus.DENIED) {
updatedAction = confirmationManagement.denyAction(action.getId(), actionUpdateStatus.getCode().orElse(null),
messages);
} else {
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status)
.messages(messages);
actionUpdateStatus.getCode().ifPresent(code -> {
actionStatus.code(code);
actionStatus.message("Device reported status code: " + code);
});
updatedAction = (Status.CANCELED == status) ? controllerManagement.addCancelActionStatus(actionStatus)
: controllerManagement.addUpdateActionStatus(actionStatus);
}
if (shouldTargetProceed(updatedAction)) {
if (shouldTargetProceed(updatedAction) || actionUpdateStatus.getActionStatus() == DmfActionStatus.CONFIRMED) {
sendUpdateCommandToTarget(action.getTarget());
}
}
@@ -391,6 +425,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
status = Status.RETRIEVED;
break;
case RUNNING:
case CONFIRMED:
status = Status.RUNNING;
break;
case CANCELED:
@@ -411,6 +446,9 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
case CANCEL_REJECTED:
status = handleCancelRejectedState(message, action);
break;
case DENIED:
status = Status.WAIT_FOR_CONFIRMATION;
break;
default:
logAndThrowMessageError(message, "Status for action does not exisit.");
}