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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user