From 8e824429221822553e4ede479c9444ebea998489 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 24 Mar 2020 01:47:21 +0100 Subject: [PATCH 1/8] Manual case operation details tab --- .../midpoint/gui/api/ComponentConstants.java | 3 + .../gui/api/util/WebComponentUtil.java | 18 + .../web/component/prism/show/SceneUtil.java | 12 + .../page/admin/cases/ManualCaseTabPanel.html | 11 + .../page/admin/cases/ManualCaseTabPanel.java | 69 +++ .../web/page/admin/cases/PageCase.java | 559 +++++++++--------- 6 files changed, 398 insertions(+), 274 deletions(-) create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.html create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java index 29acee60ba2..c40c019f57a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java @@ -45,6 +45,9 @@ public class ComponentConstants { public static final QName UI_CASE_TAB_APPROVAL = new QName(NS_COMPONENTS_PREFIX, "caseTabApproval"); public static final String UI_CASE_TAB_APPROVAL_URL = QNameUtil.qNameToUri(UI_CASE_TAB_WORKITEMS); + public static final QName UI_CASE_TAB_MANUAL_OPERATION_DETAILS = new QName(NS_COMPONENTS_PREFIX, "caseTabManualOperationDetails"); + public static final String UI_CASE_TAB_MANUAL_OPERATION_DETAILS_URL = QNameUtil.qNameToUri(UI_CASE_TAB_MANUAL_OPERATION_DETAILS); + public static final QName UI_CASE_TAB_EVENTS = new QName(NS_COMPONENTS_PREFIX, "caseTabEvents"); public static final String UI_CASE_TAB_EVENTS_URL = QNameUtil.qNameToUri(UI_CASE_TAB_EVENTS); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java index da761ad9d67..859cfbc4429 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java @@ -4191,6 +4191,24 @@ public static SceneDto createSceneDto(CaseType caseObject, PageBase pageBase, St return null; } + public static SceneDto createSceneDtoForManualCase(CaseType caseObject, PageBase pageBase, String operation){ + if (caseObject == null || caseObject.getManualProvisioningContext() == null || + caseObject.getManualProvisioningContext().getPendingOperation() == null) { + return null; + } + ObjectReferenceType objectRef = caseObject.getObjectRef(); + OperationResult result = new OperationResult(operation); + Task task = pageBase.createSimpleTask(operation); + try { + Scene deltasScene = SceneUtil.visualizeObjectDeltaType(caseObject.getManualProvisioningContext().getPendingOperation().getDelta(), + "pageWorkItem.delta", pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); + return new SceneDto(deltasScene); + } catch (SchemaException | ExpressionEvaluationException ex){ + LOGGER.error("Unable to create delta visualization for case {}: {}", caseObject, ex.getLocalizedMessage(), ex); + } + return null; + } + public static void workItemApproveActionPerformed(AjaxRequestTarget target, CaseWorkItemType workItem, AbstractWorkItemOutputType workItemOutput, Component formPanel, PrismObject powerDonor, boolean approved, OperationResult result, PageBase pageBase) { if (workItem == null){ diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/show/SceneUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/show/SceneUtil.java index 76243d6805c..f04f51810ba 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/show/SceneUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/show/SceneUtil.java @@ -20,6 +20,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTreeDeltasType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectionObjectDeltaType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import java.util.ArrayList; import java.util.List; @@ -45,4 +46,15 @@ public static Scene visualizeObjectTreeDeltas(ObjectTreeDeltasType deltas, Strin } return new WrapperScene(scenes, displayNameKey); } + + public static Scene visualizeObjectDeltaType(ObjectDeltaType objectDeltaType, String displayNameKey, + PrismContext prismContext, ModelInteractionService modelInteractionService, + ObjectReferenceType objectRef, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException { + List scenes = new ArrayList<>(); + if (objectDeltaType != null) { + ObjectDelta delta = DeltaConvertor.createObjectDelta(objectDeltaType, prismContext); + scenes.add(modelInteractionService.visualizeDelta(delta, false, objectRef, task, result)); + } + return new WrapperScene(scenes, displayNameKey); + } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.html new file mode 100644 index 00000000000..e03f519c6dc --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.html @@ -0,0 +1,11 @@ + + + + +
+ diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.java new file mode 100644 index 00000000000..2b877fc7563 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ManualCaseTabPanel.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.web.page.admin.cases; + +import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.gui.api.prism.PrismObjectWrapper; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.web.component.form.Form; +import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectTabPanel; +import com.evolveum.midpoint.web.component.prism.show.SceneDto; +import com.evolveum.midpoint.web.component.prism.show.ScenePanel; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; + +import org.apache.wicket.model.IModel; + +/** + * Created by honchar + */ +public class ManualCaseTabPanel extends AbstractObjectTabPanel { + private static final long serialVersionUID = 1L; + + private static final String DOT_CLASS = OperationRequestCaseTabPanel.class.getName() + "."; + private static final Trace LOGGER = TraceManager.getTrace(OperationRequestCaseTabPanel.class); + private static final String OPERATION_PREPARE_DELTA_VISUALIZATION = DOT_CLASS + "prepareDeltaVisualization"; + + private static final String ID_MANUAL_CASE_DETAILS_PANEL = "manualCaseDetailsPanel"; + private IModel sceneModel; + + public ManualCaseTabPanel(String id, Form> mainForm, LoadableModel> objectWrapperModel, PageBase pageBase) { + super(id, mainForm, objectWrapperModel); + } + + @Override + protected void onInitialize(){ + super.onInitialize(); + initModels(); + initLayout(); + } + + private void initModels(){ + sceneModel = new LoadableModel(false) { + @Override + protected SceneDto load() { + PageBase pageBase = ManualCaseTabPanel.this.getPageBase(); + try { + return WebComponentUtil.createSceneDtoForManualCase(ManualCaseTabPanel.this.getObjectWrapperModel().getObject().getObject().asObjectable(), + pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); + } catch (Exception ex){ + LOGGER.error("Couldn't prepare delta visualization: {}", ex.getLocalizedMessage()); + } + return null; + } + }; + } + + private void initLayout() { + ScenePanel scenePanel = new ScenePanel(ID_MANUAL_CASE_DETAILS_PANEL, sceneModel); + scenePanel.setOutputMarkupId(true); + add(scenePanel); + } + +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java index 4fe5c384c64..43352fae809 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java @@ -1,274 +1,285 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.web.page.admin.cases; - -import java.util.List; - -import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; -import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.schema.util.CaseTypeUtil; -import com.evolveum.midpoint.web.component.AjaxButton; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.extensions.markup.html.tabs.ITab; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.repeater.RepeatingView; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import com.evolveum.midpoint.gui.api.ComponentConstants; -import com.evolveum.midpoint.gui.api.component.tabs.CountablePanelTab; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.security.api.AuthorizationConstants; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.application.AuthorizationAction; -import com.evolveum.midpoint.web.application.PageDescriptor; -import com.evolveum.midpoint.web.component.ObjectSummaryPanel; -import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectMainPanel; -import com.evolveum.midpoint.web.component.objectdetails.AssignmentHolderTypeMainPanel; -import com.evolveum.midpoint.web.page.admin.PageAdminObjectDetails; -import com.evolveum.midpoint.web.util.OnePageParameterEncoder; - -@PageDescriptor(url = "/admin/case", encoder = OnePageParameterEncoder.class, action = { - @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_CASES_ALL_URL, - label = "PageAdminCases.auth.casesAll.label", - description = "PageAdminCases.auth.casesAll.description"), - @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_CASE_URL, - label = "PageCase.auth.case.label", - description = "PageCase.auth.case.description")}) -public class PageCase extends PageAdminObjectDetails { - private static final long serialVersionUID = 1L; - - private static final Trace LOGGER = TraceManager.getTrace(PageCase.class); - private static final String DOT_CLASS = PageCase.class.getName() + "."; - private static final String OPERATION_LOAD_CONNECTED_TASK = DOT_CLASS + "loadConnectedTask"; - - private static final String ID_SUMMARY_PANEL = "summaryPanel"; - - public PageCase() { - this(null, true); - } - - public PageCase(PrismObject unitToEdit, boolean isNewObject) { - initialize(unitToEdit, isNewObject, true); - } - - public PageCase(PageParameters parameters) { - getPageParameters().overwriteWith(parameters); - initialize(null, false, true); - } - - - @Override - protected AbstractObjectMainPanel createMainPanel(String id) { - return new AssignmentHolderTypeMainPanel(id, getObjectModel(), this) { - - private static final long serialVersionUID = 1L; - - @Override - protected List createTabs(final PageAdminObjectDetails parentPage) { - List tabs = super.createTabs(parentPage); - - if (matchCaseType(SystemObjectsType.ARCHETYPE_APPROVAL_CASE) - && CaseTypeUtil.approvalSchemaExists(getObject() != null ? getObject().asObjectable() : null)) { - tabs.add(0, - new PanelTab(parentPage.createStringResource("PageCase.approvalTab"), - getTabVisibility(ComponentConstants.UI_CASE_TAB_APPROVAL_URL, true, parentPage)) { - - private static final long serialVersionUID = 1L; - - @Override - public WebMarkupContainer createPanel(String panelId) { - return new ApprovalCaseTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); - } - }); - } else if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)) { - tabs.add(0, - new PanelTab(parentPage.createStringResource("PageCase.operationRequestTab"), - getTabVisibility(ComponentConstants.UI_CASE_TAB_APPROVAL_URL, true, parentPage)) { - - private static final long serialVersionUID = 1L; - - @Override - public WebMarkupContainer createPanel(String panelId) { - return new OperationRequestCaseTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); - } - - }); - } else if (matchCaseType(SystemObjectsType.ARCHETYPE_MANUAL_CASE)) { - //todo manual case tab - } - if (!matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)) { - - tabs.add( - new CountablePanelTab(parentPage.createStringResource("PageCase.workitemsTab"), - getTabVisibility(ComponentConstants.UI_CASE_TAB_WORKITEMS_URL, false, parentPage)) { - - private static final long serialVersionUID = 1L; - - @Override - public WebMarkupContainer createPanel(String panelId) { - return new CaseWorkitemsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); - } - - @Override - public String getCount() { - return Integer.toString(countWorkItems()); - } - }); - } - if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)){ - tabs.add( - new CountablePanelTab(parentPage.createStringResource("PageCase.childCasesTab"), - getTabVisibility(ComponentConstants.UI_CASE_TAB_CHILD_CASES_URL, false, parentPage)) { - - private static final long serialVersionUID = 1L; - - @Override - public WebMarkupContainer createPanel(String panelId) { - return new ChildCasesTabPanel(panelId, getMainForm(), getObjectModel()); - } - - @Override - public String getCount() { - return Integer.toString(countChildrenCases()); - } - }); - } - - // commented now as it doesn't display informative data -// tabs.add( -// new CountablePanelTab(parentPage.createStringResource("PageCase.events"), -// getTabVisibility(ComponentConstants.UI_CASE_TAB_EVENTS_URL, false, parentPage)) { -// -// private static final long serialVersionUID = 1L; -// -// @Override -// public WebMarkupContainer createPanel(String panelId) { -// return new CaseEventsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); -// } -// -// @Override -// public String getCount() { -// return Integer.toString(countEvents()); -// } -// }); - return tabs; - } - - @Override - protected boolean getOptionsPanelVisibility() { - return false; - } - - @Override - protected boolean isReadonly(){ - return true; - } - }; - } - - @Override - protected IModel createPageTitleModel() { - return createStringResource("PageCase.title"); - } - - @Override - protected ObjectSummaryPanel createSummaryPanel(IModel summaryModel) { - return new CaseSummaryPanel(ID_SUMMARY_PANEL, CaseType.class, summaryModel, this); - } - - protected void initOperationalButtons(RepeatingView repeatingView){ - OperationResult result = new OperationResult(OPERATION_LOAD_CONNECTED_TASK); - ObjectQuery query = getPrismContext().queryFor(TaskType.class) - .item(TaskType.F_OBJECT_REF) - .ref(getObjectWrapper().getOid()) - .build(); - List> connectedTasks = WebModelServiceUtils.searchObjects(TaskType.class, query, result, PageCase.this); - final ObjectReferenceType taskRef = new ObjectReferenceType(); - if (CollectionUtils.isNotEmpty(connectedTasks)) { - taskRef.setOid(connectedTasks.get(0).getOid()); - taskRef.setType(TaskType.COMPLEX_TYPE); - } - if (StringUtils.isNotEmpty(taskRef.getOid())) { - AjaxButton navigateToTask = new AjaxButton(repeatingView.newChildId(), createStringResource("PageCase.navigateToTask")) { - @Override - public void onClick(AjaxRequestTarget target) { - WebComponentUtil.dispatchToObjectDetailsPage(taskRef, PageCase.this, false); - } - }; - navigateToTask.add(AttributeAppender.append("class", "btn-default")); - repeatingView.add(navigateToTask); - } - } - - @Override - public void finishProcessing(AjaxRequestTarget target, OperationResult result, boolean returningFromAsync) { - - } - - @Override - public void continueEditing(AjaxRequestTarget target) { - - } - - @Override - public Class getCompileTimeClass() { - return CaseType.class; - } - - @Override - protected CaseType createNewObject(){ - return new CaseType(); - } - - @Override - protected Class getRestartResponsePage() { - return PageCases.class; - } - - private boolean matchCaseType(SystemObjectsType archetypeType){ - CaseType caseObject = getObjectWrapper().getObject().asObjectable(); - if (caseObject == null || caseObject.getAssignment() == null){ - return false; - } - for (AssignmentType assignment : caseObject.getAssignment()){ - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && archetypeType.value().equals(targetRef.getOid())){ - return true; - } - } - return false; - } - - private int countWorkItems(){ - List workItemsList = getObjectModel().getObject().getObject().asObjectable().getWorkItem(); - return workItemsList == null ? 0 : workItemsList.size(); - } - - private int countChildrenCases(){ - CaseType currentCase = getObjectModel().getObject().getObject().asObjectable(); - ObjectQuery childrenCasesQuery = getPrismContext().queryFor(CaseType.class) - .item(CaseType.F_PARENT_REF).ref(currentCase.getOid()) - .build(); - return WebModelServiceUtils.countObjects(CaseType.class, childrenCasesQuery, PageCase.this); - } - - private int countEvents(){ - List eventsList = getObjectModel().getObject().getObject().asObjectable().getEvent(); - return eventsList == null ? 0 : eventsList.size(); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.web.page.admin.cases; + +import java.util.List; + +import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.web.component.AjaxButton; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.extensions.markup.html.tabs.ITab; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.repeater.RepeatingView; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import com.evolveum.midpoint.gui.api.ComponentConstants; +import com.evolveum.midpoint.gui.api.component.tabs.CountablePanelTab; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.AuthorizationConstants; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.web.application.AuthorizationAction; +import com.evolveum.midpoint.web.application.PageDescriptor; +import com.evolveum.midpoint.web.component.ObjectSummaryPanel; +import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectMainPanel; +import com.evolveum.midpoint.web.component.objectdetails.AssignmentHolderTypeMainPanel; +import com.evolveum.midpoint.web.page.admin.PageAdminObjectDetails; +import com.evolveum.midpoint.web.util.OnePageParameterEncoder; + +@PageDescriptor(url = "/admin/case", encoder = OnePageParameterEncoder.class, action = { + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_CASES_ALL_URL, + label = "PageAdminCases.auth.casesAll.label", + description = "PageAdminCases.auth.casesAll.description"), + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_CASE_URL, + label = "PageCase.auth.case.label", + description = "PageCase.auth.case.description")}) +public class PageCase extends PageAdminObjectDetails { + private static final long serialVersionUID = 1L; + + private static final Trace LOGGER = TraceManager.getTrace(PageCase.class); + private static final String DOT_CLASS = PageCase.class.getName() + "."; + private static final String OPERATION_LOAD_CONNECTED_TASK = DOT_CLASS + "loadConnectedTask"; + + private static final String ID_SUMMARY_PANEL = "summaryPanel"; + + public PageCase() { + this(null, true); + } + + public PageCase(PrismObject unitToEdit, boolean isNewObject) { + initialize(unitToEdit, isNewObject, true); + } + + public PageCase(PageParameters parameters) { + getPageParameters().overwriteWith(parameters); + initialize(null, false, true); + } + + + @Override + protected AbstractObjectMainPanel createMainPanel(String id) { + return new AssignmentHolderTypeMainPanel(id, getObjectModel(), this) { + + private static final long serialVersionUID = 1L; + + @Override + protected List createTabs(final PageAdminObjectDetails parentPage) { + List tabs = super.createTabs(parentPage); + + if (matchCaseType(SystemObjectsType.ARCHETYPE_APPROVAL_CASE) + && CaseTypeUtil.approvalSchemaExists(getObject() != null ? getObject().asObjectable() : null)) { + tabs.add(0, + new PanelTab(parentPage.createStringResource("PageCase.approvalTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_APPROVAL_URL, true, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new ApprovalCaseTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); + } + }); + } else if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)) { + tabs.add(0, + new PanelTab(parentPage.createStringResource("PageCase.operationRequestTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_APPROVAL_URL, true, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new OperationRequestCaseTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); + } + + }); + } else if (matchCaseType(SystemObjectsType.ARCHETYPE_MANUAL_CASE)) { + tabs.add(0, + new PanelTab(parentPage.createStringResource("PageCase.manualOperationDetailsTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_MANUAL_OPERATION_DETAILS_URL, true, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new ManualCaseTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); + } + + }); + } + if (!matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)) { + + tabs.add( + new CountablePanelTab(parentPage.createStringResource("PageCase.workitemsTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_WORKITEMS_URL, false, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new CaseWorkitemsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); + } + + @Override + public String getCount() { + return Integer.toString(countWorkItems()); + } + }); + } + if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)){ + tabs.add( + new CountablePanelTab(parentPage.createStringResource("PageCase.childCasesTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_CHILD_CASES_URL, false, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new ChildCasesTabPanel(panelId, getMainForm(), getObjectModel()); + } + + @Override + public String getCount() { + return Integer.toString(countChildrenCases()); + } + }); + } + + // commented now as it doesn't display informative data +// tabs.add( +// new CountablePanelTab(parentPage.createStringResource("PageCase.events"), +// getTabVisibility(ComponentConstants.UI_CASE_TAB_EVENTS_URL, false, parentPage)) { +// +// private static final long serialVersionUID = 1L; +// +// @Override +// public WebMarkupContainer createPanel(String panelId) { +// return new CaseEventsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); +// } +// +// @Override +// public String getCount() { +// return Integer.toString(countEvents()); +// } +// }); + return tabs; + } + + @Override + protected boolean getOptionsPanelVisibility() { + return false; + } + + @Override + protected boolean isReadonly(){ + return true; + } + }; + } + + @Override + protected IModel createPageTitleModel() { + return createStringResource("PageCase.title"); + } + + @Override + protected ObjectSummaryPanel createSummaryPanel(IModel summaryModel) { + return new CaseSummaryPanel(ID_SUMMARY_PANEL, CaseType.class, summaryModel, this); + } + + protected void initOperationalButtons(RepeatingView repeatingView){ + OperationResult result = new OperationResult(OPERATION_LOAD_CONNECTED_TASK); + ObjectQuery query = getPrismContext().queryFor(TaskType.class) + .item(TaskType.F_OBJECT_REF) + .ref(getObjectWrapper().getOid()) + .build(); + List> connectedTasks = WebModelServiceUtils.searchObjects(TaskType.class, query, result, PageCase.this); + final ObjectReferenceType taskRef = new ObjectReferenceType(); + if (CollectionUtils.isNotEmpty(connectedTasks)) { + taskRef.setOid(connectedTasks.get(0).getOid()); + taskRef.setType(TaskType.COMPLEX_TYPE); + } + if (StringUtils.isNotEmpty(taskRef.getOid())) { + AjaxButton navigateToTask = new AjaxButton(repeatingView.newChildId(), createStringResource("PageCase.navigateToTask")) { + @Override + public void onClick(AjaxRequestTarget target) { + WebComponentUtil.dispatchToObjectDetailsPage(taskRef, PageCase.this, false); + } + }; + navigateToTask.add(AttributeAppender.append("class", "btn-default")); + repeatingView.add(navigateToTask); + } + } + + @Override + public void finishProcessing(AjaxRequestTarget target, OperationResult result, boolean returningFromAsync) { + + } + + @Override + public void continueEditing(AjaxRequestTarget target) { + + } + + @Override + public Class getCompileTimeClass() { + return CaseType.class; + } + + @Override + protected CaseType createNewObject(){ + return new CaseType(); + } + + @Override + protected Class getRestartResponsePage() { + return PageCases.class; + } + + private boolean matchCaseType(SystemObjectsType archetypeType){ + CaseType caseObject = getObjectWrapper().getObject().asObjectable(); + if (caseObject == null || caseObject.getAssignment() == null){ + return false; + } + for (AssignmentType assignment : caseObject.getAssignment()){ + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && archetypeType.value().equals(targetRef.getOid())){ + return true; + } + } + return false; + } + + private int countWorkItems(){ + List workItemsList = getObjectModel().getObject().getObject().asObjectable().getWorkItem(); + return workItemsList == null ? 0 : workItemsList.size(); + } + + private int countChildrenCases(){ + CaseType currentCase = getObjectModel().getObject().getObject().asObjectable(); + ObjectQuery childrenCasesQuery = getPrismContext().queryFor(CaseType.class) + .item(CaseType.F_PARENT_REF).ref(currentCase.getOid()) + .build(); + return WebModelServiceUtils.countObjects(CaseType.class, childrenCasesQuery, PageCase.this); + } + + private int countEvents(){ + List eventsList = getObjectModel().getObject().getObject().asObjectable().getEvent(); + return eventsList == null ? 0 : eventsList.size(); + } +} From 5ce638d9cb14740b7c39db03aacca853804a9111 Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 24 Mar 2020 11:25:23 +0100 Subject: [PATCH 2/8] adding comparison of oids for active dashboard menu (MID-5982) --- .../java/com/evolveum/midpoint/gui/api/page/PageBase.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java index f21b5eaddf0..0fa6483937a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java @@ -2038,7 +2038,13 @@ private MainMenuItem createHomeItems() { } PageParameters pageParameters = new PageParameters(); pageParameters.add(OnePageParameterEncoder.PARAMETER, dashboard.getOid()); - MenuItem menu = new MenuItem(label, "", PageDashboardConfigurable.class, pageParameters, null, null); + MenuItem menu = new MenuItem(label, "", PageDashboardConfigurable.class, pageParameters, null, null){ + @Override + protected boolean isMenuActive() { + StringValue dashboardOid = getPageParameters().get(OnePageParameterEncoder.PARAMETER); + return dashboard.getOid().equals(dashboardOid.toString()); + } + }; item.getItems().add(menu); }); From f2ec6c52e0b3df6e9bc5f419a67d59efb2818b71 Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 24 Mar 2020 14:06:38 +0100 Subject: [PATCH 3/8] removing last slash in request url (MID-5139) --- .../evolveum/midpoint/web/util/ExactMatchMountedMapper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java index b8a590a9d2c..0161eda38f0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java @@ -16,6 +16,8 @@ import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder; import org.apache.wicket.request.mapper.parameter.PageParametersEncoder; +import java.util.List; + /** * Created by lazyman on 09/03/2017. */ @@ -41,6 +43,10 @@ protected boolean urlStartsWithMountedSegments(Url url) { if (url == null) { return false; } + int segmentsSize = url.getSegments().size(); + if (segmentsSize != 0 && StringUtils.isBlank(url.getSegments().get(segmentsSize-1))) { + url.getSegments().remove(segmentsSize-1); + } if (!(pageParametersEncoder instanceof PageParametersEncoder)) { LOG.trace("Matching using standard mounted mapper for '{}'", url); From 19a60fc82cb3896bac8ba27a75e566f132238c1a Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 24 Mar 2020 15:19:42 +0100 Subject: [PATCH 4/8] manual case work item detail (mid-6094) --- .../midpoint/gui/api/util/WebComponentUtil.java | 5 +++-- .../web/page/admin/workflow/WorkItemDetailsPanel.java | 11 ++++++++--- .../evolveum/midpoint/schema/util/CaseTypeUtil.java | 10 +--------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java index 859cfbc4429..10331c776a0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java @@ -4182,7 +4182,8 @@ public static SceneDto createSceneDto(CaseType caseObject, PageBase pageBase, St OperationResult result = new OperationResult(operation); Task task = pageBase.createSimpleTask(operation); try { - Scene deltasScene = SceneUtil.visualizeObjectTreeDeltas(caseObject.getApprovalContext().getDeltasToApprove(), "pageWorkItem.delta", + Scene deltasScene = SceneUtil.visualizeObjectTreeDeltas(caseObject.getApprovalContext().getDeltasToApprove(), + CaseTypeUtil.isClosed(caseObject) ? "pageWorkItem.changesApplied" : "pageWorkItem.delta", pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); return new SceneDto(deltasScene); } catch (SchemaException | ExpressionEvaluationException ex){ @@ -4201,7 +4202,7 @@ public static SceneDto createSceneDtoForManualCase(CaseType caseObject, PageBase Task task = pageBase.createSimpleTask(operation); try { Scene deltasScene = SceneUtil.visualizeObjectDeltaType(caseObject.getManualProvisioningContext().getPendingOperation().getDelta(), - "pageWorkItem.delta", pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); + CaseTypeUtil.isClosed(caseObject) ? "pageWorkItem.changesApplied" : "pageWorkItem.changesToBeApplied", pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); return new SceneDto(deltasScene); } catch (SchemaException | ExpressionEvaluationException ex){ LOGGER.error("Unable to create delta visualization for case {}: {}", caseObject, ex.getLocalizedMessage(), ex); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java index b1b0f794b37..53f29106f89 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java @@ -34,6 +34,7 @@ import com.evolveum.midpoint.web.component.prism.show.ScenePanel; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; +import com.evolveum.midpoint.web.page.admin.cases.ManualCaseTabPanel; import com.evolveum.midpoint.web.page.admin.cases.PageCaseWorkItem; import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnBlurAjaxFormUpdatingBehaviour; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -105,7 +106,12 @@ private void initModels(){ @Override protected SceneDto load() { PageBase pageBase = WorkItemDetailsPanel.this.getPageBase(); - return WebComponentUtil.createSceneDto(WorkItemDetailsPanel.this.getModelObject(), pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); + CaseType parentCase = CaseTypeUtil.getCase(WorkItemDetailsPanel.this.getModelObject()); + if (CaseTypeUtil.isManualProvisioningCase(parentCase)){ + return WebComponentUtil.createSceneDtoForManualCase(parentCase, pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); + } else { + return WebComponentUtil.createSceneDto(WorkItemDetailsPanel.this.getModelObject(), pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); + } } }; evidenceFile = WorkItemTypeUtil.getEvidence(getModelObject()); @@ -180,10 +186,9 @@ private void initLayout(){ add(reasonPanel); - if (CaseTypeUtil.isApprovalCase(parentCase)){ + if (CaseTypeUtil.isApprovalCase(parentCase) || CaseTypeUtil.isManualProvisioningCase(parentCase)){ ScenePanel scenePanel = new ScenePanel(ID_DELTAS_TO_APPROVE, sceneModel); scenePanel.setOutputMarkupId(true); - scenePanel.add(new VisibleBehaviour(() -> CaseTypeUtil.isApprovalCase(parentCase))); add(scenePanel); } else { add(new WebMarkupContainer(ID_DELTAS_TO_APPROVE)); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseTypeUtil.java index f7aa1cd50f3..5c029a3998e 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseTypeUtil.java @@ -70,15 +70,7 @@ public static boolean isManualProvisioningCase(CaseType aCase){ if (aCase == null || CollectionUtils.isEmpty(aCase.getArchetypeRef())){ return false; } - for (ObjectReferenceType ort : aCase.getArchetypeRef()){ - if (ort == null){ - continue; - } - if (SystemObjectsType.ARCHETYPE_MANUAL_CASE.value().equals(ort.getOid())){ - return true; - } - } - return false; + return aCase != null && ObjectTypeUtil.hasArchetype(aCase, SystemObjectsType.ARCHETYPE_MANUAL_CASE.value()); } public static boolean isApprovalCase(CaseType aCase) { From c4ec19e5b24cdc2420069a7dee3ce9ef592abfdf Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 24 Mar 2020 16:05:29 +0100 Subject: [PATCH 5/8] Add minor improvements to tracing Operations (represented by operation results) and mappings are now categorizable. Some visualization configuration support was added as well. --- .../xml/ns/public/common/common-core-3.xsd | 12 + .../public/common/common-model-context-3.xsd | 298 +- .../model/common/mapping/MappingImpl.java | 16 +- .../impl/controller/MappingDiagEvaluator.java | 277 +- .../model/impl/lens/AssignmentEvaluator.java | 3093 +++++++++-------- .../model/impl/lens/Construction.java | 16 +- .../lens/projector/ActivationProcessor.java | 1880 +++++----- .../lens/projector/OutboundProcessor.java | 14 +- .../ProjectionCredentialsProcessor.java | 959 +++-- .../projector/focus/InboundProcessor.java | 20 +- ...AssignedFocusMappingEvaluationRequest.java | 3 +- ...utoassignRoleMappingEvaluationRequest.java | 2 +- .../FocalMappingEvaluationRequest.java | 9 +- .../projector/mappings/MappingEvaluator.java | 25 +- .../mappings/MappingSetEvaluator.java | 2 +- .../TemplateMappingEvaluationRequest.java | 3 +- .../projector/policy/PolicyRuleProcessor.java | 1 + 17 files changed, 3451 insertions(+), 3179 deletions(-) diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index b83003ead33..39d678c80f4 100755 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -15604,6 +15604,18 @@ + + + + Kind of operation (e.g. clockwork execution, mapping evaluation, repository access). To be used + for easy navigation, result filtering, and so on. + + + 4.1 + true + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 2c28fb577bd..102d8c40392 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -1449,6 +1449,7 @@ 4.0 + true @@ -1492,11 +1493,52 @@ + + + + Role auto-assign mapping. + + + + 4.1 + + + + + + + Mapping used in a construction. + + + + 4.1 + + + + + + + Condition for policy rule. + + + + + + + + + + Condition for assignment or inducement. + + + + + + - Various auxiliary "condition" mappings e.g. when evaluating condition for global policy rule, - for assignment/inducement, etc. + Other conditions. @@ -2525,4 +2567,256 @@ + + + + Kind of operation (captured by OperationResult). + + + 4.1 + true + + + + + + + Whole execution (run) of the clockwork. + + + + + + + + + + Single click of the clockwork. + + + + + + + + + + Execution of the projector. + + + + + + + + + + Evaluation of a mapping. + + + + + + + + + + Evaluation of a script. + + + + + + + + + + Repository cache operation. + + + + + + + + + + Repository operation. + + + + + + + + + + Other kind of operation. + + + + + + + + + + + + + How to visualize trace file. + + + true + true + 4.1 + + + + + + + + + + + + How to visualize trace file. + + + true + true + 4.1 + + + + + + + + Any of these selectors may match. (TODO reconsider) + + + + + + + + + + + + Selects an operation trace. If more conditions are specified, all of them must apply. + + + true + true + 4.1 + + + + + + + Type of the trace to match. Applies if any of the types match. + + + + + + + Kind of operation to match. Applies if any of the kinds matches. + + + + + + + + + + + How a traced operation is to be visualized. + + + true + true + 4.1 + + + + + + + Generic visualization type (off, one-liner, detailed). TODO. + The interpretation is left to the particular trace visualizer. + + + + + + + Should operational items be displayed? + The interpretation is left to the particular trace visualizer. + + + + + + + + + + + Generic visualization type (off, one-liner, detailed). TODO. + + + 4.1 + true + + + + + + + The trace nor its children are not visualized. + + + + + + + + + + The trace is not visualized. But its children could be. + This is the default. + + + + + + + + + + The trace is visualized in "one-line" mode. No details. + + + + + + + + + + The trace is visualized in details. + + + + + + + + diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java index 059164bd6fd..6430401d940 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java @@ -12,6 +12,8 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; + import org.jetbrains.annotations.NotNull; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; @@ -61,6 +63,7 @@ public class MappingImpl // configuration properties (unmodifiable) private final MappingType mappingType; + private final MappingKindType mappingKind; private final ExpressionFactory expressionFactory; private final ExpressionVariables variables; private final PrismContext prismContext; @@ -134,6 +137,7 @@ private MappingImpl(Builder builder) { expressionFactory = builder.expressionFactory; variables = builder.variables; mappingType = builder.mappingType; + mappingKind = builder.mappingKind; objectResolver = builder.objectResolver; securityContextManager = builder.securityContextManager; defaultSource = builder.defaultSource; @@ -400,7 +404,10 @@ public void evaluate(Task task, OperationResult parentResult) throws ExpressionE .build(); if (result.isTracingNormal(MappingEvaluationTraceType.class)) { // temporary solution - to avoid checking level at too many places - trace = new MappingEvaluationTraceType(prismContext); + trace = new MappingEvaluationTraceType(prismContext) + .mapping(mappingType.clone()) + .mappingKind(mappingKind) + .containingObjectRef(ObjectTypeUtil.createObjectRef(originObject, prismContext)); trace.setMapping(mappingType.clone()); result.addTrace(trace); } else { @@ -1270,6 +1277,7 @@ private void assertState(MappingEvaluationState expectedState) { public PrismValueDeltaSetTripleProducer clone() { MappingImpl clone = new Builder() .mappingType(mappingType) + .mappingKind(mappingKind) .contextDescription(contextDescription) .expressionFactory(expressionFactory) .securityContextManager(securityContextManager) @@ -1457,6 +1465,7 @@ public static final class Builder defaultSource; @@ -1497,6 +1506,11 @@ public Builder mappingType(MappingType val) { return this; } + public Builder mappingKind(MappingKindType val) { + mappingKind = val; + return this; + } + public Builder objectResolver(ObjectResolver val) { objectResolver = val; return this; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java index 97f7a3b30d3..343b62e3547 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java @@ -1,138 +1,139 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.controller; - -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Executes mappings in diagnostic mode. - * - * @author mederly - */ -@Component -public class MappingDiagEvaluator { - - @Autowired - private MappingFactory mappingFactory; - - @Autowired - private ModelService modelService; - - @Autowired - private ModelObjectResolver objectResolver; - - @Autowired - private PrismContext prismContext; - - @Autowired - private Clock clock; - - public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationRequestType request, @NotNull Task task, - @NotNull OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - - MappingImpl.Builder builder = mappingFactory.createMappingBuilder(); - - ObjectDeltaObject sourceContext = createSourceContext(request, task, result); - - builder = builder - .mappingType(request.getMapping()) - .contextDescription("mapping diagnostic execution") - .sourceContext(sourceContext) - .targetContext(createTargetContext(request, sourceContext)) - .profiling(true) - .now(clock.currentTimeXMLGregorianCalendar()); - - MappingImpl mapping = builder.build(); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - mapping.evaluate(task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - StringBuilder sb = new StringBuilder(); - sb.append("Output triple: "); - dumpOutputTriple(sb, mapping.getOutputTriple()); - sb.append("Condition output triple: "); - dumpOutputTriple(sb, mapping.getConditionOutputTriple()); - sb.append("Time constraint valid: ").append(mapping.evaluateTimeConstraintValid(task, result)).append("\n"); - sb.append("Next recompute time: ").append(mapping.getNextRecomputeTime()).append("\n"); - sb.append("\n"); - sb.append("Evaluation time: ").append(mapping.getEtime()).append(" ms\n"); - - MappingEvaluationResponseType response = new MappingEvaluationResponseType(); - response.setResponse(sb.toString()); - return response; - } - - private void dumpOutputTriple(StringBuilder sb, PrismValueDeltaSetTriple triple) { - if (triple != null) { - sb.append("\n").append(triple.debugDump(1)).append("\n\n"); - } else { - sb.append("(null)\n\n"); - } - } - - private PrismObjectDefinition createTargetContext(MappingEvaluationRequestType request, ObjectDeltaObject sourceContext) { - if (request.getTargetContext() == null) { - return sourceContext.getDefinition(); - } - return prismContext.getSchemaRegistry().findObjectDefinitionByType(request.getTargetContext()); - } - - private ObjectDeltaObject createSourceContext(MappingEvaluationRequestType request, Task task, - OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (request.getSourceContext() == null) { - return null; - } - MappingEvaluationSourceContextType ctx = request.getSourceContext(); - - PrismObject oldObject; - if (ctx.getObject() != null) { - oldObject = ctx.getObject().getValue().asPrismObject(); - } else if (ctx.getObjectRef() != null) { - oldObject = objectResolver.resolve(ctx.getObjectRef(), ObjectType.class, null, "resolving default source", task, result).asPrismObject(); - } else { - oldObject = null; - } - ObjectDelta delta; - if (ctx.getDelta() != null) { - delta = DeltaConvertor.createObjectDelta(ctx.getDelta(), prismContext); - } else { - delta = null; - } - return new ObjectDeltaObject(oldObject, delta, null, oldObject.getDefinition()); - } -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.controller; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Executes mappings in diagnostic mode. + * + * @author mederly + */ +@Component +public class MappingDiagEvaluator { + + @Autowired + private MappingFactory mappingFactory; + + @Autowired + private ModelService modelService; + + @Autowired + private ModelObjectResolver objectResolver; + + @Autowired + private PrismContext prismContext; + + @Autowired + private Clock clock; + + public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationRequestType request, @NotNull Task task, + @NotNull OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + + MappingImpl.Builder builder = mappingFactory.createMappingBuilder(); + + ObjectDeltaObject sourceContext = createSourceContext(request, task, result); + + builder = builder + .mappingType(request.getMapping()) + .mappingKind(MappingKindType.OTHER) + .contextDescription("mapping diagnostic execution") + .sourceContext(sourceContext) + .targetContext(createTargetContext(request, sourceContext)) + .profiling(true) + .now(clock.currentTimeXMLGregorianCalendar()); + + MappingImpl mapping = builder.build(); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + mapping.evaluate(task, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + + StringBuilder sb = new StringBuilder(); + sb.append("Output triple: "); + dumpOutputTriple(sb, mapping.getOutputTriple()); + sb.append("Condition output triple: "); + dumpOutputTriple(sb, mapping.getConditionOutputTriple()); + sb.append("Time constraint valid: ").append(mapping.evaluateTimeConstraintValid(task, result)).append("\n"); + sb.append("Next recompute time: ").append(mapping.getNextRecomputeTime()).append("\n"); + sb.append("\n"); + sb.append("Evaluation time: ").append(mapping.getEtime()).append(" ms\n"); + + MappingEvaluationResponseType response = new MappingEvaluationResponseType(); + response.setResponse(sb.toString()); + return response; + } + + private void dumpOutputTriple(StringBuilder sb, PrismValueDeltaSetTriple triple) { + if (triple != null) { + sb.append("\n").append(triple.debugDump(1)).append("\n\n"); + } else { + sb.append("(null)\n\n"); + } + } + + private PrismObjectDefinition createTargetContext(MappingEvaluationRequestType request, ObjectDeltaObject sourceContext) { + if (request.getTargetContext() == null) { + return sourceContext.getDefinition(); + } + return prismContext.getSchemaRegistry().findObjectDefinitionByType(request.getTargetContext()); + } + + private ObjectDeltaObject createSourceContext(MappingEvaluationRequestType request, Task task, + OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (request.getSourceContext() == null) { + return null; + } + MappingEvaluationSourceContextType ctx = request.getSourceContext(); + + PrismObject oldObject; + if (ctx.getObject() != null) { + oldObject = ctx.getObject().getValue().asPrismObject(); + } else if (ctx.getObjectRef() != null) { + oldObject = objectResolver.resolve(ctx.getObjectRef(), ObjectType.class, null, "resolving default source", task, result).asPrismObject(); + } else { + oldObject = null; + } + ObjectDelta delta; + if (ctx.getDelta() != null) { + delta = DeltaConvertor.createObjectDelta(ctx.getDelta(), prismContext); + } else { + delta = null; + } + return new ObjectDeltaObject(oldObject, delta, null, oldObject.getDefinition()); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 1f1f4ee2c15..a573e503d97 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -1,1546 +1,1547 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.stream.Collectors; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluationOrder; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PlusMinusZero; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.internals.InternalMonitor; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.FocusTypeUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; - -import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; -import org.apache.commons.lang.BooleanUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, - * authorizations, policy rules, and so on. - * - * @author semancik - */ -public class AssignmentEvaluator { - - private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); - - private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; - private static final String OP_EVALUATE_FROM_SEGMENT = AssignmentEvaluator.class.getName()+".evaluateFromSegment"; - - private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); - - // "Configuration parameters" - private final RepositoryService repository; - private final ObjectDeltaObject focusOdo; - private final LensContext lensContext; - private final String channel; - private final ObjectResolver objectResolver; - private final SystemObjectCache systemObjectCache; - private final RelationRegistry relationRegistry; - private final PrismContext prismContext; - private final MappingFactory mappingFactory; - private final ActivationComputer activationComputer; - private final XMLGregorianCalendar now; - private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) - private final PrismObject systemConfiguration; - private final MappingEvaluator mappingEvaluator; - private final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; - private final LifecycleStateModelType focusStateModel; - - // Evaluation state - private final List memberOfInvocations = new ArrayList<>(); // experimental - - private AssignmentEvaluator(Builder builder) { - repository = builder.repository; - focusOdo = builder.focusOdo; - lensContext = builder.lensContext; - channel = builder.channel; - objectResolver = builder.objectResolver; - systemObjectCache = builder.systemObjectCache; - relationRegistry = builder.relationRegistry; - prismContext = builder.prismContext; - mappingFactory = builder.mappingFactory; - activationComputer = builder.activationComputer; - now = builder.now; - loginMode = builder.loginMode; - systemConfiguration = builder.systemConfiguration; - mappingEvaluator = builder.mappingEvaluator; - evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); - - LensFocusContext focusContext = lensContext.getFocusContext(); - if (focusContext != null) { - focusStateModel = focusContext.getLifecycleModel(); - } else { - focusStateModel = null; - } - } - - public RepositoryService getRepository() { - return repository; - } - - @SuppressWarnings("unused") - public ObjectDeltaObject getFocusOdo() { - return focusOdo; - } - - public LensContext getLensContext() { - return lensContext; - } - - public String getChannel() { - return channel; - } - - public ObjectResolver getObjectResolver() { - return objectResolver; - } - - public SystemObjectCache getSystemObjectCache() { - return systemObjectCache; - } - - public PrismContext getPrismContext() { - return prismContext; - } - - public MappingFactory getMappingFactory() { - return mappingFactory; - } - - public ActivationComputer getActivationComputer() { - return activationComputer; - } - - public XMLGregorianCalendar getNow() { - return now; - } - - @SuppressWarnings("unused") - public boolean isLoginMode() { - return loginMode; - } - - public PrismObject getSystemConfiguration() { - return systemConfiguration; - } - - @SuppressWarnings("unused") - public MappingEvaluator getMappingEvaluator() { - return mappingEvaluator; - } - - public void reset(boolean alsoMemberOfInvocations) { - evaluatedAssignmentTargetCache.reset(); - if (alsoMemberOfInvocations) { - memberOfInvocations.clear(); - } - } - - // This is to reduce the number of parameters passed between methods in this class. - // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. - // (There is single EvaluationContext instance for any call to evaluate().) - private class EvaluationContext { - @NotNull private final EvaluatedAssignmentImpl evalAssignment; - @NotNull private final AssignmentPathImpl assignmentPath; - // The primary assignment mode tells whether the primary assignment was added, removed or it is unchanged. - // The primary assignment is the first assignment in the assignment path, the assignment that is located in the - // focal object. - private final PlusMinusZero primaryAssignmentMode; - private final boolean evaluateOld; - private final Task task; - private EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, - @NotNull AssignmentPathImpl assignmentPath, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, Task task) { - this.evalAssignment = evalAssignment; - this.assignmentPath = assignmentPath; - this.primaryAssignmentMode = primaryAssignmentMode; - this.evaluateOld = evaluateOld; - this.task = task; - } - } - - /** - * evaluateOld: If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. - */ - public EvaluatedAssignmentImpl evaluate( - ItemDeltaItem,PrismContainerDefinition> assignmentIdi, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, - AssignmentOrigin origin, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE) - .setMinor() - .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) - .addParam("evaluateOld", evaluateOld) - .addArbitraryObjectAsParam("source", source) - .addParam("sourceDescription", sourceDescription) - .addArbitraryObjectAsParam("origin", origin) - .build(); - AssignmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { - trace = new AssignmentEvaluationTraceType(prismContext) - .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) - .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) - .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) - .evaluateOld(evaluateOld) - .textSource(source != null ? source.asPrismObject().debugDump() : "null") - .sourceDescription(sourceDescription); - result.addTrace(trace); - } else { - trace = null; - } - try { - assertSourceNotNull(source, assignmentIdi); - - EvaluatedAssignmentImpl evalAssignmentImpl = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); - - EvaluationContext ctx = new EvaluationContext( - evalAssignmentImpl, - new AssignmentPathImpl(prismContext), - primaryAssignmentMode, evaluateOld, task); - - evaluatedAssignmentTargetCache.resetForNextAssignment(); - - AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true, - evaluateOld, relationRegistry, prismContext); - segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); - segment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); - segment.setValidityOverride(true); - segment.setPathToSourceValid(true); - segment.setProcessMembership(true); - segment.setRelation(getRelation(getAssignmentType(segment, ctx))); - - evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx, result); - - if (segment.getTarget() != null) { - result.addContext("assignmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - - LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); - if (trace != null) { - trace.setTextResult(ctx.evalAssignment.debugDump()); - } - result.computeStatusIfUnknown(); - return ctx.evalAssignment; - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } - } - - private AssignmentType getAssignmentBean( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - boolean old) { - PrismContainerValue pcv = assignmentIdi.getSingleValue(old); - return pcv != null ? pcv.asContainerable() : null; - } - - private EvaluationOrder getInitialEvaluationOrder( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - EvaluationContext ctx) { - AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); - return EvaluationOrderImpl.zero(relationRegistry).advance(getRelation(assignmentType)); - } - - /** - * @param relativeMode - * - * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). - * This is a mode relative to the primary assignment. It does NOT tell whether the assignment as a whole - * is added or removed. It tells whether the part of the assignment that we are processing is to be - * added or removed. This may happen, e.g. if a condition in an existing assignment turns from false to true. - * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. - * The relative mode always starts at ZERO, even for added or removed assignments. - * - * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old - * state the value is false, and in new state the value is true), then the mode is PLUS. - * - * This "triples algebra" is based on the following two methods: - * - * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines - * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, - * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. - * - * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition - * and determines the new mode (commutatively): - * - * PLUS + PLUS/ZERO = PLUS - * MINUS + MINUS/ZERO = MINUS - * ZERO + ZERO = ZERO - * PLUS + MINUS = none - * - * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally - * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally - * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not - * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). - * - * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. - */ - private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, EvaluationContext ctx, - OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE_FROM_SEGMENT) - .setMinor() - .addParam("segment", segment.shortDump()) - .addArbitraryObjectAsParam("relativeMode", relativeMode) - .build(); - AssignmentSegmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { - trace = new AssignmentSegmentEvaluationTraceType(prismContext) - .segment(segment.toAssignmentPathSegmentType(true)) - .mode(PlusMinusZeroType.fromValue(relativeMode)); - result.addTrace(trace); - } else { - trace = null; - } - try { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("*** Evaluate from segment: {}", segment); - LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); - LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); - LOGGER.trace("*** mode: {}, process membership: {}", relativeMode, segment.isProcessMembership()); - LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); - } - - assertSourceNotNull(segment.source, ctx.evalAssignment); - checkSchema(segment, ctx); - - ctx.assignmentPath.add(segment); - LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDumpLazily()); - - boolean evaluateContent; - AssignmentType assignmentType = getAssignmentType(segment, ctx); - MappingType assignmentCondition = assignmentType.getCondition(); - if (assignmentCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(assignmentCondition, - segment.source, assignmentPathVariables, - "condition in assignment in " + segment.getSourceDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", - FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); - } - evaluateContent = false; - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, - modeFromCondition, relativeMode); - evaluateContent = true; - } - } else { - evaluateContent = true; - } - - if (ctx.assignmentPath.isEmpty() && ctx.evalAssignment.isVirtual()) { - segment.setValidityOverride(ctx.evalAssignment.isVirtual()); - } - - boolean isValid = - (evaluateContent && evaluateSegmentContent(segment, relativeMode, ctx, result)) || ctx.evalAssignment.isVirtual(); - - ctx.assignmentPath.removeLast(segment); - if (ctx.assignmentPath.isEmpty()) { // direct assignment - ctx.evalAssignment.setValid(isValid); - } - - LOGGER.trace("evalAssignment isVirtual {} ", ctx.evalAssignment.isVirtual()); - if (segment.getSource() != null) { - result.addContext("segmentSourceName", PolyString.getOrig(segment.getSource().getName())); - } - if (segment.getTarget() != null) { - result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - result.addArbitraryObjectAsReturn("relativeMode", relativeMode); - result.addReturn("evaluateContent", evaluateContent); - result.addReturn("isValid", isValid); - if (trace != null) { - trace.setTextResult(segment.debugDump()); - } - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - // "content" means "payload + targets" here - private boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, - PlusMinusZero relativeMode, EvaluationContext ctx, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - assert ctx.assignmentPath.last() == segment; - - final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; - - AssignmentType assignment = getAssignmentType(segment, ctx); - - // Assignment validity is checked with respect to the assignment source, not to the focus object. - // So, if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. - // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are - // in active lifecycle states. See also MID-6114. - AssignmentHolderType source = segment.isMatchingOrder() ? focusOdo.getNewObject().asObjectable() : segment.getSource(); - boolean isAssignmentValid = LensUtil.isAssignmentValid(source, assignment, now, activationComputer, focusStateModel); - if (isAssignmentValid || segment.isValidityOverride()) { - // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. - // Direct assignments are visited even if they are not valid (i.e. effectively disabled). - // It is because we need to collect e.g. assignment policy rules for them. - // Also because we could have deltas that disable/enable these assignments. - boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; - if (!loginMode && segment.isMatchingOrder()) { - if (assignment.getConstruction() != null) { - collectConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getPersonaConstruction() != null) { - collectPersonaConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getFocusMappings() != null) { - if (reallyValid && relativeMode != null) { // null relative mode means both PLUS and MINUS - collectFocusMappings(segment, relativeMode, ctx); - } - } - } - if (!loginMode && assignment.getPolicyRule() != null) { - // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when - // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. - // TODO is it ok? - if (isNonNegative(relativeMode)) { - if (segment.isMatchingOrder()) { - collectPolicyRule(true, segment, ctx); - } - if (segment.isMatchingOrderForTarget()) { - collectPolicyRule(false, segment, ctx); - } - } - } - if (assignment.getTargetRef() != null) { - QName relation = getRelation(assignment); - if (loginMode && !relationRegistry.isProcessedOnLogin(relation)) { - LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", assignment.getTargetRef().getOid(), relation); - // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // We want to make this configurable in the future MID-3581 - } else if (!loginMode && !isChanged(ctx.primaryAssignmentMode) && - !relationRegistry.isProcessedOnRecompute(relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { - LOGGER.debug("Skipping processing of assignment target {} because relation {} is configured for recompute skip (mode={})", assignment.getTargetRef().getOid(), relation, relativeMode); - // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules - // We want to make this configurable in the future MID-3581 - - // Important: but we still want this to be reflected in roleMembershipRef - if ((isNonNegative(relativeMode)) && segment.isProcessMembership()) { - if (assignment.getTargetRef().getOid() != null) { - addToMembershipLists(assignment.getTargetRef(), relation, ctx); - } else { - // no OID, so we have to resolve the filter - for (PrismObject targetObject : getTargets(segment, ctx, result)) { - ObjectType target = targetObject.asObjectable(); - if (target instanceof FocusType) { - addToMembershipLists((FocusType) target, relation, ctx); - } - } - } - } - } else { - List> targets = getTargets(segment, ctx, result); - LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignment.getId(), targets); - if (isDirectAssignment) { - setEvaluatedAssignmentTarget(segment, targets, ctx); - } - for (PrismObject target : targets) { - if (hasCycle(segment, target, ctx)) { - continue; - } - if (isDelegationToNonDelegableTarget(assignment, target, ctx)) { - continue; - } - evaluateSegmentTarget(segment, relativeMode, reallyValid, (AssignmentHolderType) target.asObjectable(), relation, ctx, result); - } - } - } - } else { - LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignment); - } - return isAssignmentValid; - } - - private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { - return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(lensContext.getOptions()); - } - - private boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject target, - EvaluationContext ctx) { - AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); - if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { - return false; - } - if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", - FocusTypeUtil.dumpAssignment(assignmentType), target); - } - return true; - } else { - return false; - } - } - - // number of times any given target is allowed to occur in the assignment path - private static final int MAX_TARGET_OCCURRENCES = 2; - - private boolean hasCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject target, - EvaluationContext ctx) throws PolicyViolationException { - // TODO reconsider this - if (target.getOid().equals(segment.source.getOid())) { - throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); - } - // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" - // as currently it is always true - // TODO reconsider this - int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); - if (count >= MAX_TARGET_OCCURRENCES) { - LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", - MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); - return true; - } else { - return false; - } - } - - private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ConstructionType constructionType = assignmentType.getConstruction(); - - LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); - - Construction construction = new Construction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setMappingFactory(mappingFactory); - construction.setMappingEvaluator(mappingEvaluator); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setOrderOneObject(segment.getOrderOneObject()); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - ctx.evalAssignment.addConstruction(construction, mode); - } - - private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PersonaConstructionType constructionType = assignmentType.getPersonaConstruction(); - - LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); - - PersonaConstruction construction = new PersonaConstruction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - ctx.evalAssignment.addPersonaConstruction(construction, mode); - } - - private void collectFocusMappings(AssignmentPathSegmentImpl segment, @NotNull PlusMinusZero relativeMode, EvaluationContext ctx) - throws SchemaException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentBean = getAssignmentType(segment, ctx); - MappingsType mappingsBean = assignmentBean.getFocusMappings(); - - LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", - mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - - for (MappingType mappingBean: mappingsBean.getMapping()) { - AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, - ctx.evalAssignment, relativeMode, assignmentPathVariables, segment.sourceDescription); - ctx.evalAssignment.addFocusMappingEvaluationRequest(request); - } - } - - private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PolicyRuleType policyRuleType = assignmentType.getPolicyRule(); - - LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); - - EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), ctx.assignmentPath.clone(), prismContext); - - if (focusRule) { - ctx.evalAssignment.addFocusPolicyRule(policyRule); - } else { - if (appliesDirectly(ctx.assignmentPath)) { - ctx.evalAssignment.addThisTargetPolicyRule(policyRule); - } else { - ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); - } - } - } - - private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { - assert !assignmentPath.isEmpty(); - // TODO what about deputy relation which does not increase summaryOrder? - long zeroOrderCount = assignmentPath.getSegments().stream() - .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) - .count(); - return zeroOrderCount == 1; - } - - @NotNull - private List> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - if (assignmentType.getTargetRef() != null) { - try { - return resolveTargets(segment, ctx, result); - } catch (ObjectNotFoundException ex) { - // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing - // an exception would prohibit any operations with the users that have the role, including removal of the reference. - // The failure is recorded in the result and we will log it. It should be enough. - LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); - // For OrgType references we trigger the reconciliation (see MID-2242) - ctx.evalAssignment.setForceRecon(true); - return Collections.emptyList(); - } - } else { - throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); - } - } - - @NotNull - private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - String oid = targetRef.getOid(); - - // Target is referenced, need to fetch it - Class targetClass; - if (targetRef.getType() != null) { - targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); - if (targetClass == null) { - throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - - if (oid == null) { - LOGGER.trace("Resolving dynamic target ref"); - if (targetRef.getFilter() == null){ - throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); - } - return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); - } else { - LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); - PrismObject target; - try { - target = repository.getObject(targetClass, oid, null, result); - } catch (SchemaException e) { - throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); - } - // Not handling object not found exception here. Caller will handle that. - if (target == null) { - throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); - } - return Collections.singletonList(target); - } - } - - @NotNull - private List> resolveTargetsFromFilter(Class targetClass, - SearchFilterType filter, AssignmentPathSegmentImpl segment, - EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, result)); - try { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), prismContext); - variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - if (assignmentPathVariables != null) { - ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); - } - variables.addVariableDefinitions(getAssignmentEvaluationVariables()); - ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, targetClass); - // TODO: expression profile should be determined from the holding object archetype - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, result); - if (evaluatedFilter == null) { - throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); - } - - return repository.searchObjects(targetClass, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); - // we don't check for no targets here; as we don't care for referential integrity - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private ExpressionVariables getAssignmentEvaluationVariables() { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); - // e.g. AssignmentEvaluator itself, model context, etc (when needed) - return variables; - } - - // Note: This method must be single-return after targetEvaluationInformation is established. - private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, boolean isAssignmentPathValid, - AssignmentHolderType target, QName relation, EvaluationContext ctx, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - assert ctx.assignmentPath.last() == segment; - - segment.setTarget(target); - segment.setRelation(relation); // probably not needed - - if (evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { - LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); - InternalMonitor.recordRoleEvaluationSkip(target, true); - return; - } - - LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); - - checkRelationWithTarget(segment, target, relation); - - LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), systemConfiguration); - boolean isTargetValid = LensUtil.isFocusValid(target, now, activationComputer, targetStateModel); - boolean isPathAndTargetValid = isAssignmentPathValid && isTargetValid; - - LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); - InternalMonitor.recordRoleEvaluation(target, true); - - AssignmentTargetEvaluationInformation targetEvaluationInformation; - if (isPathAndTargetValid) { - // Cache it immediately, even before evaluation. So if there is a cycle in the role path - // then we can detect it and skip re-evaluation of aggressively idempotent roles. - // - // !!! Ensure we will not return without updating this object (except for exceptions). So please keep this - // method a single-return one after this point. We did not want to complicate things using try...finally. - // - targetEvaluationInformation = evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); - } else { - targetEvaluationInformation = null; - } - int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - boolean skipOnConditionResult = false; - - if (isTargetValid && target instanceof AbstractRoleType) { - MappingType roleCondition = ((AbstractRoleType)target).getCondition(); - if (roleCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(roleCondition, - segment.source, assignmentPathVariables, - "condition in " + segment.getTargetDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - skipOnConditionResult = true; - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", - target, condOld, condNew); - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", target, condOld, condNew, - origMode, modeFromCondition, relativeMode); - } - } - } - - if (!skipOnConditionResult) { - EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( - target.asPrismObject(), - segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised - ctx.assignmentPath.clone(), - getAssignmentType(segment, ctx), - isPathAndTargetValid); - ctx.evalAssignment.addRole(evalAssignmentTarget, relativeMode); - - // we need to evaluate assignments also for disabled targets, because of target policy rules - // ... but only for direct ones! - if (isTargetValid || ctx.assignmentPath.size() == 1) { - for (AssignmentType nextAssignment : target.getAssignment()) { - evaluateAssignment(segment, relativeMode, isPathAndTargetValid, ctx, target, relation, nextAssignment, result); - } - } - - // we need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127 - if (isNonNegative(relativeMode) && segment.isProcessMembership()) { - addToMembershipLists(target, relation, ctx); - } - - if (isTargetValid) { - if (isNonNegative(relativeMode)) { - setAsTenantRef(target, ctx); - } - - // We continue evaluation even if the relation is non-membership and non-delegation. - // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. - - if (target instanceof AbstractRoleType) { - for (AssignmentType roleInducement : ((AbstractRoleType) target).getInducement()) { - evaluateInducement(segment, relativeMode, isPathAndTargetValid, ctx, target, roleInducement, result); - } - } - - if (segment.isMatchingOrder() && target instanceof AbstractRoleType && isNonNegative(relativeMode)) { - for (AuthorizationType authorizationType : ((AbstractRoleType) target).getAuthorization()) { - Authorization authorization = createAuthorization(authorizationType, target.toString()); - if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { - ctx.evalAssignment.addAuthorization(authorization); - } - } - AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); - if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations() - .contains(adminGuiConfiguration)) { - ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); - } - } - } - } - int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, targetPolicyRulesOnEntry, - targetPolicyRulesOnExit); - if (targetEvaluationInformation != null) { - targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); - } - } - - // TODO revisit this - private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { - EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); - if (evaluationOrder.getSummaryOrder() == 1) { - return segment.getTarget(); - } else { - if (segment.getSource() != null) { // should be always the case... - return segment.getSource(); - } else { - return segment.getTarget(); - } - } - } - - private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, - AssignmentHolderType target, QName relation, AssignmentType nextAssignment, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (relationRegistry.isDelegation(relation)) { - // We have to handle assignments as though they were inducements here. - if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", - FocusTypeUtil.dumpAssignment(nextAssignment)); - } - return; - } - } - QName nextRelation = getRelation(nextAssignment); - EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); - EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", - segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpAssignment(nextAssignment), nextEvaluationOrder); - } - String nextSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, nextSourceDescription, nextAssignment, true, relationRegistry, prismContext); - nextSegment.setRelation(nextRelation); - nextSegment.setEvaluationOrder(nextEvaluationOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - /* - * We obviously want to process membership from the segment if it's of matching order. - * - * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to - * collect membership from all assignments of any user that we find on the assignment path. - * - * TODO: does this work for invalid (effectiveStatus = disabled) assignments? - */ - boolean isUser = target instanceof UserType; - nextSegment.setProcessMembership(nextSegment.isMatchingOrder() || isUser); - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, - AssignmentHolderType target, AssignmentType inducement, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (!isInducementApplicableToFocusType(inducement.getFocusType())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", - FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), target.getClass().getSimpleName()); - } - return; - } - if (!isAllowedByLimitations(segment, inducement, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); - } - return; - } - String subSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, subSourceDescription, inducement, false, relationRegistry, prismContext); - // note that 'old' and 'new' values for assignment in nextSegment are the same - boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrder(), nextSegment.getAssignmentNew()); - boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrderForTarget(), nextSegment.getAssignmentNew()); - - Holder nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); - Holder nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); - adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment, ctx); - nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); - - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - nextSegment.setProcessMembership(nextIsMatchingOrder); - nextSegment.setRelation(getRelation(inducement)); - - // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into - // inducements with non-matching order: for example because we need to extract target-related policy rules - // (these are stored with order of one less than orders for focus-related policy rules). - // - // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. - // processMembership attribute to false for these inducements. - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", - segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpInducementConstraints(inducement), - FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); - } - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void adjustOrder(Holder evaluationOrderHolder, Holder targetEvaluationOrderHolder, - List constraints, Integer order, AssignmentPathImpl assignmentPath, - AssignmentPathSegmentImpl nextSegment, EvaluationContext ctx) { - - if (constraints.isEmpty()) { - if (order == null || order == 1) { - return; - } else if (order <= 0) { - throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); - } - // converting legacy -> new specification - int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); - if (order > currentOrder) { - LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", - order, currentOrder); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - // i.e. currentOrder >= order, i.e. currentOrder > order-1 - int newOrder = currentOrder - (order - 1); - assert newOrder > 0; - constraints = Collections.singletonList(new OrderConstraintsType(prismContext) - .order(order) - .resetOrder(newOrder)); - } - - OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); - Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? - summaryConstraints.getResetOrder() : null; - - if (resetSummaryTo != null) { - int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; - if (summaryBackwards < 0) { - // or should we throw an exception? - LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", - summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } else if (summaryBackwards > 0) { -// MultiSet backRelations = new HashMultiSet<>(); - int assignmentsSeen = 0; - int i = assignmentPath.size()-1; - while (assignmentsSeen < summaryBackwards) { - if (i < 0) { - LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", - summaryBackwards, assignmentsSeen, assignmentPath); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); - if (segment.isAssignment()) { - if (!relationRegistry.isDelegation(segment.getRelation())) { - // backRelations.add(segment.getRelation()); - assignmentsSeen++; - LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, - assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); - } - } else { - AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld - for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { - if (constraint.getResetOrder() != null && constraint.getRelation() != null) { - LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" - + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, - constraint, segment); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - } - if (segment.getLastEqualOrderSegmentIndex() != null) { - i = segment.getLastEqualOrderSegmentIndex(); - continue; - } - } - i--; - } - nextSegment.setLastEqualOrderSegmentIndex(i); - evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); - targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); - } else { - // summaryBackwards is 0 - nothing to change - } - for (OrderConstraintsType constraint : constraints) { - if (constraint.getRelation() != null && constraint.getResetOrder() != null) { - LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", - constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, - evaluationOrderHolder.getValue().getSummaryOrder(), constraint); - } - } - } else { - EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); - for (OrderConstraintsType constraint : constraints) { - if (constraint.getResetOrder() != null) { - assert constraint.getRelation() != null; // already processed above - int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); - int newOrder = constraint.getResetOrder(); - if (newOrder > currentOrder) { - LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), - currentOrder, newOrder, constraint); - } else if (newOrder < currentOrder) { - evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); - LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); - } else { - LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); - } - } - } - Map difference = beforeChange.diff(evaluationOrderHolder.getValue()); - targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); - } - - if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - } - if (!targetEvaluationOrderHolder.getValue().isValid()) { - // some extreme cases like the one described in TestAssignmentProcessor2.test520 - makeUndefined(targetEvaluationOrderHolder); - } - if (!evaluationOrderHolder.getValue().isValid()) { - throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); - } - } - - @SafeVarargs - private final void makeUndefined(Holder... holders) { // final because of SafeVarargs (on java8) - for (Holder holder : holders) { - holder.setValue(EvaluationOrderImpl.UNDEFINED); - } - } - - private void addToMembershipLists(AssignmentHolderType targetToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setObject(targetToAdd.asPrismObject()); - valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); - - addToMembershipLists(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); - } - - private void setAsTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { - if (targetToSet instanceof OrgType) { - if (BooleanUtils.isTrue(((OrgType)targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { - if (ctx.assignmentPath.hasOnlyOrgs()) { - ctx.evalAssignment.setTenantOid(targetToSet.getOid()); - } - } - } - } - - private void addToMembershipLists(ObjectReferenceType referenceToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setOid(referenceToAdd.getOid()); - valueToAdd.setTargetType(referenceToAdd.getType()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(referenceToAdd.getTargetName()); - - Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); - addToMembershipLists(valueToAdd, targetClass, relation, referenceToAdd, ctx); - } - - private void addToMembershipLists(PrismReferenceValue valueToAdd, Class targetClass, QName relation, - Object targetDesc, EvaluationContext ctx) { - if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, relationRegistry)) { - addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); - } else { - if (AbstractRoleType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); - } - } - if (OrgType.class.isAssignableFrom(targetClass) && relationRegistry.isStoredIntoParentOrgRef(relation)) { - addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); - } - if (ArchetypeType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); - } - } - - private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, - Object targetDesc) { - if (!collection.contains(valueToAdd)) { - LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); - collection.add(valueToAdd); - } else { - LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); - } - } - - private boolean isNonNegative(PlusMinusZero mode) { - // mode == null is also considered negative, because it is a combination of PLUS and MINUS; - // so the net result is that for both old and new state there exists an unsatisfied condition on the path. - return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; - } - - private boolean isChanged(PlusMinusZero mode) { - // mode == null is also considered negative, because it is a combination of PLUS and MINUS; - // so the net result is that for both old and new state there exists an unsatisfied condition on the path. - return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; - } - - private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, AssignmentHolderType targetType, QName relation) - throws SchemaException { - if (targetType instanceof AbstractRoleType || targetType instanceof TaskType) { //TODO: - // OK, just go on - } else if (targetType instanceof UserType) { - if (!relationRegistry.isDelegation(relation)) { - throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); - } - } - - private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { - if (inducementFocusType == null) { - return true; - } - Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); - if (inducementFocusClass == null) { - throw new SchemaException("Could not determine class for " + inducementFocusType); - } - if (lensContext.getFocusClass() == null) { - // should not occur; it would be probably safe to throw an exception here - LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", - inducementFocusType, lensContext.debugDump()); - return false; - } - return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { - AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); - AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); - if (isDeputyDelegation(nextAssignment)) { // delegation of delegation - return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); - } else { - // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) - // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). - return targetLimitation == null || nextAssignment.getTargetRef() == null || - FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, prismContext); - } - } - - private boolean isDeputyDelegation(AssignmentType assignmentType) { - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); - } - - private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { - Authorization authorization = new Authorization(authorizationType); - authorization.setSourceDescription(sourceDesc); - return authorization; - } - - private void assertSourceNotNull(ObjectType source, EvaluatedAssignment assignment) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); - } - } - - private void assertSourceNotNull(ObjectType source, ItemDeltaItem,PrismContainerDefinition> assignmentIdi) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); - } - } - - private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - return segment.getAssignment(ctx.evaluateOld); - } - - private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - //noinspection unchecked - PrismContainerValue assignmentContainerValue = assignmentType.asPrismContainerValue(); - PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); - if (assignmentContainer == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); - } - if (assignmentContainer.getDefinition() == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); - } - PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); - if (extensionContainer != null) { - if (extensionContainer.getDefinition() == null) { - throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); - } - - if (extensionContainer.getValue().getItems() == null) { - throw new SchemaException("Extension without items in assignment " + assignmentType + " in " + segment.sourceDescription + ", empty extension tag?"); - } - - for (Item item: extensionContainer.getValue().getItems()) { - if (item == null) { - throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - if (item.getDefinition() == null) { - throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - } - } - } - - private void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, - @NotNull List> targets, EvaluationContext ctx) { - assert ctx.evalAssignment.getTarget() == null; - if (targets.size() > 1) { - throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment(ctx.evaluateOld)); - } else if (!targets.isEmpty()) { - ctx.evalAssignment.setTarget(targets.get(0)); - } - } - - private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, - ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, - OperationResult result) throws ExpressionEvaluationException, - ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - MappingImpl.Builder,PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(); - builder = builder.mappingType(condition) - .contextDescription(contextDescription) - .sourceContext(focusOdo) - .originType(OriginType.ASSIGNMENTS) - .originObject(source) - .defaultTargetDefinition(prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) - .addVariableDefinitions(getAssignmentEvaluationVariables()) - .rootNode(focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) - .addAliasRegistration(ExpressionConstants.VAR_USER, null) - .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) - .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) - .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); - builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, prismContext); - - MappingImpl, PrismPropertyDefinition> mapping = builder.build(); - - mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, result); - - return mapping.getOutputTriple(); - } - - @Nullable - private QName getRelation(AssignmentType assignmentType) { - return assignmentType.getTargetRef() != null ? - relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; - } - - /* - * This "isMemberOf iteration" section is an experimental implementation of MID-5366. - * - * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef - * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects - * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor - * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, - * using updated check results. - * - * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). - */ - public boolean isMemberOf(String targetOid) { - if (targetOid == null) { - throw new IllegalArgumentException("Null targetOid in isMemberOf call"); - } - MemberOfInvocation existingInvocation = findInvocation(targetOid); - if (existingInvocation != null) { - return existingInvocation.result; - } else { - boolean result = computeIsMemberOfDuringEvaluation(targetOid); - memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); - return result; - } - } - - private MemberOfInvocation findInvocation(String targetOid) { - List matching = memberOfInvocations.stream() - .filter(invocation -> targetOid.equals(invocation.targetOid)) - .collect(Collectors.toList()); - if (matching.isEmpty()) { - return null; - } else if (matching.size() == 1) { - return matching.get(0); - } else { - throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); - } - } - - private boolean computeIsMemberOfDuringEvaluation(String targetOid) { - // TODO Or should we consider evaluateOld? - PrismObject focus = focusOdo.getNewObject(); - return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); - } - - public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { - if (!memberOfInvocations.isEmpty()) { - // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic - List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() - .filter(EvaluatedAssignmentImpl::isValid) - .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) - .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) - .collect(Collectors.toList()); - LOGGER.trace("Computed new membership: {}", membership); - return updateMemberOfInvocations(membership); - } else { - return false; - } - } - - private boolean updateMemberOfInvocations(List newMembership) { - boolean changed = false; - for (MemberOfInvocation invocation : memberOfInvocations) { - boolean newResult = containsMember(newMembership, invocation.targetOid); - if (newResult != invocation.result) { - LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); - invocation.result = newResult; - changed = true; - } - } - return changed; - } - - // todo generalize a bit (e.g. by including relation) - private boolean containsMember(List membership, String targetOid) { - return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); - } - - public static final class Builder { - private RepositoryService repository; - private ObjectDeltaObject focusOdo; - private LensContext lensContext; - private String channel; - private ObjectResolver objectResolver; - private SystemObjectCache systemObjectCache; - private RelationRegistry relationRegistry; - private PrismContext prismContext; - private MappingFactory mappingFactory; - private ActivationComputer activationComputer; - private XMLGregorianCalendar now; - private boolean loginMode = false; - private PrismObject systemConfiguration; - private MappingEvaluator mappingEvaluator; - - public Builder() { - } - - public Builder repository(RepositoryService val) { - repository = val; - return this; - } - - public Builder focusOdo(ObjectDeltaObject val) { - focusOdo = val; - return this; - } - - public Builder lensContext(LensContext val) { - lensContext = val; - return this; - } - - public Builder channel(String val) { - channel = val; - return this; - } - - public Builder objectResolver(ObjectResolver val) { - objectResolver = val; - return this; - } - - public Builder systemObjectCache(SystemObjectCache val) { - systemObjectCache = val; - return this; - } - - public Builder relationRegistry(RelationRegistry val) { - relationRegistry = val; - return this; - } - - public Builder prismContext(PrismContext val) { - prismContext = val; - return this; - } - - public Builder mappingFactory(MappingFactory val) { - mappingFactory = val; - return this; - } - - public Builder activationComputer(ActivationComputer val) { - activationComputer = val; - return this; - } - - public Builder now(XMLGregorianCalendar val) { - now = val; - return this; - } - - public Builder loginMode(boolean val) { - loginMode = val; - return this; - } - - public Builder systemConfiguration(PrismObject val) { - systemConfiguration = val; - return this; - } - - public Builder mappingEvaluator(MappingEvaluator val) { - mappingEvaluator = val; - return this; - } - - public AssignmentEvaluator build() { - return new AssignmentEvaluator<>(this); - } - } - - private static class MemberOfInvocation { - private final String targetOid; - private boolean result; - - private MemberOfInvocation(String targetOid, boolean result) { - this.targetOid = targetOid; - this.result = result; - } - - @Override - public String toString() { - return "MemberOfInvocation{" + - "targetOid='" + targetOid + '\'' + - ", result=" + result + - '}'; - } - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import java.util.stream.Collectors; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.ActivationComputer; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.Holder; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.PolicyViolationException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; + +import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, + * authorizations, policy rules, and so on. + * + * @author semancik + */ +public class AssignmentEvaluator { + + private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); + + private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; + private static final String OP_EVALUATE_FROM_SEGMENT = AssignmentEvaluator.class.getName()+".evaluateFromSegment"; + + private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); + + // "Configuration parameters" + private final RepositoryService repository; + private final ObjectDeltaObject focusOdo; + private final LensContext lensContext; + private final String channel; + private final ObjectResolver objectResolver; + private final SystemObjectCache systemObjectCache; + private final RelationRegistry relationRegistry; + private final PrismContext prismContext; + private final MappingFactory mappingFactory; + private final ActivationComputer activationComputer; + private final XMLGregorianCalendar now; + private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) + private final PrismObject systemConfiguration; + private final MappingEvaluator mappingEvaluator; + private final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; + private final LifecycleStateModelType focusStateModel; + + // Evaluation state + private final List memberOfInvocations = new ArrayList<>(); // experimental + + private AssignmentEvaluator(Builder builder) { + repository = builder.repository; + focusOdo = builder.focusOdo; + lensContext = builder.lensContext; + channel = builder.channel; + objectResolver = builder.objectResolver; + systemObjectCache = builder.systemObjectCache; + relationRegistry = builder.relationRegistry; + prismContext = builder.prismContext; + mappingFactory = builder.mappingFactory; + activationComputer = builder.activationComputer; + now = builder.now; + loginMode = builder.loginMode; + systemConfiguration = builder.systemConfiguration; + mappingEvaluator = builder.mappingEvaluator; + evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); + + LensFocusContext focusContext = lensContext.getFocusContext(); + if (focusContext != null) { + focusStateModel = focusContext.getLifecycleModel(); + } else { + focusStateModel = null; + } + } + + public RepositoryService getRepository() { + return repository; + } + + @SuppressWarnings("unused") + public ObjectDeltaObject getFocusOdo() { + return focusOdo; + } + + public LensContext getLensContext() { + return lensContext; + } + + public String getChannel() { + return channel; + } + + public ObjectResolver getObjectResolver() { + return objectResolver; + } + + public SystemObjectCache getSystemObjectCache() { + return systemObjectCache; + } + + public PrismContext getPrismContext() { + return prismContext; + } + + public MappingFactory getMappingFactory() { + return mappingFactory; + } + + public ActivationComputer getActivationComputer() { + return activationComputer; + } + + public XMLGregorianCalendar getNow() { + return now; + } + + @SuppressWarnings("unused") + public boolean isLoginMode() { + return loginMode; + } + + public PrismObject getSystemConfiguration() { + return systemConfiguration; + } + + @SuppressWarnings("unused") + public MappingEvaluator getMappingEvaluator() { + return mappingEvaluator; + } + + public void reset(boolean alsoMemberOfInvocations) { + evaluatedAssignmentTargetCache.reset(); + if (alsoMemberOfInvocations) { + memberOfInvocations.clear(); + } + } + + // This is to reduce the number of parameters passed between methods in this class. + // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. + // (There is single EvaluationContext instance for any call to evaluate().) + private class EvaluationContext { + @NotNull private final EvaluatedAssignmentImpl evalAssignment; + @NotNull private final AssignmentPathImpl assignmentPath; + // The primary assignment mode tells whether the primary assignment was added, removed or it is unchanged. + // The primary assignment is the first assignment in the assignment path, the assignment that is located in the + // focal object. + private final PlusMinusZero primaryAssignmentMode; + private final boolean evaluateOld; + private final Task task; + private EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, + @NotNull AssignmentPathImpl assignmentPath, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, Task task) { + this.evalAssignment = evalAssignment; + this.assignmentPath = assignmentPath; + this.primaryAssignmentMode = primaryAssignmentMode; + this.evaluateOld = evaluateOld; + this.task = task; + } + } + + /** + * evaluateOld: If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. + */ + public EvaluatedAssignmentImpl evaluate( + ItemDeltaItem,PrismContainerDefinition> assignmentIdi, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, + AssignmentOrigin origin, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + OperationResult result = parentResult.subresult(OP_EVALUATE) + .setMinor() + .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) + .addParam("evaluateOld", evaluateOld) + .addArbitraryObjectAsParam("source", source) + .addParam("sourceDescription", sourceDescription) + .addArbitraryObjectAsParam("origin", origin) + .build(); + AssignmentEvaluationTraceType trace; + if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { + trace = new AssignmentEvaluationTraceType(prismContext) + .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) + .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) + .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) + .evaluateOld(evaluateOld) + .textSource(source != null ? source.asPrismObject().debugDump() : "null") + .sourceDescription(sourceDescription); + result.addTrace(trace); + } else { + trace = null; + } + try { + assertSourceNotNull(source, assignmentIdi); + + EvaluatedAssignmentImpl evalAssignmentImpl = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); + + EvaluationContext ctx = new EvaluationContext( + evalAssignmentImpl, + new AssignmentPathImpl(prismContext), + primaryAssignmentMode, evaluateOld, task); + + evaluatedAssignmentTargetCache.resetForNextAssignment(); + + AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true, + evaluateOld, relationRegistry, prismContext); + segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); + segment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); + segment.setValidityOverride(true); + segment.setPathToSourceValid(true); + segment.setProcessMembership(true); + segment.setRelation(getRelation(getAssignmentType(segment, ctx))); + + evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx, result); + + if (segment.getTarget() != null) { + result.addContext("assignmentTargetName", PolyString.getOrig(segment.getTarget().getName())); + } + + LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); + if (trace != null) { + trace.setTextResult(ctx.evalAssignment.debugDump()); + } + result.computeStatusIfUnknown(); + return ctx.evalAssignment; + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } + } + + private AssignmentType getAssignmentBean( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + boolean old) { + PrismContainerValue pcv = assignmentIdi.getSingleValue(old); + return pcv != null ? pcv.asContainerable() : null; + } + + private EvaluationOrder getInitialEvaluationOrder( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + EvaluationContext ctx) { + AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); + return EvaluationOrderImpl.zero(relationRegistry).advance(getRelation(assignmentType)); + } + + /** + * @param relativeMode + * + * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). + * This is a mode relative to the primary assignment. It does NOT tell whether the assignment as a whole + * is added or removed. It tells whether the part of the assignment that we are processing is to be + * added or removed. This may happen, e.g. if a condition in an existing assignment turns from false to true. + * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. + * The relative mode always starts at ZERO, even for added or removed assignments. + * + * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old + * state the value is false, and in new state the value is true), then the mode is PLUS. + * + * This "triples algebra" is based on the following two methods: + * + * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines + * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, + * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. + * + * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition + * and determines the new mode (commutatively): + * + * PLUS + PLUS/ZERO = PLUS + * MINUS + MINUS/ZERO = MINUS + * ZERO + ZERO = ZERO + * PLUS + MINUS = none + * + * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally + * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally + * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not + * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). + * + * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. + */ + private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, EvaluationContext ctx, + OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + OperationResult result = parentResult.subresult(OP_EVALUATE_FROM_SEGMENT) + .setMinor() + .addParam("segment", segment.shortDump()) + .addArbitraryObjectAsParam("relativeMode", relativeMode) + .build(); + AssignmentSegmentEvaluationTraceType trace; + if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { + trace = new AssignmentSegmentEvaluationTraceType(prismContext) + .segment(segment.toAssignmentPathSegmentType(true)) + .mode(PlusMinusZeroType.fromValue(relativeMode)); + result.addTrace(trace); + } else { + trace = null; + } + try { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("*** Evaluate from segment: {}", segment); + LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); + LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); + LOGGER.trace("*** mode: {}, process membership: {}", relativeMode, segment.isProcessMembership()); + LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); + } + + assertSourceNotNull(segment.source, ctx.evalAssignment); + checkSchema(segment, ctx); + + ctx.assignmentPath.add(segment); + LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDumpLazily()); + + boolean evaluateContent; + AssignmentType assignmentType = getAssignmentType(segment, ctx); + MappingType assignmentCondition = assignmentType.getCondition(); + if (assignmentCondition != null) { + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(assignmentCondition, + segment.source, assignmentPathVariables, + "condition in assignment in " + segment.getSourceDescription(), ctx, result); + boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); + boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); + PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); + if (modeFromCondition == null) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", + FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); + } + evaluateContent = false; + } else { + PlusMinusZero origMode = relativeMode; + relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); + LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, + modeFromCondition, relativeMode); + evaluateContent = true; + } + } else { + evaluateContent = true; + } + + if (ctx.assignmentPath.isEmpty() && ctx.evalAssignment.isVirtual()) { + segment.setValidityOverride(ctx.evalAssignment.isVirtual()); + } + + boolean isValid = + (evaluateContent && evaluateSegmentContent(segment, relativeMode, ctx, result)) || ctx.evalAssignment.isVirtual(); + + ctx.assignmentPath.removeLast(segment); + if (ctx.assignmentPath.isEmpty()) { // direct assignment + ctx.evalAssignment.setValid(isValid); + } + + LOGGER.trace("evalAssignment isVirtual {} ", ctx.evalAssignment.isVirtual()); + if (segment.getSource() != null) { + result.addContext("segmentSourceName", PolyString.getOrig(segment.getSource().getName())); + } + if (segment.getTarget() != null) { + result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); + } + result.addArbitraryObjectAsReturn("relativeMode", relativeMode); + result.addReturn("evaluateContent", evaluateContent); + result.addReturn("isValid", isValid); + if (trace != null) { + trace.setTextResult(segment.debugDump()); + } + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + // "content" means "payload + targets" here + private boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, + PlusMinusZero relativeMode, EvaluationContext ctx, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + assert ctx.assignmentPath.last() == segment; + + final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; + + AssignmentType assignment = getAssignmentType(segment, ctx); + + // Assignment validity is checked with respect to the assignment source, not to the focus object. + // So, if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. + // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are + // in active lifecycle states. See also MID-6114. + AssignmentHolderType source = segment.isMatchingOrder() ? focusOdo.getNewObject().asObjectable() : segment.getSource(); + boolean isAssignmentValid = LensUtil.isAssignmentValid(source, assignment, now, activationComputer, focusStateModel); + if (isAssignmentValid || segment.isValidityOverride()) { + // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. + // Direct assignments are visited even if they are not valid (i.e. effectively disabled). + // It is because we need to collect e.g. assignment policy rules for them. + // Also because we could have deltas that disable/enable these assignments. + boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; + if (!loginMode && segment.isMatchingOrder()) { + if (assignment.getConstruction() != null) { + collectConstruction(segment, relativeMode, reallyValid, ctx); + } + if (assignment.getPersonaConstruction() != null) { + collectPersonaConstruction(segment, relativeMode, reallyValid, ctx); + } + if (assignment.getFocusMappings() != null) { + if (reallyValid && relativeMode != null) { // null relative mode means both PLUS and MINUS + collectFocusMappings(segment, relativeMode, ctx); + } + } + } + if (!loginMode && assignment.getPolicyRule() != null) { + // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when + // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. + // TODO is it ok? + if (isNonNegative(relativeMode)) { + if (segment.isMatchingOrder()) { + collectPolicyRule(true, segment, ctx); + } + if (segment.isMatchingOrderForTarget()) { + collectPolicyRule(false, segment, ctx); + } + } + } + if (assignment.getTargetRef() != null) { + QName relation = getRelation(assignment); + if (loginMode && !relationRegistry.isProcessedOnLogin(relation)) { + LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", assignment.getTargetRef().getOid(), relation); + // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // We want to make this configurable in the future MID-3581 + } else if (!loginMode && !isChanged(ctx.primaryAssignmentMode) && + !relationRegistry.isProcessedOnRecompute(relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { + LOGGER.debug("Skipping processing of assignment target {} because relation {} is configured for recompute skip (mode={})", assignment.getTargetRef().getOid(), relation, relativeMode); + // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules + // We want to make this configurable in the future MID-3581 + + // Important: but we still want this to be reflected in roleMembershipRef + if ((isNonNegative(relativeMode)) && segment.isProcessMembership()) { + if (assignment.getTargetRef().getOid() != null) { + addToMembershipLists(assignment.getTargetRef(), relation, ctx); + } else { + // no OID, so we have to resolve the filter + for (PrismObject targetObject : getTargets(segment, ctx, result)) { + ObjectType target = targetObject.asObjectable(); + if (target instanceof FocusType) { + addToMembershipLists((FocusType) target, relation, ctx); + } + } + } + } + } else { + List> targets = getTargets(segment, ctx, result); + LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignment.getId(), targets); + if (isDirectAssignment) { + setEvaluatedAssignmentTarget(segment, targets, ctx); + } + for (PrismObject target : targets) { + if (hasCycle(segment, target, ctx)) { + continue; + } + if (isDelegationToNonDelegableTarget(assignment, target, ctx)) { + continue; + } + evaluateSegmentTarget(segment, relativeMode, reallyValid, (AssignmentHolderType) target.asObjectable(), relation, ctx, result); + } + } + } + } else { + LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignment); + } + return isAssignmentValid; + } + + private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { + return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(lensContext.getOptions()); + } + + private boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject target, + EvaluationContext ctx) { + AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); + if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { + return false; + } + if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", + FocusTypeUtil.dumpAssignment(assignmentType), target); + } + return true; + } else { + return false; + } + } + + // number of times any given target is allowed to occur in the assignment path + private static final int MAX_TARGET_OCCURRENCES = 2; + + private boolean hasCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject target, + EvaluationContext ctx) throws PolicyViolationException { + // TODO reconsider this + if (target.getOid().equals(segment.source.getOid())) { + throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); + } + // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" + // as currently it is always true + // TODO reconsider this + int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); + if (count >= MAX_TARGET_OCCURRENCES) { + LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", + MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); + return true; + } else { + return false; + } + } + + private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + ConstructionType constructionType = assignmentType.getConstruction(); + + LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); + + Construction construction = new Construction<>(constructionType, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(focusOdo); + construction.setLensContext(lensContext); + construction.setObjectResolver(objectResolver); + construction.setPrismContext(prismContext); + construction.setMappingFactory(mappingFactory); + construction.setMappingEvaluator(mappingEvaluator); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(channel); + construction.setOrderOneObject(segment.getOrderOneObject()); + construction.setValid(isValid); + construction.setRelativityMode(mode); + + // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. + if (mode == null) { + return; // null mode (i.e. plus + minus) means 'ignore the payload' + } + ctx.evalAssignment.addConstruction(construction, mode); + } + + private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + if (mode == null) { + return; // null mode (i.e. plus + minus) means 'ignore the payload' + } + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + PersonaConstructionType constructionType = assignmentType.getPersonaConstruction(); + + LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); + + PersonaConstruction construction = new PersonaConstruction<>(constructionType, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(focusOdo); + construction.setLensContext(lensContext); + construction.setObjectResolver(objectResolver); + construction.setPrismContext(prismContext); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(channel); + construction.setValid(isValid); + construction.setRelativityMode(mode); + + ctx.evalAssignment.addPersonaConstruction(construction, mode); + } + + private void collectFocusMappings(AssignmentPathSegmentImpl segment, @NotNull PlusMinusZero relativeMode, EvaluationContext ctx) + throws SchemaException { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentBean = getAssignmentType(segment, ctx); + MappingsType mappingsBean = assignmentBean.getFocusMappings(); + + LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", + mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + + for (MappingType mappingBean: mappingsBean.getMapping()) { + AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, + ctx.evalAssignment, relativeMode, assignmentPathVariables, segment.sourceDescription); + ctx.evalAssignment.addFocusMappingEvaluationRequest(request); + } + } + + private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + PolicyRuleType policyRuleType = assignmentType.getPolicyRule(); + + LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); + + EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), ctx.assignmentPath.clone(), prismContext); + + if (focusRule) { + ctx.evalAssignment.addFocusPolicyRule(policyRule); + } else { + if (appliesDirectly(ctx.assignmentPath)) { + ctx.evalAssignment.addThisTargetPolicyRule(policyRule); + } else { + ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); + } + } + } + + private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { + assert !assignmentPath.isEmpty(); + // TODO what about deputy relation which does not increase summaryOrder? + long zeroOrderCount = assignmentPath.getSegments().stream() + .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) + .count(); + return zeroOrderCount == 1; + } + + @NotNull + private List> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, + OperationResult result) throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + if (assignmentType.getTargetRef() != null) { + try { + return resolveTargets(segment, ctx, result); + } catch (ObjectNotFoundException ex) { + // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing + // an exception would prohibit any operations with the users that have the role, including removal of the reference. + // The failure is recorded in the result and we will log it. It should be enough. + LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); + // For OrgType references we trigger the reconciliation (see MID-2242) + ctx.evalAssignment.setForceRecon(true); + return Collections.emptyList(); + } + } else { + throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); + } + } + + @NotNull + private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, + OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + ObjectReferenceType targetRef = assignmentType.getTargetRef(); + String oid = targetRef.getOid(); + + // Target is referenced, need to fetch it + Class targetClass; + if (targetRef.getType() != null) { + targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); + if (targetClass == null) { + throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); + } + + if (oid == null) { + LOGGER.trace("Resolving dynamic target ref"); + if (targetRef.getFilter() == null){ + throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); + } + return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); + } else { + LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); + PrismObject target; + try { + target = repository.getObject(targetClass, oid, null, result); + } catch (SchemaException e) { + throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); + } + // Not handling object not found exception here. Caller will handle that. + if (target == null) { + throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); + } + return Collections.singletonList(target); + } + } + + @NotNull + private List> resolveTargetsFromFilter(Class targetClass, + SearchFilterType filter, AssignmentPathSegmentImpl segment, + EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, result)); + try { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), prismContext); + variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + if (assignmentPathVariables != null) { + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); + } + variables.addVariableDefinitions(getAssignmentEvaluationVariables()); + ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, targetClass); + // TODO: expression profile should be determined from the holding object archetype + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, result); + if (evaluatedFilter == null) { + throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); + } + + return repository.searchObjects(targetClass, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); + // we don't check for no targets here; as we don't care for referential integrity + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + private ExpressionVariables getAssignmentEvaluationVariables() { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); + // e.g. AssignmentEvaluator itself, model context, etc (when needed) + return variables; + } + + // Note: This method must be single-return after targetEvaluationInformation is established. + private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, boolean isAssignmentPathValid, + AssignmentHolderType target, QName relation, EvaluationContext ctx, + OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + assert ctx.assignmentPath.last() == segment; + + segment.setTarget(target); + segment.setRelation(relation); // probably not needed + + if (evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { + LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); + InternalMonitor.recordRoleEvaluationSkip(target, true); + return; + } + + LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); + + checkRelationWithTarget(segment, target, relation); + + LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), systemConfiguration); + boolean isTargetValid = LensUtil.isFocusValid(target, now, activationComputer, targetStateModel); + boolean isPathAndTargetValid = isAssignmentPathValid && isTargetValid; + + LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); + InternalMonitor.recordRoleEvaluation(target, true); + + AssignmentTargetEvaluationInformation targetEvaluationInformation; + if (isPathAndTargetValid) { + // Cache it immediately, even before evaluation. So if there is a cycle in the role path + // then we can detect it and skip re-evaluation of aggressively idempotent roles. + // + // !!! Ensure we will not return without updating this object (except for exceptions). So please keep this + // method a single-return one after this point. We did not want to complicate things using try...finally. + // + targetEvaluationInformation = evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); + } else { + targetEvaluationInformation = null; + } + int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + + boolean skipOnConditionResult = false; + + if (isTargetValid && target instanceof AbstractRoleType) { + MappingType roleCondition = ((AbstractRoleType)target).getCondition(); + if (roleCondition != null) { + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(roleCondition, + segment.source, assignmentPathVariables, + "condition in " + segment.getTargetDescription(), ctx, result); + boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); + boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); + PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); + if (modeFromCondition == null) { + skipOnConditionResult = true; + LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", + target, condOld, condNew); + } else { + PlusMinusZero origMode = relativeMode; + relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); + LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", target, condOld, condNew, + origMode, modeFromCondition, relativeMode); + } + } + } + + if (!skipOnConditionResult) { + EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( + target.asPrismObject(), + segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised + ctx.assignmentPath.clone(), + getAssignmentType(segment, ctx), + isPathAndTargetValid); + ctx.evalAssignment.addRole(evalAssignmentTarget, relativeMode); + + // we need to evaluate assignments also for disabled targets, because of target policy rules + // ... but only for direct ones! + if (isTargetValid || ctx.assignmentPath.size() == 1) { + for (AssignmentType nextAssignment : target.getAssignment()) { + evaluateAssignment(segment, relativeMode, isPathAndTargetValid, ctx, target, relation, nextAssignment, result); + } + } + + // we need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127 + if (isNonNegative(relativeMode) && segment.isProcessMembership()) { + addToMembershipLists(target, relation, ctx); + } + + if (isTargetValid) { + if (isNonNegative(relativeMode)) { + setAsTenantRef(target, ctx); + } + + // We continue evaluation even if the relation is non-membership and non-delegation. + // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. + + if (target instanceof AbstractRoleType) { + for (AssignmentType roleInducement : ((AbstractRoleType) target).getInducement()) { + evaluateInducement(segment, relativeMode, isPathAndTargetValid, ctx, target, roleInducement, result); + } + } + + if (segment.isMatchingOrder() && target instanceof AbstractRoleType && isNonNegative(relativeMode)) { + for (AuthorizationType authorizationType : ((AbstractRoleType) target).getAuthorization()) { + Authorization authorization = createAuthorization(authorizationType, target.toString()); + if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { + ctx.evalAssignment.addAuthorization(authorization); + } + } + AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); + if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations() + .contains(adminGuiConfiguration)) { + ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); + } + } + } + } + int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + + LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, targetPolicyRulesOnEntry, + targetPolicyRulesOnExit); + if (targetEvaluationInformation != null) { + targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); + } + } + + // TODO revisit this + private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { + EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); + if (evaluationOrder.getSummaryOrder() == 1) { + return segment.getTarget(); + } else { + if (segment.getSource() != null) { // should be always the case... + return segment.getSource(); + } else { + return segment.getTarget(); + } + } + } + + private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + AssignmentHolderType target, QName relation, AssignmentType nextAssignment, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (relationRegistry.isDelegation(relation)) { + // We have to handle assignments as though they were inducements here. + if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", + FocusTypeUtil.dumpAssignment(nextAssignment)); + } + return; + } + } + QName nextRelation = getRelation(nextAssignment); + EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); + EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", + segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpAssignment(nextAssignment), nextEvaluationOrder); + } + String nextSourceDescription = target+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, nextSourceDescription, nextAssignment, true, relationRegistry, prismContext); + nextSegment.setRelation(nextRelation); + nextSegment.setEvaluationOrder(nextEvaluationOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); + nextSegment.setOrderOneObject(orderOneObject); + nextSegment.setPathToSourceValid(isValid); + /* + * We obviously want to process membership from the segment if it's of matching order. + * + * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to + * collect membership from all assignments of any user that we find on the assignment path. + * + * TODO: does this work for invalid (effectiveStatus = disabled) assignments? + */ + boolean isUser = target instanceof UserType; + nextSegment.setProcessMembership(nextSegment.isMatchingOrder() || isUser); + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx, result); + } + + private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + AssignmentHolderType target, AssignmentType inducement, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (!isInducementApplicableToFocusType(inducement.getFocusType())) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", + FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), target.getClass().getSimpleName()); + } + return; + } + if (!isAllowedByLimitations(segment, inducement, ctx)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); + } + return; + } + String subSourceDescription = target+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, subSourceDescription, inducement, false, relationRegistry, prismContext); + // note that 'old' and 'new' values for assignment in nextSegment are the same + boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrder(), nextSegment.getAssignmentNew()); + boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrderForTarget(), nextSegment.getAssignmentNew()); + + Holder nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); + Holder nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); + adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment, ctx); + nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); + + nextSegment.setOrderOneObject(orderOneObject); + nextSegment.setPathToSourceValid(isValid); + nextSegment.setProcessMembership(nextIsMatchingOrder); + nextSegment.setRelation(getRelation(inducement)); + + // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into + // inducements with non-matching order: for example because we need to extract target-related policy rules + // (these are stored with order of one less than orders for focus-related policy rules). + // + // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. + // processMembership attribute to false for these inducements. + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", + segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpInducementConstraints(inducement), + FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); + } + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx, result); + } + + private void adjustOrder(Holder evaluationOrderHolder, Holder targetEvaluationOrderHolder, + List constraints, Integer order, AssignmentPathImpl assignmentPath, + AssignmentPathSegmentImpl nextSegment, EvaluationContext ctx) { + + if (constraints.isEmpty()) { + if (order == null || order == 1) { + return; + } else if (order <= 0) { + throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); + } + // converting legacy -> new specification + int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); + if (order > currentOrder) { + LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", + order, currentOrder); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + // i.e. currentOrder >= order, i.e. currentOrder > order-1 + int newOrder = currentOrder - (order - 1); + assert newOrder > 0; + constraints = Collections.singletonList(new OrderConstraintsType(prismContext) + .order(order) + .resetOrder(newOrder)); + } + + OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); + Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? + summaryConstraints.getResetOrder() : null; + + if (resetSummaryTo != null) { + int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; + if (summaryBackwards < 0) { + // or should we throw an exception? + LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", + summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } else if (summaryBackwards > 0) { +// MultiSet backRelations = new HashMultiSet<>(); + int assignmentsSeen = 0; + int i = assignmentPath.size()-1; + while (assignmentsSeen < summaryBackwards) { + if (i < 0) { + LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", + summaryBackwards, assignmentsSeen, assignmentPath); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); + if (segment.isAssignment()) { + if (!relationRegistry.isDelegation(segment.getRelation())) { + // backRelations.add(segment.getRelation()); + assignmentsSeen++; + LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, + assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); + } + } else { + AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld + for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { + if (constraint.getResetOrder() != null && constraint.getRelation() != null) { + LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" + + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, + constraint, segment); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + } + if (segment.getLastEqualOrderSegmentIndex() != null) { + i = segment.getLastEqualOrderSegmentIndex(); + continue; + } + } + i--; + } + nextSegment.setLastEqualOrderSegmentIndex(i); + evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); + targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); + } else { + // summaryBackwards is 0 - nothing to change + } + for (OrderConstraintsType constraint : constraints) { + if (constraint.getRelation() != null && constraint.getResetOrder() != null) { + LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", + constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, + evaluationOrderHolder.getValue().getSummaryOrder(), constraint); + } + } + } else { + EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); + for (OrderConstraintsType constraint : constraints) { + if (constraint.getResetOrder() != null) { + assert constraint.getRelation() != null; // already processed above + int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); + int newOrder = constraint.getResetOrder(); + if (newOrder > currentOrder) { + LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), + currentOrder, newOrder, constraint); + } else if (newOrder < currentOrder) { + evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); + LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); + } else { + LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); + } + } + } + Map difference = beforeChange.diff(evaluationOrderHolder.getValue()); + targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); + } + + if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + } + if (!targetEvaluationOrderHolder.getValue().isValid()) { + // some extreme cases like the one described in TestAssignmentProcessor2.test520 + makeUndefined(targetEvaluationOrderHolder); + } + if (!evaluationOrderHolder.getValue().isValid()) { + throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); + } + } + + @SafeVarargs + private final void makeUndefined(Holder... holders) { // final because of SafeVarargs (on java8) + for (Holder holder : holders) { + holder.setValue(EvaluationOrderImpl.UNDEFINED); + } + } + + private void addToMembershipLists(AssignmentHolderType targetToAdd, QName relation, EvaluationContext ctx) { + PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); + valueToAdd.setObject(targetToAdd.asPrismObject()); + valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); + + addToMembershipLists(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); + } + + private void setAsTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { + if (targetToSet instanceof OrgType) { + if (BooleanUtils.isTrue(((OrgType)targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { + if (ctx.assignmentPath.hasOnlyOrgs()) { + ctx.evalAssignment.setTenantOid(targetToSet.getOid()); + } + } + } + } + + private void addToMembershipLists(ObjectReferenceType referenceToAdd, QName relation, EvaluationContext ctx) { + PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); + valueToAdd.setOid(referenceToAdd.getOid()); + valueToAdd.setTargetType(referenceToAdd.getType()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(referenceToAdd.getTargetName()); + + Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); + addToMembershipLists(valueToAdd, targetClass, relation, referenceToAdd, ctx); + } + + private void addToMembershipLists(PrismReferenceValue valueToAdd, Class targetClass, QName relation, + Object targetDesc, EvaluationContext ctx) { + if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, relationRegistry)) { + addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); + } else { + if (AbstractRoleType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); + } + } + if (OrgType.class.isAssignableFrom(targetClass) && relationRegistry.isStoredIntoParentOrgRef(relation)) { + addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); + } + if (ArchetypeType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); + } + } + + private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, + Object targetDesc) { + if (!collection.contains(valueToAdd)) { + LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); + collection.add(valueToAdd); + } else { + LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); + } + } + + private boolean isNonNegative(PlusMinusZero mode) { + // mode == null is also considered negative, because it is a combination of PLUS and MINUS; + // so the net result is that for both old and new state there exists an unsatisfied condition on the path. + return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; + } + + private boolean isChanged(PlusMinusZero mode) { + // mode == null is also considered negative, because it is a combination of PLUS and MINUS; + // so the net result is that for both old and new state there exists an unsatisfied condition on the path. + return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; + } + + private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, AssignmentHolderType targetType, QName relation) + throws SchemaException { + if (targetType instanceof AbstractRoleType || targetType instanceof TaskType) { //TODO: + // OK, just go on + } else if (targetType instanceof UserType) { + if (!relationRegistry.isDelegation(relation)) { + throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); + } + } + + private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { + if (inducementFocusType == null) { + return true; + } + Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); + if (inducementFocusClass == null) { + throw new SchemaException("Could not determine class for " + inducementFocusType); + } + if (lensContext.getFocusClass() == null) { + // should not occur; it would be probably safe to throw an exception here + LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", + inducementFocusType, lensContext.debugDump()); + return false; + } + return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { + AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); + AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); + if (isDeputyDelegation(nextAssignment)) { // delegation of delegation + return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); + } else { + // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) + // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). + return targetLimitation == null || nextAssignment.getTargetRef() == null || + FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, prismContext); + } + } + + private boolean isDeputyDelegation(AssignmentType assignmentType) { + ObjectReferenceType targetRef = assignmentType.getTargetRef(); + return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); + } + + private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { + Authorization authorization = new Authorization(authorizationType); + authorization.setSourceDescription(sourceDesc); + return authorization; + } + + private void assertSourceNotNull(ObjectType source, EvaluatedAssignment assignment) { + if (source == null) { + throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); + } + } + + private void assertSourceNotNull(ObjectType source, ItemDeltaItem,PrismContainerDefinition> assignmentIdi) { + if (source == null) { + throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); + } + } + + private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { + return segment.getAssignment(ctx.evaluateOld); + } + + private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + //noinspection unchecked + PrismContainerValue assignmentContainerValue = assignmentType.asPrismContainerValue(); + PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); + if (assignmentContainer == null) { + throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); + } + if (assignmentContainer.getDefinition() == null) { + throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); + } + PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); + if (extensionContainer != null) { + if (extensionContainer.getDefinition() == null) { + throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); + } + + if (extensionContainer.getValue().getItems() == null) { + throw new SchemaException("Extension without items in assignment " + assignmentType + " in " + segment.sourceDescription + ", empty extension tag?"); + } + + for (Item item: extensionContainer.getValue().getItems()) { + if (item == null) { + throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); + } + if (item.getDefinition() == null) { + throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); + } + } + } + } + + private void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, + @NotNull List> targets, EvaluationContext ctx) { + assert ctx.evalAssignment.getTarget() == null; + if (targets.size() > 1) { + throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment(ctx.evaluateOld)); + } else if (!targets.isEmpty()) { + ctx.evalAssignment.setTarget(targets.get(0)); + } + } + + private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, + ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, + OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + MappingImpl.Builder,PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(); + builder = builder.mappingType(condition) + .mappingKind(MappingKindType.ASSIGNMENT_CONDITION) + .contextDescription(contextDescription) + .sourceContext(focusOdo) + .originType(OriginType.ASSIGNMENTS) + .originObject(source) + .defaultTargetDefinition(prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) + .addVariableDefinitions(getAssignmentEvaluationVariables()) + .rootNode(focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) + .addAliasRegistration(ExpressionConstants.VAR_USER, null) + .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) + .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) + .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); + builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, prismContext); + + MappingImpl, PrismPropertyDefinition> mapping = builder.build(); + + mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, result); + + return mapping.getOutputTriple(); + } + + @Nullable + private QName getRelation(AssignmentType assignmentType) { + return assignmentType.getTargetRef() != null ? + relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; + } + + /* + * This "isMemberOf iteration" section is an experimental implementation of MID-5366. + * + * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef + * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects + * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor + * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, + * using updated check results. + * + * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). + */ + public boolean isMemberOf(String targetOid) { + if (targetOid == null) { + throw new IllegalArgumentException("Null targetOid in isMemberOf call"); + } + MemberOfInvocation existingInvocation = findInvocation(targetOid); + if (existingInvocation != null) { + return existingInvocation.result; + } else { + boolean result = computeIsMemberOfDuringEvaluation(targetOid); + memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); + return result; + } + } + + private MemberOfInvocation findInvocation(String targetOid) { + List matching = memberOfInvocations.stream() + .filter(invocation -> targetOid.equals(invocation.targetOid)) + .collect(Collectors.toList()); + if (matching.isEmpty()) { + return null; + } else if (matching.size() == 1) { + return matching.get(0); + } else { + throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); + } + } + + private boolean computeIsMemberOfDuringEvaluation(String targetOid) { + // TODO Or should we consider evaluateOld? + PrismObject focus = focusOdo.getNewObject(); + return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); + } + + public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { + if (!memberOfInvocations.isEmpty()) { + // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic + List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() + .filter(EvaluatedAssignmentImpl::isValid) + .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) + .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) + .collect(Collectors.toList()); + LOGGER.trace("Computed new membership: {}", membership); + return updateMemberOfInvocations(membership); + } else { + return false; + } + } + + private boolean updateMemberOfInvocations(List newMembership) { + boolean changed = false; + for (MemberOfInvocation invocation : memberOfInvocations) { + boolean newResult = containsMember(newMembership, invocation.targetOid); + if (newResult != invocation.result) { + LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); + invocation.result = newResult; + changed = true; + } + } + return changed; + } + + // todo generalize a bit (e.g. by including relation) + private boolean containsMember(List membership, String targetOid) { + return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); + } + + public static final class Builder { + private RepositoryService repository; + private ObjectDeltaObject focusOdo; + private LensContext lensContext; + private String channel; + private ObjectResolver objectResolver; + private SystemObjectCache systemObjectCache; + private RelationRegistry relationRegistry; + private PrismContext prismContext; + private MappingFactory mappingFactory; + private ActivationComputer activationComputer; + private XMLGregorianCalendar now; + private boolean loginMode = false; + private PrismObject systemConfiguration; + private MappingEvaluator mappingEvaluator; + + public Builder() { + } + + public Builder repository(RepositoryService val) { + repository = val; + return this; + } + + public Builder focusOdo(ObjectDeltaObject val) { + focusOdo = val; + return this; + } + + public Builder lensContext(LensContext val) { + lensContext = val; + return this; + } + + public Builder channel(String val) { + channel = val; + return this; + } + + public Builder objectResolver(ObjectResolver val) { + objectResolver = val; + return this; + } + + public Builder systemObjectCache(SystemObjectCache val) { + systemObjectCache = val; + return this; + } + + public Builder relationRegistry(RelationRegistry val) { + relationRegistry = val; + return this; + } + + public Builder prismContext(PrismContext val) { + prismContext = val; + return this; + } + + public Builder mappingFactory(MappingFactory val) { + mappingFactory = val; + return this; + } + + public Builder activationComputer(ActivationComputer val) { + activationComputer = val; + return this; + } + + public Builder now(XMLGregorianCalendar val) { + now = val; + return this; + } + + public Builder loginMode(boolean val) { + loginMode = val; + return this; + } + + public Builder systemConfiguration(PrismObject val) { + systemConfiguration = val; + return this; + } + + public Builder mappingEvaluator(MappingEvaluator val) { + mappingEvaluator = val; + return this; + } + + public AssignmentEvaluator build() { + return new AssignmentEvaluator<>(this); + } + } + + private static class MemberOfInvocation { + private final String targetOid; + private boolean result; + + private MemberOfInvocation(String targetOid, boolean result) { + this.targetOid = targetOid; + this.result = result; + } + + @Override + public String toString() { + return "MemberOfInvocation{" + + "targetOid='" + targetOid + '\'' + + ", result=" + result + + '}'; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java index 3c0a0a1daa3..048a7f55412 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java @@ -57,20 +57,7 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionStrengthType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceAttributeDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectAssociationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ReferentialIntegrityType; import org.jetbrains.annotations.NotNull; @@ -613,6 +600,7 @@ private > MappingImpl ev } builder = builder.mappingQName(mappingQName) + .mappingKind(MappingKindType.CONSTRUCTION) .sourceContext(getFocusOdo()) .defaultTargetDefinition(outputDefinition) .originType(getOriginType()) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index cebb9b5f278..1756f97919d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -1,943 +1,937 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens.projector; - -import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.api.context.SynchronizationIntent; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.PropertyDelta; -import com.evolveum.midpoint.prism.path.UniformItemPath; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.schema.CapabilityUtil; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceActivationDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectLifecycleDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationLockoutStatusCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationStatusCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationValidityCapabilityType; - -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * The processor that takes care of user activation mapping to an account (outbound direction). - * - * @author Radovan Semancik - */ -@Component -public class ActivationProcessor { - - private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); - - private static final QName SHADOW_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "shadowExists"); - private static final QName LEGAL_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "legal"); - private static final QName ASSIGNED_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "assigned"); - private static final QName FOCUS_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "focusExists"); - - @Autowired private ContextLoader contextLoader; - @Autowired private PrismContext prismContext; - @Autowired private MappingEvaluator mappingEvaluator; - @Autowired private MidpointFunctions midpointFunctions; - - private PrismObjectDefinition userDefinition; - private PrismContainerDefinition activationDefinition; - - public void processActivation(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - return; - } - - processActivationFocal((LensContext)context, projectionContext, now, task, result); - } - - private void processActivationFocal(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - processActivationMetadata(context, projectionContext, now, result); - return; - } - try { - - processActivationUserCurrent(context, projectionContext, now, task, result); - processActivationMetadata(context, projectionContext, now, result); - processActivationUserFuture(context, projectionContext, now, task, result); - - } catch (ObjectNotFoundException e) { - if (projectionContext.isTombstone()) { - // This is not critical. The projection is marked as thombstone and we can go on with processing - // No extra action is needed. - } else { - throw e; - } - } - } - - public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - String projCtxDesc = projCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); - - if (decision == SynchronizationPolicyDecision.BROKEN) { - LOGGER.trace("Broken projection {}, skipping further activation processing", projCtxDesc); - return; - } - if (decision != null) { - throw new IllegalStateException("Decision "+decision+" already present for projection "+projCtxDesc); - } - - if (synchronizationIntent == SynchronizationIntent.UNLINK) { - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); - LOGGER.trace("Evaluated decision for {} to {} because of unlink synchronization intent, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.UNLINK); - return; - } - - if (projCtx.isTombstone()) { - if (projCtx.isDelete() && ModelExecuteOptions.isForce(context.getOptions())) { - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); - LOGGER.trace("Evaluated decision for tombstone {} to {} (force), skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); - return; - } else { - // Let's keep thombstones linked until they expire. So we do not have shadows without owners. - // This is also needed for async delete operations. - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.KEEP); - LOGGER.trace("Evaluated decision for {} to {} because it is tombstone, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.KEEP); - return; - } - } - - if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { - // TODO: is this OK? - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); - LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); - return; - } - - LOGGER.trace("Evaluating intended existence of projection {} (legal={})", projCtxDesc, projCtx.isLegal()); - - boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, MappingTimeEval.CURRENT, task, result); - - LOGGER.trace("Evaluated intended existence of projection {} to {} (legal={})", projCtxDesc, shadowShouldExist, projCtx.isLegal()); - - // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context - - LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); - - if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - if (lowerOrderContext != null && lowerOrderContext.isDelete()) { - // HACK HACK HACK - decision = SynchronizationPolicyDecision.DELETE; - } else { - decision = SynchronizationPolicyDecision.KEEP; - } - } else { - if (lowerOrderContext != null) { - if (lowerOrderContext.isDelete()) { - // HACK HACK HACK - decision = SynchronizationPolicyDecision.DELETE; - } else { - // If there is a lower-order context then that one will be ADD - // and this one is KEEP. When the execution comes to this context - // then the projection already exists - decision = SynchronizationPolicyDecision.KEEP; - } - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } - } else { - // Delete - if (projCtx.isExists()) { - decision = SynchronizationPolicyDecision.DELETE; - } else { - // we should delete the entire context, but then we will lost track of what - // happened. So just ignore it. - decision = SynchronizationPolicyDecision.IGNORE; - // if there are any triggers then move them to focus. We may still need them. - LensUtil.moveTriggers(projCtx, context.getFocusContext()); - } - } - - } else if (synchronizationIntent == SynchronizationIntent.ADD) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - // Attempt to add something that is already there, but should be OK - decision = SynchronizationPolicyDecision.KEEP; - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } else { - throw new PolicyViolationException("Request to add projection "+projCtxDesc+" but the activation policy decided that it should not exist"); - } - - } else if (synchronizationIntent == SynchronizationIntent.KEEP) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - decision = SynchronizationPolicyDecision.KEEP; - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } else { - throw new PolicyViolationException("Request to keep projection "+projCtxDesc+" but the activation policy decided that it should not exist"); - } - - } else { - throw new IllegalStateException("Unknown sync intent "+synchronizationIntent); - } - - LOGGER.trace("Evaluated decision for projection {} to {}", projCtxDesc, decision); - - projCtx.setSynchronizationPolicyDecision(decision); - - PrismObject focusNew = context.getFocusContext().getObjectNew(); - if (focusNew == null) { - // This must be a user delete or something similar. No point in proceeding - LOGGER.trace("focusNew is null, skipping activation processing of {}", projCtxDesc); - return; - } - - if (decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { - LOGGER.trace("Decision is {}, skipping activation properties processing for {}", decision, projCtxDesc); - return; - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + projCtxDesc); - return; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - LOGGER.trace("No activation definition in projection {}, skipping activation properties processing", projCtxDesc); - return; - } - - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); - if (capActivation == null) { - LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); - return; - } - - ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); - - if (capStatus != null) { - evaluateActivationMapping(context, projCtx, - activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); - } - - ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); - if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); - } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); - } else { - evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), - SchemaConstants.PATH_ACTIVATION_VALID_FROM, - SchemaConstants.PATH_ACTIVATION_VALID_FROM, - null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); - if (validToMappingType == null || validToMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); - } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); - } else { - evaluateActivationMapping(context, projCtx, activationType.getValidTo(), - SchemaConstants.PATH_ACTIVATION_VALID_TO, - SchemaConstants.PATH_ACTIVATION_VALID_TO, - null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_TO.getLocalPart(), task, result); - } - - if (capLockoutStatus != null) { - evaluateActivationMapping(context, projCtx, - activationType.getLockoutStatus(), - SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, - capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); - } - - } - - public void processActivationMetadata(LensContext context, LensProjectionContext accCtx, - XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - ObjectDelta projDelta = accCtx.getDelta(); - if (projDelta == null) { - return; - } - - PropertyDelta statusDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS); - - if (statusDelta != null && !statusDelta.isDelete()) { - - // we have to determine if the status really changed - PrismObject oldShadow = accCtx.getObjectOld(); - ActivationStatusType statusOld = null; - if (oldShadow != null && oldShadow.asObjectable().getActivation() != null) { - statusOld = oldShadow.asObjectable().getActivation().getAdministrativeStatus(); - } - - PrismProperty statusPropNew = (PrismProperty) statusDelta.getItemNewMatchingPath(null); - ActivationStatusType statusNew = statusPropNew.getRealValue(); - - if (statusNew == statusOld) { - LOGGER.trace("Administrative status not changed ({}), timestamp and/or reason will not be recorded", statusNew); - } else { - // timestamps - PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(statusNew, - now, getActivationDefinition(), OriginType.OUTBOUND, prismContext); - accCtx.swallowToSecondaryDelta(timestampDelta); - - // disableReason - if (statusNew == ActivationStatusType.DISABLED) { - PropertyDelta disableReasonDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_DISABLE_REASON); - if (disableReasonDelta == null) { - String disableReason = null; - ObjectDelta projPrimaryDelta = accCtx.getPrimaryDelta(); - ObjectDelta projSecondaryDelta = accCtx.getSecondaryDelta(); - if (projPrimaryDelta != null - && projPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) != null - && (projSecondaryDelta == null || projSecondaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) == null)) { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_EXPLICIT; - } else if (accCtx.isLegal()) { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_MAPPED; - } else { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_DEPROVISION; - } - - PrismPropertyDefinition disableReasonDef = activationDefinition.findPropertyDefinition(ActivationType.F_DISABLE_REASON); - disableReasonDelta = disableReasonDef.createEmptyDelta( - ItemPath.create(FocusType.F_ACTIVATION, ActivationType.F_DISABLE_REASON)); - disableReasonDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(disableReason, OriginType.OUTBOUND, null)); - accCtx.swallowToSecondaryDelta(disableReasonDelta); - } - } - } - } - - } - - public void processActivationUserFuture(LensContext context, LensProjectionContext accCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - String accCtxDesc = accCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); - - if (accCtx.isTombstone() || decision == SynchronizationPolicyDecision.BROKEN - || decision == SynchronizationPolicyDecision.IGNORE - || decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { - return; - } - - accCtx.recompute(); - - evaluateExistenceMapping(context, accCtx, now, MappingTimeEval.FUTURE, task, result); - - PrismObject focusNew = context.getFocusContext().getObjectNew(); - if (focusNew == null) { - // This must be a user delete or something similar. No point in proceeding - LOGGER.trace("focusNew is null, skipping activation processing of {}", accCtxDesc); - return; - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - return; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - return; - } - - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); - if (capActivation == null) { - return; - } - - ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - - if (capStatus != null) { - - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - capActivation, now, MappingTimeEval.FUTURE, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); - } - - if (capValidFrom != null) { - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, - null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - if (capValidTo != null) { - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, - null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - } - - - private boolean evaluateExistenceMapping(final LensContext context, - final LensProjectionContext projCtx, final XMLGregorianCalendar now, final MappingTimeEval current, - Task task, final OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - final String projCtxDesc = projCtx.toHumanReadableString(); - - final Boolean legal = projCtx.isLegal(); - if (legal == null) { - throw new IllegalStateException("Null 'legal' for "+projCtxDesc); - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - return legal; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - return legal; - } - ResourceBidirectionalMappingType existenceType = activationType.getExistence(); - if (existenceType == null) { - return legal; - } - List outbound = existenceType.getOutbound(); - if (outbound == null || outbound.isEmpty()) { - // "default mapping" - return legal; - } - - MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); - params.setMappingTypes(outbound); - params.setMappingDesc("outbound existence mapping in projection " + projCtxDesc); - params.setNow(now); - params.setAPrioriTargetObject(projCtx.getObjectOld()); - params.setEvaluateCurrent(current); - params.setTargetContext(projCtx); - params.setFixTarget(true); - params.setContext(context); - - params.setInitializer(builder -> { - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource - = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.defaultSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); - - // Variable: focus - builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); - - // Variable: user (for convenience, same as "focus"), DEPRECATED - builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); - builder.addAliasRegistration(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); - - // Variable: projection - // This may be tricky when creation of a new projection is considered. - // In that case we do not have any projection object (account) yet, neither new nor old. But we already have - // projection context. We have to pass projection definition explicitly here. - builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - builder.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - builder.addAliasRegistration(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); - - // Variable: resource - builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), ResourceType.class); - - builder.originType(OriginType.OUTBOUND); - builder.originObject(projCtx.getResource()); - return builder; - }); - - PrismValueDeltaSetTriple> aggregatedOutputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); - - params.setProcessor((mappingOutputPath, outputStruct) -> { - // This is a very primitive implementation of output processing. - // Maybe we should somehow use the default processing in MappingEvaluator, but it's quite complex - // and therefore we should perhaps wait for general mapping cleanup (MID-3847). - PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); - if (outputTriple != null) { - aggregatedOutputTriple.merge(outputTriple); - } - return false; - }); - - MutablePrismPropertyDefinition shadowExistenceTargetDef = prismContext.definitionFactory().createPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); - shadowExistenceTargetDef.setMinOccurs(1); - shadowExistenceTargetDef.setMaxOccurs(1); - params.setTargetItemDefinition(shadowExistenceTargetDef); - mappingEvaluator.evaluateMappingSetProjection(params, task, result); - - boolean output; - if (aggregatedOutputTriple.isEmpty()) { - output = legal; // the default - } else { - Collection> nonNegativeValues = aggregatedOutputTriple.getNonNegativeValues(); - if (nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Activation existence expression resulted in no values for projection " + projCtxDesc); - } else if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + projCtxDesc + ": " + nonNegativeValues); - } else { - PrismPropertyValue value = nonNegativeValues.iterator().next(); - if (value != null && value.getRealValue() != null) { - output = value.getRealValue(); - } else { - // TODO could this even occur? - throw new ExpressionEvaluationException("Activation existence expression resulted in null value for projection " + projCtxDesc); - } - } - } - - return output; - } - - private void evaluateActivationMapping(final LensContext context, - final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, - final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, - final ActivationCapabilityType capActivation, XMLGregorianCalendar now, final MappingTimeEval current, - String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - MappingInitializer,PrismPropertyDefinition> initializer = - builder -> { - // Source: administrativeStatus, validFrom or validTo - ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - - if (capActivation != null && focusPropertyPath.equivalent(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - - // Source: computedShadowStatus - ItemDeltaItem,PrismPropertyDefinition> computedIdi; - if (capValidFrom != null && capValidTo != null) { - // "Native" validFrom and validTo, directly use administrativeStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - - } else { - // Simulate validFrom and validTo using effectiveStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); - - } - - Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); - - builder.defaultSource(computedSource); - - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); - builder.addSource(source); - - } else { - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(source); - } - - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource = new Source<>(legalIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.addSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); - - return builder; - }; - - evaluateOutboundMapping(context, projCtx, bidirectionalMappingType, focusPropertyPath, projectionPropertyPath, initializer, - now, current, desc + " outbound activation mapping", task, result); - - } - - private void evaluateOutboundMapping(final LensContext context, - final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, - final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, - final MappingInitializer,PrismPropertyDefinition> initializer, - XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (bidirectionalMappingType == null) { - LOGGER.trace("No '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); - return; - } - List outboundMappingTypes = bidirectionalMappingType.getOutbound(); - if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { - LOGGER.trace("No outbound definition in '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); - return; - } - - String projCtxDesc = projCtx.toHumanReadableString(); - PrismObject shadowNew = projCtx.getObjectNew(); - - MappingInitializer,PrismPropertyDefinition> internalInitializer = - builder -> { - - builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); - - builder.originType(OriginType.OUTBOUND); - builder.originObject(projCtx.getResource()); - - initializer.initialize(builder); - - return builder; - }; - - MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); - params.setMappingTypes(outboundMappingTypes); - params.setMappingDesc(desc + " in projection " + projCtxDesc); - params.setNow(now); - params.setInitializer(internalInitializer); - params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); - params.setAPrioriTargetObject(shadowNew); - params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); - if (context.getFocusContext() != null) { - params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); - } - params.setTargetContext(projCtx); - params.setDefaultTargetItemPath(projectionPropertyPath); - params.setEvaluateCurrent(evaluateCurrent); - params.setEvaluateWeak(true); - params.setContext(context); - params.setHasFullTargetObject(projCtx.hasFullShadow()); - - Map>> outputTripleMap = mappingEvaluator.evaluateMappingSetProjection(params, task, result); - - LOGGER.trace("Mapping processing output after {} ({}):\n{}", desc, evaluateCurrent, - DebugUtil.debugDumpLazily(outputTripleMap, 1)); - - if (projCtx.isDoReconciliation()) { - reconcileOutboundValue(context, projCtx, outputTripleMap, desc); - } - - } - - /** - * TODO: can we align this with ReconciliationProcessor? - */ - private void reconcileOutboundValue(LensContext context, LensProjectionContext projCtx, - Map>> outputTripleMap, String desc) throws SchemaException { - - // TODO: check for full shadow? - - for (Entry>> entry: outputTripleMap.entrySet()) { - UniformItemPath mappingOutputPath = entry.getKey(); - MappingOutputStruct> mappingOutputStruct = entry.getValue(); - if (mappingOutputStruct.isWeakMappingWasUsed()) { - // Thing to do. All deltas should already be in context - LOGGER.trace("Skip reconciliation of {} in {} because of weak", mappingOutputPath, desc); - continue; - } - if (!mappingOutputStruct.isStrongMappingWasUsed()) { - // Normal mappings are not processed for reconciliation - LOGGER.trace("Skip reconciliation of {} in {} because not strong", mappingOutputPath, desc); - continue; - } - LOGGER.trace("reconciliation of {} for {}", mappingOutputPath, desc); - - PrismObjectDefinition targetObjectDefinition = projCtx.getObjectDefinition(); - PrismPropertyDefinition targetItemDefinition = targetObjectDefinition.findPropertyDefinition(mappingOutputPath); - if (targetItemDefinition == null) { - throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); - } - PropertyDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); - - PrismValueDeltaSetTriple> outputTriple = mappingOutputStruct.getOutputTriple(); - - PrismProperty currentTargetItem = null; - PrismObject shadowCurrent = projCtx.getObjectCurrent(); - if (shadowCurrent != null) { - currentTargetItem = shadowCurrent.findProperty(mappingOutputPath); - } - Collection> hasValues = new ArrayList<>(); - if (currentTargetItem != null) { - hasValues.addAll(currentTargetItem.getValues()); - } - - Collection> shouldHaveValues = outputTriple.getNonNegativeValues(); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Reconciliation of {}:\n hasValues:\n{}\n shouldHaveValues\n{}", - mappingOutputPath, DebugUtil.debugDump(hasValues, 2), DebugUtil.debugDump(shouldHaveValues, 2)); - } - - for (PrismPropertyValue shouldHaveValue: shouldHaveValues) { - if (!PrismValueCollectionsUtil.containsRealValue(hasValues, shouldHaveValue)) { - if (targetItemDefinition.isSingleValue()) { - targetItemDelta.setValueToReplace(shouldHaveValue.clone()); - } else { - targetItemDelta.addValueToAdd(shouldHaveValue.clone()); - } - } - } - - if (targetItemDefinition.isSingleValue()) { - if (!targetItemDelta.isReplace() && shouldHaveValues.isEmpty()) { - targetItemDelta.setValueToReplace(); - } - } else { - for (PrismPropertyValue hasValue: hasValues) { - if (!PrismValueCollectionsUtil.containsRealValue(shouldHaveValues, hasValue)) { - targetItemDelta.addValueToDelete(hasValue.clone()); - } - } - } - - if (!targetItemDelta.isEmpty()) { - LOGGER.trace("Reconciliation delta:\n{}", targetItemDelta.debugDumpLazily(1)); - projCtx.swallowToSecondaryDelta(targetItemDelta); - } - } - - } - - - - private ItemDeltaItem,PrismPropertyDefinition> getLegalIdi(LensProjectionContext accCtx) throws SchemaException { - Boolean legal = accCtx.isLegal(); - Boolean legalOld = accCtx.isLegalOld(); - return createBooleanIdi(LEGAL_PROPERTY_NAME, legalOld, legal); - } - - @NotNull - private ItemDeltaItem, PrismPropertyDefinition> createBooleanIdi( - QName propertyName, Boolean old, Boolean current) throws SchemaException { - MutablePrismPropertyDefinition definition = prismContext.definitionFactory().createPropertyDefinition(propertyName, DOMUtil.XSD_BOOLEAN); - definition.setMinOccurs(1); - definition.setMaxOccurs(1); - PrismProperty property = definition.instantiate(); - property.add(prismContext.itemFactory().createPropertyValue(current)); - - if (current == old) { - return new ItemDeltaItem<>(property); - } else { - PrismProperty propertyOld = property.clone(); - propertyOld.setRealValue(old); - PropertyDelta delta = propertyOld.createDelta(); - delta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(current)); - return new ItemDeltaItem<>(propertyOld, delta, property, definition); - } - } - - private ItemDeltaItem,PrismPropertyDefinition> getAssignedIdi(LensProjectionContext accCtx) throws SchemaException { - Boolean assigned = accCtx.isAssigned(); - Boolean assignedOld = accCtx.isAssignedOld(); - return createBooleanIdi(ASSIGNED_PROPERTY_NAME, assignedOld, assigned); - } - - private ItemDeltaItem,PrismPropertyDefinition> getFocusExistsIdi( - LensFocusContext lensFocusContext) throws SchemaException { - Boolean existsOld = null; - Boolean existsNew = null; - - if (lensFocusContext != null) { - if (lensFocusContext.isDelete()) { - existsOld = true; - existsNew = false; - } else if (lensFocusContext.isAdd()) { - existsOld = false; - existsNew = true; - } else { - existsOld = true; - existsNew = true; - } - } - - MutablePrismPropertyDefinition existsDef = prismContext.definitionFactory().createPropertyDefinition(FOCUS_EXISTS_PROPERTY_NAME, - DOMUtil.XSD_BOOLEAN); - existsDef.setMinOccurs(1); - existsDef.setMaxOccurs(1); - PrismProperty existsProp = existsDef.instantiate(); - - existsProp.add(prismContext.itemFactory().createPropertyValue(existsNew)); - - if (existsOld == existsNew) { - return new ItemDeltaItem<>(existsProp); - } else { - PrismProperty existsPropOld = existsProp.clone(); - existsPropOld.setRealValue(existsOld); - PropertyDelta existsDelta = existsPropOld.createDelta(); - existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); - return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); - } - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void processLifecycle(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); - return; - } - - processLifecycleFocus((LensContext)context, projCtx, now, task, result); - } - - private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); - return; - } - - ResourceObjectLifecycleDefinitionType lifecycleDef = null; - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType != null) { - lifecycleDef = resourceAccountDefType.getLifecycle(); - } - ResourceBidirectionalMappingType lifecycleStateMappingType = null; - if (lifecycleDef != null) { - lifecycleStateMappingType = lifecycleDef.getLifecycleState(); - } - - if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { - - if (!projCtx.isAdd()) { - LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); - return; - } - - PrismObject focusNew = focusContext.getObjectNew(); - if (focusNew == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); - return; - } - - PrismObject projectionNew = projCtx.getObjectNew(); - if (projectionNew == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no new projection (default expression)"); - return; - } - - String lifecycle = midpointFunctions.computeProjectionLifecycle( - focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); - - LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); - - if (lifecycle != null) { - PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); - PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); - PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); - pval.setOriginType(OriginType.OUTBOUND); - lifeCycleDelta.setValuesToReplace(pval); - projCtx.swallowToSecondaryDelta(lifeCycleDelta); - } - - } else { - - LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); - evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, - SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, - null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); - } - - } - - private PrismObjectDefinition getUserDefinition() { - if (userDefinition == null) { - userDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByCompileTimeClass(UserType.class); - } - return userDefinition; - } - - private PrismContainerDefinition getActivationDefinition() { - if (activationDefinition == null) { - PrismObjectDefinition userDefinition = getUserDefinition(); - activationDefinition = userDefinition.findContainerDefinition(UserType.F_ACTIVATION); - } - return activationDefinition; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.projector; + +import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.schema.CapabilityUtil; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.PolicyViolationException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationLockoutStatusCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationStatusCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationValidityCapabilityType; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * The processor that takes care of user activation mapping to an account (outbound direction). + * + * @author Radovan Semancik + */ +@Component +public class ActivationProcessor { + + private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); + + private static final QName SHADOW_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "shadowExists"); + private static final QName LEGAL_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "legal"); + private static final QName ASSIGNED_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "assigned"); + private static final QName FOCUS_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "focusExists"); + + @Autowired private ContextLoader contextLoader; + @Autowired private PrismContext prismContext; + @Autowired private MappingEvaluator mappingEvaluator; + @Autowired private MidpointFunctions midpointFunctions; + + private PrismObjectDefinition userDefinition; + private PrismContainerDefinition activationDefinition; + + public void processActivation(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + return; + } + + processActivationFocal((LensContext)context, projectionContext, now, task, result); + } + + private void processActivationFocal(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + processActivationMetadata(context, projectionContext, now, result); + return; + } + try { + + processActivationUserCurrent(context, projectionContext, now, task, result); + processActivationMetadata(context, projectionContext, now, result); + processActivationUserFuture(context, projectionContext, now, task, result); + + } catch (ObjectNotFoundException e) { + if (projectionContext.isTombstone()) { + // This is not critical. The projection is marked as thombstone and we can go on with processing + // No extra action is needed. + } else { + throw e; + } + } + } + + public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + String projCtxDesc = projCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); + + if (decision == SynchronizationPolicyDecision.BROKEN) { + LOGGER.trace("Broken projection {}, skipping further activation processing", projCtxDesc); + return; + } + if (decision != null) { + throw new IllegalStateException("Decision "+decision+" already present for projection "+projCtxDesc); + } + + if (synchronizationIntent == SynchronizationIntent.UNLINK) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); + LOGGER.trace("Evaluated decision for {} to {} because of unlink synchronization intent, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.UNLINK); + return; + } + + if (projCtx.isTombstone()) { + if (projCtx.isDelete() && ModelExecuteOptions.isForce(context.getOptions())) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + LOGGER.trace("Evaluated decision for tombstone {} to {} (force), skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); + return; + } else { + // Let's keep thombstones linked until they expire. So we do not have shadows without owners. + // This is also needed for async delete operations. + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.KEEP); + LOGGER.trace("Evaluated decision for {} to {} because it is tombstone, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.KEEP); + return; + } + } + + if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { + // TODO: is this OK? + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); + return; + } + + LOGGER.trace("Evaluating intended existence of projection {} (legal={})", projCtxDesc, projCtx.isLegal()); + + boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, MappingTimeEval.CURRENT, task, result); + + LOGGER.trace("Evaluated intended existence of projection {} to {} (legal={})", projCtxDesc, shadowShouldExist, projCtx.isLegal()); + + // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context + + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); + + if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + if (lowerOrderContext != null && lowerOrderContext.isDelete()) { + // HACK HACK HACK + decision = SynchronizationPolicyDecision.DELETE; + } else { + decision = SynchronizationPolicyDecision.KEEP; + } + } else { + if (lowerOrderContext != null) { + if (lowerOrderContext.isDelete()) { + // HACK HACK HACK + decision = SynchronizationPolicyDecision.DELETE; + } else { + // If there is a lower-order context then that one will be ADD + // and this one is KEEP. When the execution comes to this context + // then the projection already exists + decision = SynchronizationPolicyDecision.KEEP; + } + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } + } else { + // Delete + if (projCtx.isExists()) { + decision = SynchronizationPolicyDecision.DELETE; + } else { + // we should delete the entire context, but then we will lost track of what + // happened. So just ignore it. + decision = SynchronizationPolicyDecision.IGNORE; + // if there are any triggers then move them to focus. We may still need them. + LensUtil.moveTriggers(projCtx, context.getFocusContext()); + } + } + + } else if (synchronizationIntent == SynchronizationIntent.ADD) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + // Attempt to add something that is already there, but should be OK + decision = SynchronizationPolicyDecision.KEEP; + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } else { + throw new PolicyViolationException("Request to add projection "+projCtxDesc+" but the activation policy decided that it should not exist"); + } + + } else if (synchronizationIntent == SynchronizationIntent.KEEP) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + decision = SynchronizationPolicyDecision.KEEP; + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } else { + throw new PolicyViolationException("Request to keep projection "+projCtxDesc+" but the activation policy decided that it should not exist"); + } + + } else { + throw new IllegalStateException("Unknown sync intent "+synchronizationIntent); + } + + LOGGER.trace("Evaluated decision for projection {} to {}", projCtxDesc, decision); + + projCtx.setSynchronizationPolicyDecision(decision); + + PrismObject focusNew = context.getFocusContext().getObjectNew(); + if (focusNew == null) { + // This must be a user delete or something similar. No point in proceeding + LOGGER.trace("focusNew is null, skipping activation processing of {}", projCtxDesc); + return; + } + + if (decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { + LOGGER.trace("Decision is {}, skipping activation properties processing for {}", decision, projCtxDesc); + return; + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + projCtxDesc); + return; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + LOGGER.trace("No activation definition in projection {}, skipping activation properties processing", projCtxDesc); + return; + } + + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); + if (capActivation == null) { + LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); + return; + } + + ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); + + if (capStatus != null) { + evaluateActivationMapping(context, projCtx, + activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); + } else { + LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); + } + + ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); + if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); + } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); + } else { + evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), + SchemaConstants.PATH_ACTIVATION_VALID_FROM, + SchemaConstants.PATH_ACTIVATION_VALID_FROM, + null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); + if (validToMappingType == null || validToMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); + } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); + } else { + evaluateActivationMapping(context, projCtx, activationType.getValidTo(), + SchemaConstants.PATH_ACTIVATION_VALID_TO, + SchemaConstants.PATH_ACTIVATION_VALID_TO, + null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_TO.getLocalPart(), task, result); + } + + if (capLockoutStatus != null) { + evaluateActivationMapping(context, projCtx, + activationType.getLockoutStatus(), + SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, + capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); + } else { + LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); + } + + } + + public void processActivationMetadata(LensContext context, LensProjectionContext accCtx, + XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + ObjectDelta projDelta = accCtx.getDelta(); + if (projDelta == null) { + return; + } + + PropertyDelta statusDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS); + + if (statusDelta != null && !statusDelta.isDelete()) { + + // we have to determine if the status really changed + PrismObject oldShadow = accCtx.getObjectOld(); + ActivationStatusType statusOld = null; + if (oldShadow != null && oldShadow.asObjectable().getActivation() != null) { + statusOld = oldShadow.asObjectable().getActivation().getAdministrativeStatus(); + } + + PrismProperty statusPropNew = (PrismProperty) statusDelta.getItemNewMatchingPath(null); + ActivationStatusType statusNew = statusPropNew.getRealValue(); + + if (statusNew == statusOld) { + LOGGER.trace("Administrative status not changed ({}), timestamp and/or reason will not be recorded", statusNew); + } else { + // timestamps + PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(statusNew, + now, getActivationDefinition(), OriginType.OUTBOUND, prismContext); + accCtx.swallowToSecondaryDelta(timestampDelta); + + // disableReason + if (statusNew == ActivationStatusType.DISABLED) { + PropertyDelta disableReasonDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_DISABLE_REASON); + if (disableReasonDelta == null) { + String disableReason = null; + ObjectDelta projPrimaryDelta = accCtx.getPrimaryDelta(); + ObjectDelta projSecondaryDelta = accCtx.getSecondaryDelta(); + if (projPrimaryDelta != null + && projPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) != null + && (projSecondaryDelta == null || projSecondaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) == null)) { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_EXPLICIT; + } else if (accCtx.isLegal()) { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_MAPPED; + } else { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_DEPROVISION; + } + + PrismPropertyDefinition disableReasonDef = activationDefinition.findPropertyDefinition(ActivationType.F_DISABLE_REASON); + disableReasonDelta = disableReasonDef.createEmptyDelta( + ItemPath.create(FocusType.F_ACTIVATION, ActivationType.F_DISABLE_REASON)); + disableReasonDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(disableReason, OriginType.OUTBOUND, null)); + accCtx.swallowToSecondaryDelta(disableReasonDelta); + } + } + } + } + + } + + public void processActivationUserFuture(LensContext context, LensProjectionContext accCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + String accCtxDesc = accCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); + + if (accCtx.isTombstone() || decision == SynchronizationPolicyDecision.BROKEN + || decision == SynchronizationPolicyDecision.IGNORE + || decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { + return; + } + + accCtx.recompute(); + + evaluateExistenceMapping(context, accCtx, now, MappingTimeEval.FUTURE, task, result); + + PrismObject focusNew = context.getFocusContext().getObjectNew(); + if (focusNew == null) { + // This must be a user delete or something similar. No point in proceeding + LOGGER.trace("focusNew is null, skipping activation processing of {}", accCtxDesc); + return; + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + return; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + return; + } + + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); + if (capActivation == null) { + return; + } + + ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + + if (capStatus != null) { + + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + capActivation, now, MappingTimeEval.FUTURE, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); + } + + if (capValidFrom != null) { + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, + null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + if (capValidTo != null) { + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, + null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + } + + + private boolean evaluateExistenceMapping(final LensContext context, + final LensProjectionContext projCtx, final XMLGregorianCalendar now, final MappingTimeEval current, + Task task, final OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + final String projCtxDesc = projCtx.toHumanReadableString(); + + final Boolean legal = projCtx.isLegal(); + if (legal == null) { + throw new IllegalStateException("Null 'legal' for "+projCtxDesc); + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + return legal; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + return legal; + } + ResourceBidirectionalMappingType existenceType = activationType.getExistence(); + if (existenceType == null) { + return legal; + } + List outbound = existenceType.getOutbound(); + if (outbound == null || outbound.isEmpty()) { + // "default mapping" + return legal; + } + + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); + params.setMappingTypes(outbound); + params.setMappingDesc("outbound existence mapping in projection " + projCtxDesc); + params.setNow(now); + params.setAPrioriTargetObject(projCtx.getObjectOld()); + params.setEvaluateCurrent(current); + params.setTargetContext(projCtx); + params.setFixTarget(true); + params.setContext(context); + + params.setInitializer(builder -> { + builder.mappingKind(MappingKindType.OUTBOUND); + + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); + Source,PrismPropertyDefinition> legalSource + = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL_QNAME); + builder.defaultSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); + builder.addSource(focusExistsSource); + + // Variable: focus + builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); + + // Variable: user (for convenience, same as "focus"), DEPRECATED + builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); + builder.addAliasRegistration(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); + + // Variable: projection + // This may be tricky when creation of a new projection is considered. + // In that case we do not have any projection object (account) yet, neither new nor old. But we already have + // projection context. We have to pass projection definition explicitly here. + builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + builder.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + builder.addAliasRegistration(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); + + // Variable: resource + builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), ResourceType.class); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(projCtx.getResource()); + return builder; + }); + + PrismValueDeltaSetTriple> aggregatedOutputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); + + params.setProcessor((mappingOutputPath, outputStruct) -> { + // This is a very primitive implementation of output processing. + // Maybe we should somehow use the default processing in MappingEvaluator, but it's quite complex + // and therefore we should perhaps wait for general mapping cleanup (MID-3847). + PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); + if (outputTriple != null) { + aggregatedOutputTriple.merge(outputTriple); + } + return false; + }); + + MutablePrismPropertyDefinition shadowExistenceTargetDef = prismContext.definitionFactory().createPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); + shadowExistenceTargetDef.setMinOccurs(1); + shadowExistenceTargetDef.setMaxOccurs(1); + params.setTargetItemDefinition(shadowExistenceTargetDef); + mappingEvaluator.evaluateMappingSetProjection(params, task, result); + + boolean output; + if (aggregatedOutputTriple.isEmpty()) { + output = legal; // the default + } else { + Collection> nonNegativeValues = aggregatedOutputTriple.getNonNegativeValues(); + if (nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Activation existence expression resulted in no values for projection " + projCtxDesc); + } else if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + projCtxDesc + ": " + nonNegativeValues); + } else { + PrismPropertyValue value = nonNegativeValues.iterator().next(); + if (value != null && value.getRealValue() != null) { + output = value.getRealValue(); + } else { + // TODO could this even occur? + throw new ExpressionEvaluationException("Activation existence expression resulted in null value for projection " + projCtxDesc); + } + } + } + + return output; + } + + private void evaluateActivationMapping(final LensContext context, + final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, + final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, + final ActivationCapabilityType capActivation, XMLGregorianCalendar now, final MappingTimeEval current, + String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + MappingInitializer,PrismPropertyDefinition> initializer = + builder -> { + builder.mappingKind(MappingKindType.OUTBOUND); + + // Source: administrativeStatus, validFrom or validTo + ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); + + if (capActivation != null && focusPropertyPath.equivalent(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + + // Source: computedShadowStatus + ItemDeltaItem,PrismPropertyDefinition> computedIdi; + if (capValidFrom != null && capValidTo != null) { + // "Native" validFrom and validTo, directly use administrativeStatus + computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); + + } else { + // Simulate validFrom and validTo using effectiveStatus + computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); + + } + + Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); + + builder.defaultSource(computedSource); + + Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); + builder.addSource(source); + + } else { + Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); + builder.defaultSource(source); + } + + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalIdi = getLegalIdi(projCtx); + Source,PrismPropertyDefinition> legalSource = new Source<>(legalIdi, ExpressionConstants.VAR_LEGAL_QNAME); + builder.addSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); + builder.addSource(focusExistsSource); + + return builder; + }; + + evaluateOutboundMapping(context, projCtx, bidirectionalMappingType, focusPropertyPath, projectionPropertyPath, initializer, + now, current, desc + " outbound activation mapping", task, result); + + } + + private void evaluateOutboundMapping(final LensContext context, + final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, + final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, + final MappingInitializer,PrismPropertyDefinition> initializer, + XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (bidirectionalMappingType == null) { + LOGGER.trace("No '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); + return; + } + List outboundMappingTypes = bidirectionalMappingType.getOutbound(); + if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { + LOGGER.trace("No outbound definition in '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); + return; + } + + String projCtxDesc = projCtx.toHumanReadableString(); + PrismObject shadowNew = projCtx.getObjectNew(); + + MappingInitializer,PrismPropertyDefinition> internalInitializer = + builder -> { + + builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); + + builder.originType(OriginType.OUTBOUND); + builder.mappingKind(MappingKindType.OUTBOUND); + builder.originObject(projCtx.getResource()); + + initializer.initialize(builder); + + return builder; + }; + + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); + params.setMappingTypes(outboundMappingTypes); + params.setMappingDesc(desc + " in projection " + projCtxDesc); + params.setNow(now); + params.setInitializer(internalInitializer); + params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); + params.setAPrioriTargetObject(shadowNew); + params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); + if (context.getFocusContext() != null) { + params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); + } + params.setTargetContext(projCtx); + params.setDefaultTargetItemPath(projectionPropertyPath); + params.setEvaluateCurrent(evaluateCurrent); + params.setEvaluateWeak(true); + params.setContext(context); + params.setHasFullTargetObject(projCtx.hasFullShadow()); + + Map>> outputTripleMap = mappingEvaluator.evaluateMappingSetProjection(params, task, result); + + LOGGER.trace("Mapping processing output after {} ({}):\n{}", desc, evaluateCurrent, + DebugUtil.debugDumpLazily(outputTripleMap, 1)); + + if (projCtx.isDoReconciliation()) { + reconcileOutboundValue(context, projCtx, outputTripleMap, desc); + } + + } + + /** + * TODO: can we align this with ReconciliationProcessor? + */ + private void reconcileOutboundValue(LensContext context, LensProjectionContext projCtx, + Map>> outputTripleMap, String desc) throws SchemaException { + + // TODO: check for full shadow? + + for (Entry>> entry: outputTripleMap.entrySet()) { + UniformItemPath mappingOutputPath = entry.getKey(); + MappingOutputStruct> mappingOutputStruct = entry.getValue(); + if (mappingOutputStruct.isWeakMappingWasUsed()) { + // Thing to do. All deltas should already be in context + LOGGER.trace("Skip reconciliation of {} in {} because of weak", mappingOutputPath, desc); + continue; + } + if (!mappingOutputStruct.isStrongMappingWasUsed()) { + // Normal mappings are not processed for reconciliation + LOGGER.trace("Skip reconciliation of {} in {} because not strong", mappingOutputPath, desc); + continue; + } + LOGGER.trace("reconciliation of {} for {}", mappingOutputPath, desc); + + PrismObjectDefinition targetObjectDefinition = projCtx.getObjectDefinition(); + PrismPropertyDefinition targetItemDefinition = targetObjectDefinition.findPropertyDefinition(mappingOutputPath); + if (targetItemDefinition == null) { + throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); + } + PropertyDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); + + PrismValueDeltaSetTriple> outputTriple = mappingOutputStruct.getOutputTriple(); + + PrismProperty currentTargetItem = null; + PrismObject shadowCurrent = projCtx.getObjectCurrent(); + if (shadowCurrent != null) { + currentTargetItem = shadowCurrent.findProperty(mappingOutputPath); + } + Collection> hasValues = new ArrayList<>(); + if (currentTargetItem != null) { + hasValues.addAll(currentTargetItem.getValues()); + } + + Collection> shouldHaveValues = outputTriple.getNonNegativeValues(); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Reconciliation of {}:\n hasValues:\n{}\n shouldHaveValues\n{}", + mappingOutputPath, DebugUtil.debugDump(hasValues, 2), DebugUtil.debugDump(shouldHaveValues, 2)); + } + + for (PrismPropertyValue shouldHaveValue: shouldHaveValues) { + if (!PrismValueCollectionsUtil.containsRealValue(hasValues, shouldHaveValue)) { + if (targetItemDefinition.isSingleValue()) { + targetItemDelta.setValueToReplace(shouldHaveValue.clone()); + } else { + targetItemDelta.addValueToAdd(shouldHaveValue.clone()); + } + } + } + + if (targetItemDefinition.isSingleValue()) { + if (!targetItemDelta.isReplace() && shouldHaveValues.isEmpty()) { + targetItemDelta.setValueToReplace(); + } + } else { + for (PrismPropertyValue hasValue: hasValues) { + if (!PrismValueCollectionsUtil.containsRealValue(shouldHaveValues, hasValue)) { + targetItemDelta.addValueToDelete(hasValue.clone()); + } + } + } + + if (!targetItemDelta.isEmpty()) { + LOGGER.trace("Reconciliation delta:\n{}", targetItemDelta.debugDumpLazily(1)); + projCtx.swallowToSecondaryDelta(targetItemDelta); + } + } + + } + + + + private ItemDeltaItem,PrismPropertyDefinition> getLegalIdi(LensProjectionContext accCtx) throws SchemaException { + Boolean legal = accCtx.isLegal(); + Boolean legalOld = accCtx.isLegalOld(); + return createBooleanIdi(LEGAL_PROPERTY_NAME, legalOld, legal); + } + + @NotNull + private ItemDeltaItem, PrismPropertyDefinition> createBooleanIdi( + QName propertyName, Boolean old, Boolean current) throws SchemaException { + MutablePrismPropertyDefinition definition = prismContext.definitionFactory().createPropertyDefinition(propertyName, DOMUtil.XSD_BOOLEAN); + definition.setMinOccurs(1); + definition.setMaxOccurs(1); + PrismProperty property = definition.instantiate(); + property.add(prismContext.itemFactory().createPropertyValue(current)); + + if (current == old) { + return new ItemDeltaItem<>(property); + } else { + PrismProperty propertyOld = property.clone(); + propertyOld.setRealValue(old); + PropertyDelta delta = propertyOld.createDelta(); + delta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(current)); + return new ItemDeltaItem<>(propertyOld, delta, property, definition); + } + } + + private ItemDeltaItem,PrismPropertyDefinition> getAssignedIdi(LensProjectionContext accCtx) throws SchemaException { + Boolean assigned = accCtx.isAssigned(); + Boolean assignedOld = accCtx.isAssignedOld(); + return createBooleanIdi(ASSIGNED_PROPERTY_NAME, assignedOld, assigned); + } + + private ItemDeltaItem,PrismPropertyDefinition> getFocusExistsIdi( + LensFocusContext lensFocusContext) throws SchemaException { + Boolean existsOld = null; + Boolean existsNew = null; + + if (lensFocusContext != null) { + if (lensFocusContext.isDelete()) { + existsOld = true; + existsNew = false; + } else if (lensFocusContext.isAdd()) { + existsOld = false; + existsNew = true; + } else { + existsOld = true; + existsNew = true; + } + } + + MutablePrismPropertyDefinition existsDef = prismContext.definitionFactory().createPropertyDefinition(FOCUS_EXISTS_PROPERTY_NAME, + DOMUtil.XSD_BOOLEAN); + existsDef.setMinOccurs(1); + existsDef.setMaxOccurs(1); + PrismProperty existsProp = existsDef.instantiate(); + + existsProp.add(prismContext.itemFactory().createPropertyValue(existsNew)); + + if (existsOld == existsNew) { + return new ItemDeltaItem<>(existsProp); + } else { + PrismProperty existsPropOld = existsProp.clone(); + existsPropOld.setRealValue(existsOld); + PropertyDelta existsDelta = existsPropOld.createDelta(); + existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); + return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void processLifecycle(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); + return; + } + + processLifecycleFocus((LensContext)context, projCtx, now, task, result); + } + + private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); + return; + } + + ResourceObjectLifecycleDefinitionType lifecycleDef = null; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } + ResourceBidirectionalMappingType lifecycleStateMappingType = null; + if (lifecycleDef != null) { + lifecycleStateMappingType = lifecycleDef.getLifecycleState(); + } + + if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + + if (!projCtx.isAdd()) { + LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); + return; + } + + PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); + return; + } + + PrismObject projectionNew = projCtx.getObjectNew(); + if (projectionNew == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no new projection (default expression)"); + return; + } + + String lifecycle = midpointFunctions.computeProjectionLifecycle( + focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); + + LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); + + if (lifecycle != null) { + PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); + PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); + PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); + pval.setOriginType(OriginType.OUTBOUND); + lifeCycleDelta.setValuesToReplace(pval); + projCtx.swallowToSecondaryDelta(lifeCycleDelta); + } + + } else { + + LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); + evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, + null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); + } + + } + + private PrismObjectDefinition getUserDefinition() { + if (userDefinition == null) { + userDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(UserType.class); + } + return userDefinition; + } + + private PrismContainerDefinition getActivationDefinition() { + if (activationDefinition == null) { + PrismObjectDefinition userDefinition = getUserDefinition(); + activationDefinition = userDefinition.findContainerDefinition(UserType.F_ACTIVATION); + } + return activationDefinition; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java index c0082a98705..83e193d0b9f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java @@ -20,6 +20,8 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -43,17 +45,6 @@ import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; /** * Processor that evaluates values of the outbound mappings. It does not create the deltas yet. It just collects the @@ -236,6 +227,7 @@ private Ma } mappingBuilder.rootNode(focusOdo); mappingBuilder.originType(OriginType.OUTBOUND); + mappingBuilder.mappingKind(MappingKindType.OUTBOUND); mappingBuilder.refinedObjectClassDefinition(rOcDef); if (projCtx.isDelete()) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java index 853d7f89a49..c93ee3127db 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java @@ -1,482 +1,477 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens.projector.credentials; - -import static com.evolveum.midpoint.prism.delta.ChangeType.MODIFY; - -import java.util.Collection; -import java.util.List; - -import javax.xml.datatype.XMLGregorianCalendar; - -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.schema.CapabilityUtil; -import com.evolveum.midpoint.util.LocalizableMessageBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator; -import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.OperationalDataManager; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingInitializer; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingOutputProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingTimeEval; -import com.evolveum.midpoint.prism.crypto.EncryptionException; -import com.evolveum.midpoint.prism.crypto.Protector; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.security.api.SecurityUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.exception.SystemException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; - -/** - * Processor for projection credentials. Which at this moment means just the password. - * - * @author Radovan Semancik - */ -@Component -public class ProjectionCredentialsProcessor { - - private static final Trace LOGGER = TraceManager.getTrace(ProjectionCredentialsProcessor.class); - - @Autowired private PrismContext prismContext; - @Autowired private ContextLoader contextLoader; - @Autowired private MappingFactory mappingFactory; - @Autowired private MappingEvaluator mappingEvaluator; - @Autowired private ValuePolicyProcessor valuePolicyProcessor; - @Autowired private Protector protector; - @Autowired private OperationalDataManager operationalDataManager; - @Autowired private ModelObjectResolver modelObjectResolver; - - public void processProjectionCredentials(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, - OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, - SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (projectionContext.isDelete()) { - return; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - - processProjectionCredentialsFocus((LensContext) context, projectionContext, now, task, result); - - } - } - - private void processProjectionCredentialsFocus(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, - OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, - SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - SecurityPolicyType securityPolicy = determineSecurityPolicy(context, projectionContext, now, task, result); - - processProjectionPasswordMapping(context, projectionContext, securityPolicy, now, task, result); - - validateProjectionPassword(context, projectionContext, securityPolicy, now, task, result); - - applyMetadata(context, projectionContext, now, task, result); - } - - private void processProjectionPasswordMapping(LensContext context, - final LensProjectionContext projCtx, final SecurityPolicyType securityPolicy, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - - PrismObject focusNew = focusContext.getObjectNew(); - if (focusNew == null) { - // This must be a focus delete or something similar. No point in proceeding - LOGGER.trace("focusNew is null, skipping credentials processing"); - return; - } - - PrismObjectDefinition accountDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByCompileTimeClass(ShadowType.class); - PrismPropertyDefinition projPasswordPropertyDefinition = accountDefinition - .findPropertyDefinition(SchemaConstants.PATH_PASSWORD_VALUE); - - ResourceShadowDiscriminator rsd = projCtx.getResourceShadowDiscriminator(); - - RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); - if (refinedProjDef == null) { - LOGGER.trace("No RefinedObjectClassDefinition, therefore also no password outbound definition, skipping credentials processing for projection {}", rsd); - return; - } - - List outboundMappingTypes = refinedProjDef.getPasswordOutbound(); - if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { - LOGGER.trace("No outbound password mapping for {}, skipping credentials processing", rsd); - return; - } - - // HACK - if (!projCtx.isDoReconciliation() && !projCtx.isAdd() && !isActivated(outboundMappingTypes, focusContext.getDelta())) { - LOGGER.trace("Outbound password mappings not activated for type {}, skipping credentials processing", rsd); - return; - } - - ObjectDelta projDelta = projCtx.getDelta(); - PropertyDelta projPasswordDelta; - if (projDelta != null && projDelta.getChangeType() == MODIFY) { - projPasswordDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - } else { - projPasswordDelta = null; - } - checkExistingDeltaSanity(projCtx, projPasswordDelta); - - boolean evaluateWeak = getEvaluateWeak(projCtx); - - ItemDeltaItem, PrismPropertyDefinition> focusPasswordIdi = focusContext - .getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); - - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - @Override - public void setOutputPath(ItemPath outputPath) { - } - @Override - public void setOutputDefinition(ItemDefinition outputDefinition) { - } - @Override - public ValuePolicyType resolve() { - return SecurityUtil.getPasswordPolicy(securityPolicy); - } - }; - - MappingInitializer,PrismPropertyDefinition> initializer = - (builder) -> { - builder.defaultTargetDefinition(projPasswordPropertyDefinition); - builder.defaultSource(new Source<>(focusPasswordIdi, ExpressionConstants.VAR_INPUT_QNAME)); - builder.valuePolicyResolver(stringPolicyResolver); - return builder; - }; - - MappingOutputProcessor> processor = - (mappingOutputPath, outputStruct) -> { - PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); - if (outputTriple == null) { - LOGGER.trace("Credentials 'password' expression resulted in null output triple, skipping credentials processing for {}", rsd); - return false; - } - - boolean projectionIsNew = projDelta != null && (projDelta.getChangeType() == ChangeType.ADD - || projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD); - - Collection> newValues; - if (projectionIsNew) { - newValues = outputTriple.getNonNegativeValues(); - } else { - newValues = outputTriple.getPlusSet(); - } - - if (!canGetCleartext(newValues)) { - ObjectDelta projectionPrimaryDelta = projCtx.getPrimaryDelta(); - if (projectionPrimaryDelta != null) { - PropertyDelta passwordPrimaryDelta = projectionPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - if (passwordPrimaryDelta != null) { - // We have only hashed value coming from the mapping. There are not very useful - // for provisioning. But we have primary projection delta - and that is very likely - // to be better. - // Skip all password mappings in this case. Primary delta trumps everything. - // No weak, normal or even strong mapping can change that. - // We need to disregard even strong mapping in this case. If we would heed the strong - // mapping then account initialization won't be possible. - LOGGER.trace("We have primary password delta in projection, skipping credentials processing"); - return false; - } - } - } - - Collection> minusSet = outputTriple.getMinusSet(); - if (minusSet != null && !minusSet.isEmpty()) { - if (!canGetCleartext(minusSet)) { - // We have hashed values in minus set. That is not great, we won't be able to get - // cleartext from that if we need it (e.g. for runAs in provisioning). - // Therefore try to get old value from focus password delta. If that matches with - // hashed value then we have the cleartext. - ProtectedStringType oldProjectionPassword = minusSet.iterator().next().getRealValue(); - PropertyDelta focusPasswordDelta = (PropertyDelta) focusPasswordIdi.getDelta(); - Collection> focusPasswordDeltaOldValues = focusPasswordDelta.getEstimatedOldValues(); - if (focusPasswordDeltaOldValues != null && !focusPasswordDeltaOldValues.isEmpty()) { - ProtectedStringType oldFocusPassword = focusPasswordDeltaOldValues.iterator().next().getRealValue(); - try { - if (oldFocusPassword.canGetCleartext() && protector.compareCleartext(oldFocusPassword, oldProjectionPassword)) { - outputTriple.clearMinusSet(); - outputTriple.addToMinusSet(prismContext.itemFactory().createPropertyValue(oldFocusPassword)); - } - } catch (EncryptionException e) { - throw new SystemException(e.getMessage(), e); - } - } - } - } - - return true; - }; - - - mappingEvaluator.evaluateOutboundMapping(context, projCtx, outboundMappingTypes, - SchemaConstants.PATH_PASSWORD_VALUE, initializer, processor, - now, MappingTimeEval.CURRENT, evaluateWeak, "password mapping", task, result); - - } - - private boolean isActivated(List outboundMappingTypes, ObjectDelta focusDelta) { - if (focusDelta == null) { - return false; - } - for (MappingType outboundMappingType: outboundMappingTypes) { - List sources = outboundMappingType.getSource(); - if (sources.isEmpty()) { - // Default source - if (focusDelta.hasItemDelta(SchemaConstants.PATH_PASSWORD_VALUE)) { - return true; - } - } - for (VariableBindingDefinitionType source: sources) { - ItemPathType pathType = source.getPath(); - ItemPath path = pathType.getItemPath().stripVariableSegment(); - if (focusDelta.hasItemDelta(path)) { - return true; - } - } - } - return false; - } - - private boolean canGetCleartext(Collection> pvals) { - if (pvals == null) { - return false; - } - for (PrismPropertyValue pval: pvals) { - if (pval.getValue().canGetCleartext()) { - return true; - } - } - return false; - } - - private boolean getEvaluateWeak(LensProjectionContext projCtx) { - CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); - if (CapabilityUtil.isPasswordReadable(credentialsCapabilityType)) { - return true; - } - // Password not readable. Therefore evaluate weak mappings only during add operaitons. - // We do not know whether there is a password already set on the resource. And we do not - // want to overwrite it every time. - return projCtx.isAdd(); - } - - private void validateProjectionPassword(LensContext context, - final LensProjectionContext projectionContext, final SecurityPolicyType securityPolicy, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (securityPolicy == null) { - LOGGER.trace("Skipping processing password policies. Security policy not specified."); - return; - } - - ObjectDelta accountDelta = projectionContext.getDelta(); - - if (accountDelta == null){ - LOGGER.trace("Skipping processing password policies. Shadow delta not specified."); - return; - } - - if (accountDelta.isDelete()) { - return; - } - - PrismObject accountShadow = null; - PrismProperty password = null; - if (accountDelta.isAdd()) { - accountShadow = accountDelta.getObjectToAdd(); - if (accountShadow != null){ - password = accountShadow.findProperty(SchemaConstants.PATH_PASSWORD_VALUE); - } - } - if (accountDelta.isModify() || password == null) { - PropertyDelta passwordValueDelta = - accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - // Modification sanity check - if (accountDelta.getChangeType() == ChangeType.MODIFY && passwordValueDelta != null - && (passwordValueDelta.isAdd() || passwordValueDelta.isDelete())) { - throw new SchemaException("Shadow password value cannot be added or deleted, it can only be replaced"); - } - if (passwordValueDelta == null) { - LOGGER.trace("Skipping processing password policies. Shadow delta does not contain password change."); - return; - } - password = (PrismProperty) passwordValueDelta.getItemNewMatchingPath(null); - } - - if (accountShadow == null) { - accountShadow = projectionContext.getObjectNew(); - } - - String passwordValue = determinePasswordValue(password); - - ObjectValuePolicyEvaluator objectValuePolicyEvaluator = new ObjectValuePolicyEvaluator.Builder() - .now(now) - .originResolver(getOriginResolver(accountShadow)) - .protector(protector) - .securityPolicy(securityPolicy) - .shortDesc("password for " + accountShadow) - .task(task) - .valueItemPath(SchemaConstants.PATH_PASSWORD_VALUE) - .valuePolicyProcessor(valuePolicyProcessor) - .build(); - OperationResult validationResult = objectValuePolicyEvaluator.validateStringValue(passwordValue, result); - -// boolean isValid = valuePolicyProcessor.validateValue(passwordValue, securityPolicy, getOriginResolver(accountShadow), "projection password policy", task, result); - - if (!validationResult.isSuccess()) { - LOGGER.debug("Password for projection {} is not valid (policy={}): {}", projectionContext.getHumanReadableName(), securityPolicy, validationResult.getUserFriendlyMessage()); - result.computeStatus(); - throw new PolicyViolationException( - new LocalizableMessageBuilder() - .key("PolicyViolationException.message.projectionPassword") - .arg(projectionContext.getHumanReadableName()) - .arg(validationResult.getUserFriendlyMessage()) - .build()); - } - } - - private ShadowValuePolicyOriginResolver getOriginResolver(PrismObject accountShadow) { - return new ShadowValuePolicyOriginResolver(accountShadow, modelObjectResolver); - } - - private void applyMetadata(LensContext context, - final LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) - throws SchemaException { - - ObjectDelta accountDelta = projectionContext.getDelta(); - - if (projectionContext.isDelete()) { - return; - } - - if (accountDelta == null) { - LOGGER.trace("Skipping application of password metadata. Shadow delta not specified."); - return; - } - - PropertyDelta passwordValueDelta = - accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - if (passwordValueDelta == null) { - LOGGER.trace("Skipping application of password metadata. No password change."); - return; - } - - if (projectionContext.isAdd()) { - MetadataType metadataType = operationalDataManager.createCreateMetadata(context, now, task); - ContainerDelta metadataDelta = prismContext.deltaFactory().container() - .createDelta(SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectDefinition()); - PrismContainerValue cval = metadataType.asPrismContainerValue(); - cval.setOriginTypeRecursive(OriginType.OUTBOUND); - metadataDelta.addValuesToAdd(metadataType.asPrismContainerValue()); - projectionContext.swallowToSecondaryDelta(metadataDelta); - - } else if (projectionContext.isModify()) { - ContainerDelta metadataDelta = accountDelta.findContainerDelta(SchemaConstants.PATH_PASSWORD_METADATA); - if (metadataDelta == null) { - Collection> modifyMetadataDeltas = operationalDataManager.createModifyMetadataDeltas(context, SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectTypeClass(), now, task); - for (ItemDelta itemDelta: modifyMetadataDeltas) { - itemDelta.setOriginTypeRecursive(OriginType.OUTBOUND); - projectionContext.swallowToSecondaryDelta(itemDelta); - } - } - } - - } - - private SecurityPolicyType determineSecurityPolicy(LensContext context, - final LensProjectionContext projCtx, XMLGregorianCalendar now, Task task, OperationResult result) { - SecurityPolicyType securityPolicy = projCtx.getProjectionSecurityPolicy(); - if (securityPolicy != null) { - return securityPolicy; - } - return context.getGlobalSecurityPolicy(); - } - - // On missing password this returns empty string (""). It is then up to password policy whether it allows empty passwords or not. - private String determinePasswordValue(PrismProperty password) { - if (password == null || password.getValue(ProtectedStringType.class) == null) { - return null; - } - - ProtectedStringType passValue = password.getRealValue(); - - return determinePasswordValue(passValue); - } - - private String determinePasswordValue(ProtectedStringType passValue) { - if (passValue == null) { - return null; - } - - String passwordStr = passValue.getClearValue(); - - if (passwordStr == null && passValue.getEncryptedDataType () != null) { - // TODO: is this appropriate handling??? - try { - passwordStr = protector.decryptString(passValue); - } catch (EncryptionException ex) { - throw new SystemException("Failed to process password for focus: " + ex.getMessage(), ex); - } - } - - return passwordStr; - } - - private void checkExistingDeltaSanity(LensProjectionContext projCtx, - PropertyDelta passwordDelta) throws SchemaException { - if (passwordDelta != null && (passwordDelta.isAdd() || passwordDelta.isDelete())) { - throw new SchemaException("Password for projection " + projCtx.getResourceShadowDiscriminator() - + " cannot be added or deleted, it can only be replaced"); - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.projector.credentials; + +import static com.evolveum.midpoint.prism.delta.ChangeType.MODIFY; + +import java.util.Collection; +import java.util.List; + +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.CapabilityUtil; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator; +import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.OperationalDataManager; +import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingInitializer; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingOutputProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingTimeEval; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.security.api.SecurityUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.PolicyViolationException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; + +/** + * Processor for projection credentials. Which at this moment means just the password. + * + * @author Radovan Semancik + */ +@Component +public class ProjectionCredentialsProcessor { + + private static final Trace LOGGER = TraceManager.getTrace(ProjectionCredentialsProcessor.class); + + @Autowired private PrismContext prismContext; + @Autowired private ContextLoader contextLoader; + @Autowired private MappingFactory mappingFactory; + @Autowired private MappingEvaluator mappingEvaluator; + @Autowired private ValuePolicyProcessor valuePolicyProcessor; + @Autowired private Protector protector; + @Autowired private OperationalDataManager operationalDataManager; + @Autowired private ModelObjectResolver modelObjectResolver; + + public void processProjectionCredentials(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, + OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (projectionContext.isDelete()) { + return; + } + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + + processProjectionCredentialsFocus((LensContext) context, projectionContext, now, task, result); + + } + } + + private void processProjectionCredentialsFocus(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, + OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + SecurityPolicyType securityPolicy = determineSecurityPolicy(context, projectionContext, now, task, result); + + processProjectionPasswordMapping(context, projectionContext, securityPolicy, now, task, result); + + validateProjectionPassword(context, projectionContext, securityPolicy, now, task, result); + + applyMetadata(context, projectionContext, now, task, result); + } + + private void processProjectionPasswordMapping(LensContext context, + final LensProjectionContext projCtx, final SecurityPolicyType securityPolicy, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext focusContext = context.getFocusContext(); + + PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + // This must be a focus delete or something similar. No point in proceeding + LOGGER.trace("focusNew is null, skipping credentials processing"); + return; + } + + PrismObjectDefinition accountDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(ShadowType.class); + PrismPropertyDefinition projPasswordPropertyDefinition = accountDefinition + .findPropertyDefinition(SchemaConstants.PATH_PASSWORD_VALUE); + + ResourceShadowDiscriminator rsd = projCtx.getResourceShadowDiscriminator(); + + RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); + if (refinedProjDef == null) { + LOGGER.trace("No RefinedObjectClassDefinition, therefore also no password outbound definition, skipping credentials processing for projection {}", rsd); + return; + } + + List outboundMappingTypes = refinedProjDef.getPasswordOutbound(); + if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { + LOGGER.trace("No outbound password mapping for {}, skipping credentials processing", rsd); + return; + } + + // HACK + if (!projCtx.isDoReconciliation() && !projCtx.isAdd() && !isActivated(outboundMappingTypes, focusContext.getDelta())) { + LOGGER.trace("Outbound password mappings not activated for type {}, skipping credentials processing", rsd); + return; + } + + ObjectDelta projDelta = projCtx.getDelta(); + PropertyDelta projPasswordDelta; + if (projDelta != null && projDelta.getChangeType() == MODIFY) { + projPasswordDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + } else { + projPasswordDelta = null; + } + checkExistingDeltaSanity(projCtx, projPasswordDelta); + + boolean evaluateWeak = getEvaluateWeak(projCtx); + + ItemDeltaItem, PrismPropertyDefinition> focusPasswordIdi = focusContext + .getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); + + ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { + @Override + public void setOutputPath(ItemPath outputPath) { + } + @Override + public void setOutputDefinition(ItemDefinition outputDefinition) { + } + @Override + public ValuePolicyType resolve() { + return SecurityUtil.getPasswordPolicy(securityPolicy); + } + }; + + MappingInitializer,PrismPropertyDefinition> initializer = + (builder) -> { + builder.mappingKind(MappingKindType.OUTBOUND); + builder.defaultTargetDefinition(projPasswordPropertyDefinition); + builder.defaultSource(new Source<>(focusPasswordIdi, ExpressionConstants.VAR_INPUT_QNAME)); + builder.valuePolicyResolver(stringPolicyResolver); + return builder; + }; + + MappingOutputProcessor> processor = + (mappingOutputPath, outputStruct) -> { + PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); + if (outputTriple == null) { + LOGGER.trace("Credentials 'password' expression resulted in null output triple, skipping credentials processing for {}", rsd); + return false; + } + + boolean projectionIsNew = projDelta != null && (projDelta.getChangeType() == ChangeType.ADD + || projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD); + + Collection> newValues; + if (projectionIsNew) { + newValues = outputTriple.getNonNegativeValues(); + } else { + newValues = outputTriple.getPlusSet(); + } + + if (!canGetCleartext(newValues)) { + ObjectDelta projectionPrimaryDelta = projCtx.getPrimaryDelta(); + if (projectionPrimaryDelta != null) { + PropertyDelta passwordPrimaryDelta = projectionPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + if (passwordPrimaryDelta != null) { + // We have only hashed value coming from the mapping. There are not very useful + // for provisioning. But we have primary projection delta - and that is very likely + // to be better. + // Skip all password mappings in this case. Primary delta trumps everything. + // No weak, normal or even strong mapping can change that. + // We need to disregard even strong mapping in this case. If we would heed the strong + // mapping then account initialization won't be possible. + LOGGER.trace("We have primary password delta in projection, skipping credentials processing"); + return false; + } + } + } + + Collection> minusSet = outputTriple.getMinusSet(); + if (minusSet != null && !minusSet.isEmpty()) { + if (!canGetCleartext(minusSet)) { + // We have hashed values in minus set. That is not great, we won't be able to get + // cleartext from that if we need it (e.g. for runAs in provisioning). + // Therefore try to get old value from focus password delta. If that matches with + // hashed value then we have the cleartext. + ProtectedStringType oldProjectionPassword = minusSet.iterator().next().getRealValue(); + PropertyDelta focusPasswordDelta = (PropertyDelta) focusPasswordIdi.getDelta(); + Collection> focusPasswordDeltaOldValues = focusPasswordDelta.getEstimatedOldValues(); + if (focusPasswordDeltaOldValues != null && !focusPasswordDeltaOldValues.isEmpty()) { + ProtectedStringType oldFocusPassword = focusPasswordDeltaOldValues.iterator().next().getRealValue(); + try { + if (oldFocusPassword.canGetCleartext() && protector.compareCleartext(oldFocusPassword, oldProjectionPassword)) { + outputTriple.clearMinusSet(); + outputTriple.addToMinusSet(prismContext.itemFactory().createPropertyValue(oldFocusPassword)); + } + } catch (EncryptionException e) { + throw new SystemException(e.getMessage(), e); + } + } + } + } + + return true; + }; + + + mappingEvaluator.evaluateOutboundMapping(context, projCtx, outboundMappingTypes, + SchemaConstants.PATH_PASSWORD_VALUE, initializer, processor, + now, MappingTimeEval.CURRENT, evaluateWeak, "password mapping", task, result); + + } + + private boolean isActivated(List outboundMappingTypes, ObjectDelta focusDelta) { + if (focusDelta == null) { + return false; + } + for (MappingType outboundMappingType: outboundMappingTypes) { + List sources = outboundMappingType.getSource(); + if (sources.isEmpty()) { + // Default source + if (focusDelta.hasItemDelta(SchemaConstants.PATH_PASSWORD_VALUE)) { + return true; + } + } + for (VariableBindingDefinitionType source: sources) { + ItemPathType pathType = source.getPath(); + ItemPath path = pathType.getItemPath().stripVariableSegment(); + if (focusDelta.hasItemDelta(path)) { + return true; + } + } + } + return false; + } + + private boolean canGetCleartext(Collection> pvals) { + if (pvals == null) { + return false; + } + for (PrismPropertyValue pval: pvals) { + if (pval.getValue().canGetCleartext()) { + return true; + } + } + return false; + } + + private boolean getEvaluateWeak(LensProjectionContext projCtx) { + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); + if (CapabilityUtil.isPasswordReadable(credentialsCapabilityType)) { + return true; + } + // Password not readable. Therefore evaluate weak mappings only during add operaitons. + // We do not know whether there is a password already set on the resource. And we do not + // want to overwrite it every time. + return projCtx.isAdd(); + } + + private void validateProjectionPassword(LensContext context, + final LensProjectionContext projectionContext, final SecurityPolicyType securityPolicy, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (securityPolicy == null) { + LOGGER.trace("Skipping processing password policies. Security policy not specified."); + return; + } + + ObjectDelta accountDelta = projectionContext.getDelta(); + + if (accountDelta == null){ + LOGGER.trace("Skipping processing password policies. Shadow delta not specified."); + return; + } + + if (accountDelta.isDelete()) { + return; + } + + PrismObject accountShadow = null; + PrismProperty password = null; + if (accountDelta.isAdd()) { + accountShadow = accountDelta.getObjectToAdd(); + if (accountShadow != null){ + password = accountShadow.findProperty(SchemaConstants.PATH_PASSWORD_VALUE); + } + } + if (accountDelta.isModify() || password == null) { + PropertyDelta passwordValueDelta = + accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + // Modification sanity check + if (accountDelta.getChangeType() == ChangeType.MODIFY && passwordValueDelta != null + && (passwordValueDelta.isAdd() || passwordValueDelta.isDelete())) { + throw new SchemaException("Shadow password value cannot be added or deleted, it can only be replaced"); + } + if (passwordValueDelta == null) { + LOGGER.trace("Skipping processing password policies. Shadow delta does not contain password change."); + return; + } + password = (PrismProperty) passwordValueDelta.getItemNewMatchingPath(null); + } + + if (accountShadow == null) { + accountShadow = projectionContext.getObjectNew(); + } + + String passwordValue = determinePasswordValue(password); + + ObjectValuePolicyEvaluator objectValuePolicyEvaluator = new ObjectValuePolicyEvaluator.Builder() + .now(now) + .originResolver(getOriginResolver(accountShadow)) + .protector(protector) + .securityPolicy(securityPolicy) + .shortDesc("password for " + accountShadow) + .task(task) + .valueItemPath(SchemaConstants.PATH_PASSWORD_VALUE) + .valuePolicyProcessor(valuePolicyProcessor) + .build(); + OperationResult validationResult = objectValuePolicyEvaluator.validateStringValue(passwordValue, result); + +// boolean isValid = valuePolicyProcessor.validateValue(passwordValue, securityPolicy, getOriginResolver(accountShadow), "projection password policy", task, result); + + if (!validationResult.isSuccess()) { + LOGGER.debug("Password for projection {} is not valid (policy={}): {}", projectionContext.getHumanReadableName(), securityPolicy, validationResult.getUserFriendlyMessage()); + result.computeStatus(); + throw new PolicyViolationException( + new LocalizableMessageBuilder() + .key("PolicyViolationException.message.projectionPassword") + .arg(projectionContext.getHumanReadableName()) + .arg(validationResult.getUserFriendlyMessage()) + .build()); + } + } + + private ShadowValuePolicyOriginResolver getOriginResolver(PrismObject accountShadow) { + return new ShadowValuePolicyOriginResolver(accountShadow, modelObjectResolver); + } + + private void applyMetadata(LensContext context, + final LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) + throws SchemaException { + + ObjectDelta accountDelta = projectionContext.getDelta(); + + if (projectionContext.isDelete()) { + return; + } + + if (accountDelta == null) { + LOGGER.trace("Skipping application of password metadata. Shadow delta not specified."); + return; + } + + PropertyDelta passwordValueDelta = + accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + if (passwordValueDelta == null) { + LOGGER.trace("Skipping application of password metadata. No password change."); + return; + } + + if (projectionContext.isAdd()) { + MetadataType metadataType = operationalDataManager.createCreateMetadata(context, now, task); + ContainerDelta metadataDelta = prismContext.deltaFactory().container() + .createDelta(SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectDefinition()); + PrismContainerValue cval = metadataType.asPrismContainerValue(); + cval.setOriginTypeRecursive(OriginType.OUTBOUND); + metadataDelta.addValuesToAdd(metadataType.asPrismContainerValue()); + projectionContext.swallowToSecondaryDelta(metadataDelta); + + } else if (projectionContext.isModify()) { + ContainerDelta metadataDelta = accountDelta.findContainerDelta(SchemaConstants.PATH_PASSWORD_METADATA); + if (metadataDelta == null) { + Collection> modifyMetadataDeltas = operationalDataManager.createModifyMetadataDeltas(context, SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectTypeClass(), now, task); + for (ItemDelta itemDelta: modifyMetadataDeltas) { + itemDelta.setOriginTypeRecursive(OriginType.OUTBOUND); + projectionContext.swallowToSecondaryDelta(itemDelta); + } + } + } + + } + + private SecurityPolicyType determineSecurityPolicy(LensContext context, + final LensProjectionContext projCtx, XMLGregorianCalendar now, Task task, OperationResult result) { + SecurityPolicyType securityPolicy = projCtx.getProjectionSecurityPolicy(); + if (securityPolicy != null) { + return securityPolicy; + } + return context.getGlobalSecurityPolicy(); + } + + // On missing password this returns empty string (""). It is then up to password policy whether it allows empty passwords or not. + private String determinePasswordValue(PrismProperty password) { + if (password == null || password.getValue(ProtectedStringType.class) == null) { + return null; + } + + ProtectedStringType passValue = password.getRealValue(); + + return determinePasswordValue(passValue); + } + + private String determinePasswordValue(ProtectedStringType passValue) { + if (passValue == null) { + return null; + } + + String passwordStr = passValue.getClearValue(); + + if (passwordStr == null && passValue.getEncryptedDataType () != null) { + // TODO: is this appropriate handling??? + try { + passwordStr = protector.decryptString(passValue); + } catch (EncryptionException ex) { + throw new SystemException("Failed to process password for focus: " + ex.getMessage(), ex); + } + } + + return passwordStr; + } + + private void checkExistingDeltaSanity(LensProjectionContext projCtx, + PropertyDelta passwordDelta) throws SchemaException { + if (passwordDelta != null && (passwordDelta.isAdd() || passwordDelta.isDelete())) { + throw new SchemaException("Password for projection " + projCtx.getResourceShadowDiscriminator() + + " cannot be added or deleted, it can only be replaced"); + } + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java index 90089b90050..fca63d3ad7b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java @@ -24,6 +24,8 @@ import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -68,22 +70,6 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertyAccessType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingAndDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectAssociationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** @@ -736,6 +722,7 @@ private vo .variables(variables) .variableResolver(variableProducer) .valuePolicyResolver(createStringPolicyResolver(context)) + .mappingKind(MappingKindType.INBOUND) .originType(OriginType.INBOUND) .originObject(resource); @@ -1329,6 +1316,7 @@ private void processSpecialPropertyInbound(Collection evaluatedAssignment, @NotNull PlusMinusZero relativeMode, AssignmentPathVariables assignmentPathVariables, String sourceDescription) { - super(mapping, originObject); + super(mapping, MappingKindType.ASSIGNED, originObject); this.evaluatedAssignment = evaluatedAssignment; this.relativeMode = relativeMode; this.assignmentPathVariables = assignmentPathVariables; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java index 473c287bb5c..92888872a27 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java @@ -32,7 +32,7 @@ public class AutoassignRoleMappingEvaluationRequest extends FocalMappingEvaluati private AssignmentType assignmentType; public AutoassignRoleMappingEvaluationRequest(@NotNull AutoassignMappingType mapping, @NotNull AbstractRoleType role) { - super(mapping, role); + super(mapping, MappingKindType.AUTO_ASSIGN, role); } @Override diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java index 2fbf8e44c28..ce8b0fd9d7a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java @@ -38,12 +38,14 @@ public abstract class FocalMappingEvaluationRequest void evaluateOutboundMapping(final LensContext MappingImpl createFocusMapping(final MappingFactory mappingFactory, - final LensContext context, final MappingType mappingType, ObjectType originObject, - ObjectDeltaObject focusOdo, AssignmentPathVariables assignmentPathVariables, PrismObject configuration, - XMLGregorianCalendar now, String contextDesc, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - Integer iteration = null; - String iterationToken = null; - if (focusOdo.getNewObject() != null) { - AH focusNewType = focusOdo.getNewObject().asObjectable(); - iteration = focusNewType.getIteration(); - iterationToken = focusNewType.getIterationToken(); - } else if (focusOdo.getOldObject() != null) { - AH focusOldType = focusOdo.getOldObject().asObjectable(); - iteration = focusOldType.getIteration(); - iterationToken = focusOldType.getIterationToken(); - } - return createFocusMapping(mappingFactory, context, mappingType, originObject, focusOdo, null, focusOdo.getAnyObject(), assignmentPathVariables, - iteration, iterationToken, configuration, now, contextDesc, task, result); - } - - public MappingImpl createFocusMapping( - final MappingFactory mappingFactory, final LensContext context, final MappingType mappingType, ObjectType originObject, + MappingImpl createFocusMapping( + final MappingFactory mappingFactory, final LensContext context, final MappingType mappingType, MappingKindType mappingKind, ObjectType originObject, ObjectDeltaObject focusOdo, Source defaultSource, PrismObject defaultTargetObject, AssignmentPathVariables assignmentPathVariables, Integer iteration, String iterationToken, PrismObject configuration, XMLGregorianCalendar now, String contextDesc, final Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { @@ -644,6 +626,7 @@ public ValuePolicyType resolve() { .targetContext(defaultTargetObject.getDefinition()) .variables(variables) .originalTargetValues(targetValues) + .mappingKind(mappingKind) .originType(OriginType.USER_POLICY) .originObject(originObject) .objectResolver(objectResolver) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java index 91a9dc8e23a..06e37145eed 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java @@ -91,7 +91,7 @@ NextRecompute evaluateMappingsToTriples( PrismObject targetObject = targetSpecification.getTargetObject(updatedFocusOdo); MappingImpl mapping = mappingEvaluator.createFocusMapping(mappingFactory, context, request.getMapping(), - request.getOriginObject(), updatedFocusOdo, request.constructDefaultSource(updatedFocusOdo), + request.getMappingKind(), request.getOriginObject(), updatedFocusOdo, request.constructDefaultSource(updatedFocusOdo), targetObject, request.getAssignmentPathVariables(), iteration, iterationToken, context.getSystemConfiguration(), now, description, task, result); if (mapping == null) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/TemplateMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/TemplateMappingEvaluationRequest.java index 4da53351dba..3ab5593d572 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/TemplateMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/TemplateMappingEvaluationRequest.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens.projector.mappings; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType; @@ -21,7 +22,7 @@ public class TemplateMappingEvaluationRequest extends FocalMappingEvaluationRequ public TemplateMappingEvaluationRequest(@NotNull ObjectTemplateMappingType mapping, @NotNull ObjectTemplateType objectTemplate) { - super(mapping, objectTemplate); + super(mapping, MappingKindType.TEMPLATE, objectTemplate); } @Override diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index ce881eb8b0b..903cbd3d2c0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -635,6 +635,7 @@ private boolean isRuleConditionTrue(GlobalPoli .createMappingBuilder(); ObjectDeltaObject focusOdo = new ObjectDeltaObject<>(focus, null, focus, focus.getDefinition()); builder = builder.mappingType(condition) + .mappingKind(MappingKindType.POLICY_RULE_CONDITION) .contextDescription("condition in global policy rule " + globalPolicyRule.getName()) .sourceContext(focusOdo) .defaultTargetDefinition( From 561b51b4a5eb1de9a3ac5a0ea5afcaddace6df15 Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 24 Mar 2020 17:59:38 +0100 Subject: [PATCH 6/8] adding prism object to reference - fix for getting activation of object (MID-6128) --- .../assignment/AssignmentEditorDto.java | 1 + .../schrodinger/page/HomePageTest.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorDto.java index b307b33b18c..8b7e7097db2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorDto.java @@ -192,6 +192,7 @@ public static AssignmentEditorDto createDtoFromObject(ObjectType object, UserDto if (relation != null){ targetRef.setRelation(relation); } + targetRef.asReferenceValue().setObject(object.asPrismObject()); AssignmentType assignment = new AssignmentType(); assignment.setTargetRef(targetRef); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java new file mode 100644 index 00000000000..7a75ec19bf6 --- /dev/null +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.testing.schrodinger.page; + +import com.evolveum.midpoint.testing.schrodinger.AbstractSchrodingerTest; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * @author skublik + */ + +public class HomePage extends AbstractSchrodingerTest { + + @Test + public void test001OpenPageWithSlashOnEndOfUrl() { + Assert.assertFalse( + aboutPage.gitDescribe().isEmpty()); + } +} From 25794fb6f43003c40fb2446b1bb5ede5ef91c37e Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 24 Mar 2020 18:01:30 +0100 Subject: [PATCH 7/8] adding of schrodinger tests for redirect to url with slash on end (MID-5139) --- .../schrodinger/page/HomePageTest.java | 20 +++++++++++++++---- .../schrodingertest/testng-integration.xml | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java index 7a75ec19bf6..1bd949a47ab 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/HomePageTest.java @@ -1,25 +1,37 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2010-2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ package com.evolveum.midpoint.testing.schrodinger.page; +import com.evolveum.midpoint.schrodinger.component.common.FeedbackBox; +import com.evolveum.midpoint.schrodinger.page.login.FormLoginPage; import com.evolveum.midpoint.testing.schrodinger.AbstractSchrodingerTest; import org.testng.Assert; import org.testng.annotations.Test; +import static com.codeborne.selenide.Selenide.open; + /** * @author skublik */ -public class HomePage extends AbstractSchrodingerTest { +public class HomePageTest extends AbstractSchrodingerTest { @Test public void test001OpenPageWithSlashOnEndOfUrl() { - Assert.assertFalse( - aboutPage.gitDescribe().isEmpty()); + open("/self/dashboard/"); + //when request will be redirect to error page, then couldn't click on home menu button + basicPage.home(); + } + + @Test + public void test002OpenPageWithoutSlashOnEndOfUrl() { + open("/self/dashboard"); + //when request will be redirect to error page, then couldn't click on home menu button + basicPage.home(); } } diff --git a/testing/schrodingertest/testng-integration.xml b/testing/schrodingertest/testng-integration.xml index 28e4c92f5b9..528cb4721ec 100644 --- a/testing/schrodingertest/testng-integration.xml +++ b/testing/schrodingertest/testng-integration.xml @@ -98,4 +98,9 @@ + + + + + \ No newline at end of file From 60328c40b2b99c6cf41ab6ce90145fae941d07bd Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 24 Mar 2020 19:35:35 +0100 Subject: [PATCH 8/8] Fix serialization of raw deltas (MID-6086) 1. CryptoUtil methods no longer fail on tunneled SchemaExceptions from the depths of prism-impl. 2. parseRealValue now correctly parses ObjectReferenceType objects (no longed using BeanUnmarshaller for their parsing) 3. Prism visitor now visits also objects embedded in reference values. 4. JaxbVisitor in RawType visits the value after being parsed. Fix #2 resolves MID-6086. Fixes #3 and #4 are necessary to correctly encrypt passwords in ShadowType objects embedded in linkRef references. --- .../midpoint/common/crypto/CryptoUtil.java | 54 ++++++------ .../midpoint/common/TestCryptoUtil.java | 85 ++++++++++++++++++- .../resources/crypto/task-add-account.xml | 53 ++++++++++++ .../prism/xml/ns/_public/types_3/RawType.java | 21 +++-- .../prism/impl/PrismReferenceValueImpl.java | 9 ++ .../impl/marshaller/BeanUnmarshaller.java | 4 +- .../impl/marshaller/PrismParserImpl.java | 6 +- .../prism/impl/xnode/MapXNodeImpl.java | 11 +-- .../midpoint/prism/PrismInternalTestUtil.java | 6 +- .../midpoint/prism/TestPrismParsing.java | 10 ++- .../common/json/user-jack-adhoc.json | 10 ++- .../common/json/user-jack-object.json | 10 ++- .../common/yaml/user-jack-adhoc.yaml | 6 +- .../common/yaml/user-jack-object.yaml | 6 +- 14 files changed, 233 insertions(+), 58 deletions(-) create mode 100644 infra/common/src/test/resources/crypto/task-add-account.xml diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/crypto/CryptoUtil.java b/infra/common/src/main/java/com/evolveum/midpoint/common/crypto/CryptoUtil.java index be4124b049e..7e82a460b6c 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/crypto/CryptoUtil.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/crypto/CryptoUtil.java @@ -15,12 +15,11 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.exception.TunnelException; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; -import com.evolveum.prism.xml.ns._public.types_3.RawType; import org.jetbrains.annotations.NotNull; import javax.crypto.Cipher; @@ -45,24 +44,24 @@ public class CryptoUtil { /** * Encrypts all encryptable values in the object. + * + * Note: We could use TunnelException here (it would be cleaner) but the tunneled exception could be + * other than EncryptionException! For example, it could come from RawType, carrying a SchemaException. + * See MID-6086. So we use throwExceptionAsUnchecked hack instead. */ + @SuppressWarnings("RedundantThrows") public static void encryptValues(Protector protector, PrismObject object) throws EncryptionException { - try { - object.accept(createEncryptingVisitor(protector)); - } catch (TunnelException e) { - throw (EncryptionException) e.getCause(); - } + //noinspection unchecked + object.accept(createEncryptingVisitor(protector)); } /** * Encrypts all encryptable values in delta. */ + @SuppressWarnings("RedundantThrows") public static void encryptValues(Protector protector, ObjectDelta delta) throws EncryptionException { - try { - delta.accept(createEncryptingVisitor(protector)); - } catch (TunnelException e) { - throw (EncryptionException) e.getCause(); - } + //noinspection unchecked + delta.accept(createEncryptingVisitor(protector)); } @NotNull @@ -76,7 +75,8 @@ private static ProtectedStringProcessor createEncryptingProcessor(Protector prot try { protector.encrypt(protectedString); } catch (EncryptionException e) { - throw new EncryptionException("Failed to encrypt value for field " + propertyName + ": " + e.getMessage(), e); + MiscUtil.throwExceptionAsUnchecked( + new EncryptionException("Failed to encrypt value for field " + propertyName + ": " + e.getMessage(), e)); } } }); @@ -85,6 +85,7 @@ private static ProtectedStringProcessor createEncryptingProcessor(Protector prot // Checks that everything is encrypted public static void checkEncrypted(final PrismObject object) { try { + //noinspection unchecked object.accept(createCheckingVisitor()); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage() + " in " + object, e); @@ -95,6 +96,7 @@ public static void checkEncrypted(final PrismObject ob // Checks that everything is encrypted public static void checkEncrypted(final ObjectDelta delta) { try { + //noinspection unchecked delta.accept(createCheckingVisitor()); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage() + " in delta " + delta, e); @@ -184,19 +186,18 @@ public static void securitySelfTest(OperationResult parentTestResult) { /** * Re-encrypts all encryptable values in the object. */ + @SuppressWarnings("RedundantThrows") public static int reencryptValues(Protector protector, PrismObject object) throws EncryptionException { - try { - Holder modCountHolder = new Holder<>(0); - object.accept(createVisitor((ps, propName) -> reencryptProtectedStringType(ps, propName, modCountHolder, protector))); - return modCountHolder.getValue(); - } catch (TunnelException e) { - throw (EncryptionException) e.getCause(); - } + Holder modCountHolder = new Holder<>(0); + //noinspection unchecked + object.accept(createVisitor((ps, propName) -> reencryptProtectedStringType(ps, propName, modCountHolder, protector))); + return modCountHolder.getValue(); } @NotNull public static Collection getEncryptionKeyNames(PrismObject object) { Set keys = new HashSet<>(); + //noinspection unchecked object.accept(createVisitor((ps, propName) -> { if (ps.getEncryptedDataType() != null && ps.getEncryptedDataType().getKeyInfo() != null) { keys.add(ps.getEncryptedDataType().getKeyInfo().getKeyName()); @@ -208,6 +209,7 @@ public static Collection getEncryptionKeyNames(Pr @SuppressWarnings("unused") // used externally public static boolean containsCleartext(PrismObject object) { Holder result = new Holder<>(false); + //noinspection unchecked object.accept(createVisitor((ps, propName) -> { if (ps.getClearValue() != null) { result.setValue(true); @@ -219,6 +221,7 @@ public static boolean containsCleartext(PrismObject ob @SuppressWarnings("unused") // used externally public static boolean containsHashedData(PrismObject object) { Holder result = new Holder<>(false); + //noinspection unchecked object.accept(createVisitor((ps, propName) -> { if (ps.getHashedDataType() != null) { result.setValue(true); @@ -237,7 +240,7 @@ private static class CombinedVisitor implements Visitor, JaxbVisitor { private ProtectedStringProcessor processor; private String lastPropName = "?"; - CombinedVisitor(ProtectedStringProcessor processor) { + private CombinedVisitor(ProtectedStringProcessor processor) { this.processor = processor; } @@ -247,7 +250,7 @@ public void visit(JaxbVisitable visitable) { try { processor.apply(((ProtectedStringType) visitable), lastPropName); } catch (EncryptionException e) { - throw new TunnelException(e); + MiscUtil.throwExceptionAsUnchecked(e); } } else { JaxbVisitable.visitPrismStructure(visitable, this); @@ -257,7 +260,7 @@ public void visit(JaxbVisitable visitable) { @Override public void visit(Visitable visitable) { if (visitable instanceof PrismPropertyValue) { - PrismPropertyValue pval = ((PrismPropertyValue) visitable); + PrismPropertyValue pval = (PrismPropertyValue) visitable; Object realValue = pval.getRealValue(); if (realValue instanceof JaxbVisitable) { String oldLastPropName = lastPropName; @@ -290,7 +293,7 @@ private static void reencryptProtectedStringType(ProtectedStringType ps, String protector.encrypt(ps); increment(modCountHolder); } catch (EncryptionException e) { - throw new TunnelException(new EncryptionException("Failed to encrypt value for field " + propName + ": " + e.getMessage(), e)); + MiscUtil.throwExceptionAsUnchecked(new EncryptionException("Failed to encrypt value for field " + propName + ": " + e.getMessage(), e)); } } else if (ps.getEncryptedDataType() != null) { try { @@ -300,7 +303,7 @@ private static void reencryptProtectedStringType(ProtectedStringType ps, String increment(modCountHolder); } } catch (EncryptionException e) { - throw new TunnelException(new EncryptionException("Failed to check/reencrypt value for field " + propName + ": " + e.getMessage(), e)); + MiscUtil.throwExceptionAsUnchecked(new EncryptionException("Failed to check/reencrypt value for field " + propName + ": " + e.getMessage(), e)); } } else { // no clear nor encrypted value @@ -311,6 +314,7 @@ private static void increment(Holder countHolder) { countHolder.setValue(countHolder.getValue() + 1); } + @SuppressWarnings("SameParameterValue") private static void securitySelfTestAlgorithm(String algorithmName, String transformationName, Integer keySize, boolean critical, OperationResult parentResult) { OperationResult subresult = parentResult.createSubresult(CryptoUtil.class.getName()+".securitySelfTest.algorithm."+algorithmName); diff --git a/infra/common/src/test/java/com/evolveum/midpoint/common/TestCryptoUtil.java b/infra/common/src/test/java/com/evolveum/midpoint/common/TestCryptoUtil.java index 50facf859c0..d033ee17663 100644 --- a/infra/common/src/test/java/com/evolveum/midpoint/common/TestCryptoUtil.java +++ b/infra/common/src/test/java/com/evolveum/midpoint/common/TestCryptoUtil.java @@ -20,14 +20,22 @@ import java.util.HashSet; import java.util.Set; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; + +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.constants.SchemaConstants; + +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + import org.testng.annotations.BeforeSuite; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.xml.sax.SAXException; import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.KeyStoreBasedProtectorBuilder; import com.evolveum.midpoint.prism.crypto.Protector; @@ -43,6 +51,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import javax.xml.namespace.QName; + @Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class }) public class TestCryptoUtil extends AbstractUnitTest { @@ -50,6 +60,7 @@ public class TestCryptoUtil extends AbstractUnitTest { private static final File FILE_USER_JACK = new File(TEST_DIR, "user-jack.xml"); private static final File FILE_TASK_MODIFY_JACK_PASSWORD = new File(TEST_DIR, "task-modify-jack-password.xml"); private static final File FILE_TASK_ADD_JACK = new File(TEST_DIR, "task-add-jack.xml"); + private static final File FILE_TASK_ADD_ACCOUNT = new File(TEST_DIR, "task-add-account.xml"); private static final File FILE_SYSTEM_CONFIGURATION = new File(TEST_DIR, "system-configuration.xml"); private static final String KEYSTORE_PATH = TEST_RESOURCES_PATH + "/keystore.jceks"; @@ -107,6 +118,76 @@ public void test120EncryptBulkActionTask() throws Exception { CryptoUtil.checkEncrypted(task); } + /** + * MID-6086 + */ + @Test + public void test125EncryptAddAccountTask() throws Exception { + given(); + PrismContext prismContext = getPrismContext(); + PrismObject task = prismContext.parserFor(FILE_TASK_ADD_ACCOUNT).xml().parse(); + + when(); + CryptoUtil.encryptValues(protector, task); + + then(); + String serialized = prismContext.xmlSerializer().serialize(task); + System.out.println("After encryption:\n" + serialized); + assertFalse("Serialized object contains the password!", serialized.contains(PASSWORD_PLAINTEXT)); + + CryptoUtil.checkEncrypted(task); + } + + /** + * MID-6086 + */ + @Test + public void test127EncryptAddAccountTaskManuallyConstructed() throws Exception { + given(); + PrismContext prismContext = getPrismContext(); + PrismObject task = new TaskType(prismContext) + .name("test127") + .asPrismObject(); + PrismPropertyDefinition deltasDefinition = task.getDefinition() + .findPropertyDefinition(ItemPath.create(TaskType.F_EXTENSION, SchemaConstants.MODEL_EXTENSION_OBJECT_DELTAS)); + PrismProperty deltas = deltasDefinition.instantiate(); + + ShadowType shadow = new ShadowType(prismContext) + .name("some-shadow"); + PrismContainerDefinition attributesDef = shadow.asPrismObject().getDefinition() + .findContainerDefinition(ShadowType.F_ATTRIBUTES); + PrismContainer attributes = attributesDef.instantiate(); + shadow.asPrismObject().add(attributes); + + MutablePrismPropertyDefinition passwordDef = prismContext.definitionFactory() + .createPropertyDefinition( + new QName(SchemaConstants.NS_ICF_SCHEMA, "password"), ProtectedStringType.COMPLEX_TYPE); + PrismProperty password = passwordDef.instantiate(); + ProtectedStringType passwordRealValue = new ProtectedStringType(); + passwordRealValue.setClearValue(PASSWORD_PLAINTEXT); + password.setRealValue(passwordRealValue); + attributes.add(password); + + PrismReferenceValue linkToAdd = ObjectTypeUtil.createObjectRefWithFullObject(shadow, prismContext).asReferenceValue(); + ObjectDelta userDelta = prismContext.deltaFor(UserType.class) + .item(UserType.F_LINK_REF) + .add(linkToAdd) + .asObjectDelta("some-oid"); + + deltas.addRealValue(DeltaConvertor.toObjectDeltaType(userDelta, null)); + task.addExtensionItem(deltas); + + when(); + CryptoUtil.encryptValues(protector, task); + + then(); + String serialized = prismContext.xmlSerializer().serialize(task); + System.out.println("After encryption:\n" + serialized); + assertFalse("Serialized object contains the password!", serialized.contains(PASSWORD_PLAINTEXT)); + + CryptoUtil.checkEncrypted(task); + } + @Test public void test130EncryptUserInDelta() throws Exception { // GIVEN diff --git a/infra/common/src/test/resources/crypto/task-add-account.xml b/infra/common/src/test/resources/crypto/task-add-account.xml new file mode 100644 index 00000000000..5c2133c1bb0 --- /dev/null +++ b/infra/common/src/test/resources/crypto/task-add-account.xml @@ -0,0 +1,53 @@ + + + + + Execute changes + + + modify + c:UserType + 07cc8c14-f94a-4da9-86ab-0246fc63bb6b + + add + c:linkRef + + + + ri:AccountObjectClass + account + default + + pass1234word + + + + + + + false + false + false + + + 1585065525794-0-1 + + + + http://midpoint.evolveum.com/xml/ns/public/gui/channels-3#user + closed + Utility + http://midpoint.evolveum.com/xml/ns/public/model/execute-deltas/handler-3 + single + tight + diff --git a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/RawType.java b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/RawType.java index ed6196f7989..44ab534ed38 100644 --- a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/RawType.java +++ b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/RawType.java @@ -469,21 +469,20 @@ public String guessFormattedValue() throws SchemaException { @Override public void accept(JaxbVisitor visitor) { - visitor.visit(this); - if (isParsed()) { - Object realValue = parsed.getRealValue(); - if (realValue instanceof JaxbVisitable) { - ((JaxbVisitable) realValue).accept(visitor); - } - } else if (explicitTypeName != null) { + Object value; + if (isParsed() || explicitTypeName != null) { + // (Potentially) parsing the value before visiting it. try { - Object value = getValue(true); - if (value instanceof JaxbVisitable) { - ((JaxbVisitable) value).accept(visitor); - } + value = getValue(true); } catch (SchemaException e) { throw new TunnelException(e); } + } else { + value = null; + } + visitor.visit(this); + if (value instanceof JaxbVisitable) { + ((JaxbVisitable) value).accept(visitor); } } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java index cc7aea49c92..d1646392eff 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismReferenceValueImpl.java @@ -710,4 +710,13 @@ public void shortDump(StringBuilder sb) { } } } + + @Override + public void accept(Visitor visitor) { + super.accept(visitor); + if (object != null) { + //noinspection unchecked + object.accept(visitor); + } + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java index 90a452cbc8f..f92e5ac09c3 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java @@ -801,8 +801,8 @@ private void computeParamTypeFromSetter(String propName, Class setterParamTyp private void computeParamTypeFromGetter(String propName, Class getterReturnType) throws SchemaException { if (!Collection.class.isAssignableFrom(getterReturnType)) { - throw new SchemaException("Cannot find getter for field " + actualPropertyName + " in " + beanClass - + " does not return collection, cannot use it to set value"); + throw new SchemaException("Cannot find setter for field " + actualPropertyName + " in " + beanClass + + ". The getter was found, but it does not return collection - so it cannot be used to set the value."); } // getter.genericReturnType = Collection<...> Type typeArgument = inspector.getTypeArgument(getter.getGenericReturnType(), diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismParserImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismParserImpl.java index 708c49e3728..ea213d9ada3 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismParserImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismParserImpl.java @@ -245,7 +245,11 @@ private T doParseRealValue(Class clazz, RootXNodeImpl root) throws IOExce } // although bean unmarshaller can process containerables as well, prism unmarshaller is better at it - if (clazz != null && !Containerable.class.isAssignableFrom(clazz) && getBeanUnmarshaller().canProcess(clazz)) { + // for referencables, bean unmarshaller cannot parse embedded objects - so we use doParseItemValue instead as well + if (clazz != null + && !Referencable.class.isAssignableFrom(clazz) + && !Containerable.class.isAssignableFrom(clazz) + && getBeanUnmarshaller().canProcess(clazz)) { return getBeanUnmarshaller().unmarshal(root, clazz, context); } else if (clazz != null && Objectable.class.isAssignableFrom(clazz)) { // we need to NOT strip off OID diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/MapXNodeImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/MapXNodeImpl.java index 0490127c398..2707d9aaad6 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/MapXNodeImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/MapXNodeImpl.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.prism.impl.xnode; import java.util.*; +import java.util.stream.Collectors; import javax.xml.namespace.QName; @@ -271,12 +272,12 @@ public String getDesc() { @Override public String toString() { - StringBuilder sb = new StringBuilder("XNode(map:"+subnodes.size()+" entries)"); + StringBuilder sb = new StringBuilder("XNode(map:"+subnodes.size()+" entries) {"); sb.append("\n"); - subnodes.entrySet().forEach(entry -> { - sb.append(entry.toString()); - sb.append("; \n"); - }); + sb.append(subnodes.entrySet().stream() + .map(Object::toString) + .collect(Collectors.joining(";\n"))); + sb.append(" }"); return sb.toString(); } diff --git a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/PrismInternalTestUtil.java b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/PrismInternalTestUtil.java index 8014277a4b4..8f737de9b6c 100644 --- a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/PrismInternalTestUtil.java +++ b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/PrismInternalTestUtil.java @@ -270,7 +270,7 @@ public static void assertVisitor(Visitable visitable, int expectedVisits) { @Override public void visit(Visitable visitable) { visits.add(visitable); - System.out.println("Visiting: "+visitable); + System.out.println("#" + visits.size() + ": Visiting: "+visitable); } }; visitable.accept(visitor); @@ -281,7 +281,7 @@ public static void assertPathVisitor(PathVisitable visitable, final ItemPath pat final List visits = new ArrayList<>(); Visitor visitor = visitable1 -> { visits.add(visitable1); - System.out.println("Visiting(path="+path+",recursive="+recursive+"): "+ visitable1); + System.out.println("#" + visits.size() + ": Visiting(path="+path+",recursive="+recursive+"): "+ visitable1); }; visitable.accept(visitor, path, recursive); assertEquals("Wrong number of visits for path "+path, expectedVisits, visits.size()); @@ -296,7 +296,7 @@ public static void assertUserJack(PrismObject user, boolean expectRawI user.assertDefinitions("test"); assertUserJackContent(user, expectRawInConstructions, expectFullPolyName, withIncomplete); assertUserJackExtension(user, withIncomplete); - assertVisitor(user, 71); + assertVisitor(user, 75); assertPathVisitor(user, UserType.F_ASSIGNMENT, true, 9); assertPathVisitor(user, ItemPath.create(UserType.F_ASSIGNMENT, USER_ASSIGNMENT_1_ID), true, 3); diff --git a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/TestPrismParsing.java b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/TestPrismParsing.java index b94e3878088..454713233a4 100644 --- a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/TestPrismParsing.java +++ b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/TestPrismParsing.java @@ -150,7 +150,9 @@ private void roundTrip(File file, boolean expectFullPolyName, boolean withIncomp // WHEN // We need to serialize with composite objects during roundtrip, otherwise the result will not be equal - String userXml = prismContext.serializerFor(getOutputFormat()).serialize(originalUser); + String userXml = prismContext.serializerFor(getOutputFormat()) + .options(SerializationOptions.createSerializeCompositeObjects()) + .serialize(originalUser); // THEN System.out.println("Serialized user:"); @@ -193,7 +195,9 @@ private void roundTripAdhoc(File file) throws SchemaException, IOException { // WHEN // We need to serialize with composite objects during roundtrip, otherwise the result will not be equal - String userXml = prismContext.serializeObjectToString(originalUser, getOutputFormat()); + String userXml = prismContext.serializerFor(getOutputFormat()) + .options(SerializationOptions.createSerializeCompositeObjects()) + .serialize(originalUser); // THEN System.out.println("Serialized user:"); @@ -386,7 +390,7 @@ protected void assertUserAdhoc(PrismObject user, boolean expectRawInCo user.checkConsistence(); assertUserJackContent(user, expectRawInConstructions, true, withIncomplete); assertUserExtensionAdhoc(user); - assertVisitor(user, 58); + assertVisitor(user, 62); } private void assertUserExtensionAdhoc(PrismObject user) { diff --git a/infra/prism-impl/src/test/resources/common/json/user-jack-adhoc.json b/infra/prism-impl/src/test/resources/common/json/user-jack-adhoc.json index 0f7901002d7..2b3d40f86db 100644 --- a/infra/prism-impl/src/test/resources/common/json/user-jack-adhoc.json +++ b/infra/prism-impl/src/test/resources/common/json/user-jack-adhoc.json @@ -66,8 +66,14 @@ } }, "accountRef" : [ { - "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111", - "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113", + "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType", + "object" : { + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113", + "name" : "jsparrow" + } + }, { + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" }, { "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1112", "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType", diff --git a/infra/prism-impl/src/test/resources/common/json/user-jack-object.json b/infra/prism-impl/src/test/resources/common/json/user-jack-object.json index 52dcd88bac8..69443661829 100644 --- a/infra/prism-impl/src/test/resources/common/json/user-jack-object.json +++ b/infra/prism-impl/src/test/resources/common/json/user-jack-object.json @@ -66,8 +66,14 @@ } }, "accountRef" : [ { - "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111", - "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113", + "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType", + "object" : { + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113", + "name" : "jsparrow" + } + }, { + "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" }, { "oid" : "c0c010c0-d34d-b33f-f00d-aaaaaaaa1112", "type" : "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType", diff --git a/infra/prism-impl/src/test/resources/common/yaml/user-jack-adhoc.yaml b/infra/prism-impl/src/test/resources/common/yaml/user-jack-adhoc.yaml index c445e6ea717..44621c94f8e 100644 --- a/infra/prism-impl/src/test/resources/common/yaml/user-jack-adhoc.yaml +++ b/infra/prism-impl/src/test/resources/common/yaml/user-jack-adhoc.yaml @@ -53,8 +53,12 @@ user: cipherData: cipherValue: "blc5OXO2Z4vJW7o/XXhqZzg/rkwsIOwRBK7KLgMqwcrVcYpeZZOjxzgRgFiNw4IB" accountRef: - - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" + - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113" type: "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" + object: + oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113" + name: "jsparrow" + - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" # deliberately without specifying the type, as it should be taken from the definition - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1112" type: "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" description: "This is a reference with a filter" diff --git a/infra/prism-impl/src/test/resources/common/yaml/user-jack-object.yaml b/infra/prism-impl/src/test/resources/common/yaml/user-jack-object.yaml index 8890b24262f..67f6d5560ef 100644 --- a/infra/prism-impl/src/test/resources/common/yaml/user-jack-object.yaml +++ b/infra/prism-impl/src/test/resources/common/yaml/user-jack-object.yaml @@ -52,8 +52,12 @@ object: !http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd/UserType cipherData: cipherValue: "blc5OXO2Z4vJW7o/XXhqZzg/rkwsIOwRBK7KLgMqwcrVcYpeZZOjxzgRgFiNw4IB" accountRef: - - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" + - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113" type: "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" + object: + oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1113" + name: "jsparrow" + - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1111" # deliberately without specifying the type, as it should be taken from the definition - oid: "c0c010c0-d34d-b33f-f00d-aaaaaaaa1112" type: "http://midpoint.evolveum.com/xml/ns/test/foo-1.xsd#AccountType" description: "This is a reference with a filter"