diff --git a/build-system/pom.xml b/build-system/pom.xml index 577f77e18f8..9c2d2a47047 100644 --- a/build-system/pom.xml +++ b/build-system/pom.xml @@ -662,12 +662,12 @@ com.evolveum.polygon connector-csv - 2.1-SNAPSHOT + 2.1 com.evolveum.polygon connector-ldap - 1.5-SNAPSHOT + 1.5 xml-apis diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentDetailsPanel.java index c69f134d0ce..bfa1bb71353 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentDetailsPanel.java @@ -20,6 +20,9 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.web.component.prism.*; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.list.ListItem; @@ -77,7 +80,7 @@ protected void initLayout(){ @Override public C getObject() { AssignmentType assignment = getModelObject().getContainerValue().getValue(); - if (AssignmentsUtil.isAssignableObject(assignment)) { + if (assignment.getTargetRef() != null) { Task task = getPageBase().createSimpleTask("Load target"); com.evolveum.midpoint.schema.result.OperationResult result = task.getResult(); return (C) WebModelServiceUtils.loadObject(assignment.getTargetRef(), getPageBase(), task, result).asObjectable(); @@ -103,13 +106,17 @@ public C getObject() { PageAdminObjectDetails pageBase = (PageAdminObjectDetails)getPageBase(); - ItemPath assignmentPath = getAssignmentPath(); + ItemPath assignmentPath = getModelObject().getPath(); // ContainerValueWrapperFromObjectWrapperModel assignmentModel = // new ContainerValueWrapperFromObjectWrapperModel(pageBase.getObjectModel(), assignmentPath); Form form = new Form<>("form"); + + ContainerValueWrapper containerWrapper = getModelObject(); + if (containerWrapper == null){} - ContainerValuePanel assignmentPanel = new ContainerValuePanel("basic", getModel(), true, form, itemWrapper -> getAssignmentBasicTabVisibity(itemWrapper, assignmentPath), pageBase); + ContainerValuePanel assignmentPanel = new ContainerValuePanel("basic", getModel(), true, form, + itemWrapper -> getAssignmentBasicTabVisibity(itemWrapper, assignmentPath), pageBase); add(assignmentPanel); @@ -173,6 +180,4 @@ protected IModel getAdditionalNameLabelStyleClass(){ return Model.of(""); } - - } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java index c05507ce74d..c8503005763 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java @@ -23,11 +23,10 @@ import javax.xml.namespace.QName; import javax.xml.validation.Schema; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.web.component.prism.ContainerValueWrapper; -import com.evolveum.midpoint.web.component.prism.ContainerWrapper; -import com.evolveum.midpoint.web.component.prism.ContainerWrapperFactory; -import com.evolveum.midpoint.web.component.prism.ValueStatus; +import com.evolveum.midpoint.web.component.prism.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -63,6 +62,7 @@ import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; +import org.springframework.expression.spel.ast.Assign; /** * Created by honchar. @@ -75,9 +75,8 @@ public class AbstractRoleAssignmentPanel extends AssignmentPanel { private static final String ID_SHOW_ALL_ASSIGNMENTS_BUTTON = "showAllAssignmentsButton"; - public AbstractRoleAssignmentPanel(String id, IModel>> assignmentsModel, - ContainerWrapper assignmentContainerWrapper){ - super(id, assignmentsModel, assignmentContainerWrapper); + public AbstractRoleAssignmentPanel(String id, IModel> assignmentContainerWrapperModel){ + super(id, assignmentContainerWrapperModel); } protected void initCustomLayout(WebMarkupContainer assignmentsContainer){ @@ -152,17 +151,21 @@ protected void addSelectedAssignmentsPerformed(AjaxReques target.add(getPageBase().getFeedbackPanel()); return; } + ContainerWrapperFactory factory = new ContainerWrapperFactory(getPageBase()); for (T object : assignmentsList){ - try { - AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(object.asPrismObject(), relation); - assignment.asPrismContainerValue().applyDefinition(assignmentContainerWrapper.getItem().getDefinition(), false); - ContainerValueWrapper newAssignmentContainerWrapper = assignmentContainerWrapper.createItem(false); - newAssignmentContainerWrapper.setStatus(ValueStatus.ADDED); - newAssignmentContainerWrapper.getContainerValue().getValue().setupContainerValue(assignment.asPrismContainerValue()); - getModelObject().add(0, newAssignmentContainerWrapper); - } catch (SchemaException ex){ + ObjectReferenceType ort = new ObjectReferenceType(); + ort.setOid(object.getOid()); + ort.setRelation(SchemaConstants.ORG_DEFAULT); + ort.setType(WebComponentUtil.classToQName(getPageBase().getPrismContext(), object.getClass())); - } + + PrismContainerValue newAssignment = getModelObject().getItem().createNewValue(); + newAssignment.asContainerable().setTargetRef(ort); + + + ContainerValueWrapper valueWrapper = factory.createContainerValueWrapper(getModelObject(), newAssignment, + ValueStatus.ADDED, new ItemPath(FocusType.F_ASSIGNMENT)); + getModelObject().getValues().add(valueWrapper); } refreshTable(target); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentDataTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentDataTablePanel.java index d0305e69b56..1906f1f2892 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentDataTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentDataTablePanel.java @@ -337,7 +337,7 @@ public void onClick(AjaxRequestTarget target, IModel rowMod @Override public void populateItem(Item> cellItem, String componentId, final IModel rowModel) { - IModel activationLabelModel = AssignmentsUtil.createActivationTitleModel(rowModel,"", AssignmentDataTablePanel.this); + IModel activationLabelModel = AssignmentsUtil.createActivationTitleModel(rowModel.getObject().getActivation(),"", AssignmentDataTablePanel.this); cellItem.add(new Label(componentId, StringUtils.isEmpty(activationLabelModel.getObject()) ? createStringResource("AssignmentEditorPanel.undefined") : activationLabelModel)); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java index b4a1966290c..a2ad96af921 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java @@ -267,7 +267,7 @@ public boolean isVisible() { nameLabel.setOutputMarkupId(true); name.add(nameLabel); - Label activation = new Label(ID_ACTIVATION, AssignmentsUtil.createActivationTitleModel(getModel(), "-", AssignmentEditorPanel.this)); + Label activation = new Label(ID_ACTIVATION, AssignmentsUtil.createActivationTitleModel(getModel().getObject().getActivation(), "-", AssignmentEditorPanel.this)); headerRow.add(activation); ToggleIconButton expandButton = new ToggleIconButton(ID_EXPAND, GuiStyleConstants.CLASS_ICON_EXPAND, diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java index d65cd3136ef..0b2bd0f0305 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java @@ -19,16 +19,17 @@ import java.util.List; import java.util.stream.Collectors; +import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.web.component.prism.ContainerValueWrapper; -import com.evolveum.midpoint.web.component.prism.ContainerWrapper; -import com.evolveum.midpoint.web.component.prism.ValueStatus; -import com.evolveum.midpoint.web.component.util.SelectableBean; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.web.component.prism.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.apache.commons.lang.StringUtils; import org.apache.wicket.AttributeModifier; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -40,7 +41,6 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; -import org.apache.wicket.util.tester.WicketTester; import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.BasePanel; @@ -49,13 +49,11 @@ import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.security.api.AuthorizationConstants; -import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.AjaxIconButton; import com.evolveum.midpoint.web.component.data.BoxedTablePanel; import com.evolveum.midpoint.web.component.data.column.CheckBoxHeaderColumn; import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; -import com.evolveum.midpoint.web.component.data.column.ColumnUtils; import com.evolveum.midpoint.web.component.data.column.DoubleButtonColumn; import com.evolveum.midpoint.web.component.data.column.IconColumn; import com.evolveum.midpoint.web.component.data.column.InlineMenuButtonColumn; @@ -64,15 +62,15 @@ import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.util.AssignmentListDataProvider; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; -import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus; import com.evolveum.midpoint.web.session.AssignmentsTabStorage; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalStatusType; -public abstract class AssignmentPanel extends BasePanel>> { +import javax.xml.datatype.XMLGregorianCalendar; + +public abstract class AssignmentPanel extends BasePanel> { private static final long serialVersionUID = 1L; @@ -88,11 +86,9 @@ public abstract class AssignmentPanel extends BasePanel>> assignmentsModel, ContainerWrapper assignmentContainerWrapper) { - super(id, assignmentsModel); - this.assignmentContainerWrapper = assignmentContainerWrapper; + public AssignmentPanel(String id, IModel> assignmentContainerWrapperModel) { + super(id, assignmentContainerWrapperModel); } protected abstract void initPaging(); @@ -159,7 +155,7 @@ public boolean isVisible() { private BoxedTablePanel> initAssignmentTable() { - AssignmentListDataProvider assignmentsProvider = new AssignmentListDataProvider(this, getModel()) { + AssignmentListDataProvider assignmentsProvider = new AssignmentListDataProvider(this, new PropertyModel<>(getModel(), "values")) { private static final long serialVersionUID = 1L; @Override @@ -192,8 +188,12 @@ public int getItemsPerPage() { @Override protected Item> customizeNewRowItem(Item> item, IModel> model) { - item.add(AttributeModifier.append("class", - AssignmentsUtil.createAssignmentStatusClassModel(Model.of(model.getObject().getContainerValue().asContainerable())))); + item.add(AttributeModifier.append("class", new AbstractReadOnlyModel() { + @Override + public String getObject() { + return AssignmentsUtil.createAssignmentStatusClassModel(model.getObject()); + } + })); return item; } @@ -216,6 +216,17 @@ protected List, String>> initBasic columns.add(new CheckBoxHeaderColumn>(){ private static final long serialVersionUID = 1L; + @Override + protected boolean isTableRowSelected(ContainerValueWrapper object){ + return object.isSelected(); + } + + @Override + protected void onUpdateRow(AjaxRequestTarget target, DataTable table, IModel> rowModel) { + super.onUpdateRow(target, table, rowModel); + rowModel.getObject().setSelected(!rowModel.getObject().isSelected()); + }; + @Override protected IModel getCheckBoxValueModel(IModel> rowModel) { return Model.of(rowModel.getObject().isSelected()); @@ -247,11 +258,10 @@ public String getObject() { @Override protected IModel createLinkModel(IModel> rowModel) { String name = AssignmentsUtil.getName(rowModel.getObject().getContainerValue().asContainerable(), getParentPage()); - if (StringUtils.isBlank(name)) { - return createStringResource("AssignmentPanel.noName"); - } - return Model.of(name); - + if (StringUtils.isBlank(name)) { + return createStringResource("AssignmentPanel.noName"); + } + return Model.of(name); } @Override @@ -260,56 +270,53 @@ public void onClick(AjaxRequestTarget target, IModel>(createStringResource("AssignmentType.activation")){ + columns.add(new AbstractColumn, String>(createStringResource("AssignmentType.activation")){ private static final long serialVersionUID = 1L; - @Override - protected IModel createLinkModel(IModel> rowModel) { -// return AssignmentsUtil.createActivationTitleModelExperimental(rowModel, AssignmentPanel.this); -return Model.of(""); - } - - @Override - public void onClick(AjaxRequestTarget target, IModel> rowModel) { -// updateAssignmnetActivation(target, rowModel); - } + @Override + public void populateItem(Item>> item, String componentId, + final IModel> rowModel) { + List assignmentItems = rowModel.getObject().getItems();//ContainerValue().findContainer(AssignmentType.F_ACTIVATION); + ItemWrapper activationItem = null; + for (ItemWrapper wrapper : assignmentItems){ + if (wrapper.getPath().containsName(AssignmentType.F_ACTIVATION)){ + activationItem = wrapper; + break; + } + } + ActivationStatusType administrativeStatus = null; + XMLGregorianCalendar validFrom = null; + XMLGregorianCalendar validTo = null; + if (activationItem != null){ + List> activationsList = activationItem.getValues(); + if (activationsList != null && activationsList.size() > 0){ + List activation = activationsList.get(0).getItems(); + if (activation != null && activation.size() > 0) { + for (ItemWrapper activationProperty : activation) { + if (activationProperty.getValues() != null && activationProperty.getValues().size() > 0) { + List values = activationProperty.getValues(); + if (values.get(0).getValue() != null && values.get(0).getValue().getRealValue() != null) { + if (activationProperty.getPath().containsName(ActivationType.F_ADMINISTRATIVE_STATUS)) { + administrativeStatus = (ActivationStatusType) values.get(0).getValue().getRealValue(); + continue; + } + if (activationProperty.getPath().containsName(ActivationType.F_VALID_FROM)) { + validFrom = (XMLGregorianCalendar) values.get(0).getValue().getRealValue(); + continue; + } + if (activationProperty.getPath().containsName(ActivationType.F_VALID_TO)) { + validTo = (XMLGregorianCalendar) values.get(0).getValue().getRealValue(); + continue; + } + } + } + } + } + } + } + item.add(new Label(componentId, AssignmentsUtil.createActivationTitleModel(administrativeStatus, validFrom, validTo, AssignmentPanel.this).getObject())); + } }); - -// columns.add(new IconColumn(Model.of("")){ -// private static final long serialVersionUID = 1L; -// -// @Override -// protected IModel createIconModel(IModel rowModel) { -// if (AssignmentsUtil.getType(rowModel.getObject().getAssignment()) == null){ -// return Model.of(""); -// } -// return Model.of(AssignmentsUtil.getType(rowModel.getObject().getAssignment()).getIconCssClass()); -// } -// -// @Override -// protected IModel createTitleModel(IModel rowModel) { -// return AssignmentsUtil.createAssignmentIconTitleModel(AbstractRoleAssignmentPanel.this, AssignmentsUtil.getType(rowModel.getObject().getAssignment())); -// } -// -// }); -// -// columns.add(new IconColumn(Model.of("")){ -// private static final long serialVersionUID = 1L; -// -// @Override -// protected IModel createIconModel(IModel rowModel) { -// return Model.of(GuiStyleConstants.CLASS_POLICY_RULES); -// } -// -// @Override -// protected IModel createTitleModel(IModel rowModel) { -// return createStringResource("PolicyRulesPanel.imageTitle"); -// } -// -// }); - - - columns.addAll(initColumns()); return columns; } @@ -455,14 +462,14 @@ public void onClick(AjaxRequestTarget target) { protected void assignmentDetailsPerformed(AjaxRequestTarget target, IModel> rowModel) { assignmentDetailsVisible = true; - getModelObject().forEach(a -> a.setSelected(false)); +// getModelObject().forEach(a -> a.setSelected(false)); rowModel.getObject().setSelected(true); target.add(AssignmentPanel.this); } protected void assignmentDetailsPerformed(AjaxRequestTarget target, List> rowModel) { assignmentDetailsVisible = true; - getModelObject().forEach(a -> a.setSelected(false)); +// getModelObject().forEach(a -> a.setSelected(false)); rowModel.stream().forEach(a -> a.setSelected(true)); target.add(AssignmentPanel.this); } @@ -479,7 +486,7 @@ protected void deleteAssignmentPerformed(AjaxRequestTarget target, List assignmentContainerWrapper : getModelObject()){ + for (ContainerValueWrapper assignmentContainerWrapper : getModelObject().getValues()){ if (toDelete.contains(assignmentContainerWrapper)){ assignmentContainerWrapper.setStatus(ValueStatus.DELETED); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentsUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentsUtil.java index 81c890ede1b..2345138304a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentsUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentsUtil.java @@ -8,6 +8,7 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.web.component.prism.ContainerValueWrapper; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -29,6 +30,7 @@ import com.evolveum.midpoint.web.component.prism.InputPanel; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus; +import org.exolab.castor.dsml.XML; /** * Created by honchar. @@ -38,32 +40,33 @@ public class AssignmentsUtil { private static final Trace LOGGER = TraceManager.getTrace(AssignmentsUtil.class); - public static IModel createActivationTitleModel(IModel model, String defaultTitle, BasePanel basePanel) { + public static IModel createActivationTitleModel(ActivationType activation, String defaultTitle, BasePanel basePanel) { + if (activation == null) { + return Model.of(""); + } + return createActivationTitleModel(activation.getAdministrativeStatus(), activation.getValidFrom(), activation.getValidTo(), basePanel); + } + + public static IModel createActivationTitleModel(ActivationStatusType administrativeStatus, XMLGregorianCalendar validFrom, XMLGregorianCalendar validTo, + BasePanel basePanel) { return new AbstractReadOnlyModel() { private static final long serialVersionUID = 1L; @Override public String getObject() { - AssignmentEditorDto dto = model.getObject(); - ActivationType activation = dto.getActivation(); - if (activation == null) { - return defaultTitle; - } - - ActivationStatusType status = activation.getAdministrativeStatus(); - String strEnabled = basePanel.createStringResource(status, "lower", "ActivationStatusType.null") + String strEnabled = basePanel.createStringResource(administrativeStatus, "lower", "ActivationStatusType.null") .getString(); - if (activation.getValidFrom() != null && activation.getValidTo() != null) { + if (validFrom != null && validTo != null) { return basePanel.getString("AssignmentEditorPanel.enabledFromTo", strEnabled, - MiscUtil.asDate(activation.getValidFrom()), - MiscUtil.asDate(activation.getValidTo())); - } else if (activation.getValidFrom() != null) { + MiscUtil.asDate(validFrom), + MiscUtil.asDate(validTo)); + } else if (validFrom != null) { return basePanel.getString("AssignmentEditorPanel.enabledFrom", strEnabled, - MiscUtil.asDate(activation.getValidFrom())); - } else if (activation.getValidTo() != null) { + MiscUtil.asDate(validFrom)); + } else if (validTo != null) { return basePanel.getString("AssignmentEditorPanel.enabledTo", strEnabled, - MiscUtil.asDate(activation.getValidTo())); + MiscUtil.asDate(validTo)); } return strEnabled; @@ -75,10 +78,10 @@ public static IModel createActivationTitleModelExperimental(IModel s.value(), basePanel); } - public static IModel createActivationTitleModelExperimental(AssignmentType model, Function transformStatusLambda, BasePanel basePanel) { + public static IModel createActivationTitleModelExperimental(AssignmentType assignmentType, Function transformStatusLambda, BasePanel basePanel) { // AssignmentDto assignmentDto = model.getObject(); - ActivationType activation = model.getActivation(); + ActivationType activation = assignmentType.getActivation(); if (activation == null) { return basePanel.createStringResource("lower.ActivationStatusType.null"); } @@ -159,30 +162,8 @@ public void setObject(Date object) { }; } - // public static IModel createAssignmentStatusClassModel(final IModel model) { -// return new AbstractReadOnlyModel() { -// private static final long serialVersionUID = 1L; -// -// @Override -// public String getObject() { -// AssignmentEditorDto dto = model.getObject(); -// return dto.getStatus().name().toLowerCase(); -// } -// }; -// } - - public static IModel createAssignmentStatusClassModel(final IModel model) { - return new AbstractReadOnlyModel() { - private static final long serialVersionUID = 1L; - - @Override - public String getObject() { - //TODO fix -// AssignmentType dto = model.getObject(); -// return dto.getStatus().name().toLowerCase(); - return ""; - } - }; + public static String createAssignmentStatusClassModel(final ContainerValueWrapper assignment) { + return assignment.getStatus().name().toLowerCase(); } public static IModel createAssignmentStatusClassModel(final UserDtoStatus model) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/GdprAssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/GdprAssignmentPanel.java index 602b388f518..efa4ae33b4c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/GdprAssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/GdprAssignmentPanel.java @@ -43,8 +43,8 @@ public class GdprAssignmentPanel extends AbstractRoleAssignmentPanel { private static final long serialVersionUID = 1L; - public GdprAssignmentPanel(String id, IModel>> assignmentsModel, ContainerWrapper assignmentContainerWrapper) { - super(id, assignmentsModel, assignmentContainerWrapper); + public GdprAssignmentPanel(String id, IModel> assignmentContainerWrapperModel) { + super(id, assignmentContainerWrapperModel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java index 9c04764eed4..f287890f6ac 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java @@ -18,11 +18,16 @@ import java.util.ArrayList; import java.util.List; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.web.component.prism.ContainerValueWrapper; import com.evolveum.midpoint.web.component.prism.ContainerWrapper; +import com.evolveum.midpoint.web.component.prism.ContainerWrapperFactory; import com.evolveum.midpoint.web.component.prism.ValueStatus; import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; @@ -47,10 +52,6 @@ import com.evolveum.midpoint.web.session.AssignmentsTabStorage; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyRuleType; /** * Created by honchar. @@ -61,8 +62,8 @@ public class PolicyRulesPanel extends AssignmentPanel { private static final long serialVersionUID = 1L; - public PolicyRulesPanel(String id, IModel>> policyRulesModel, ContainerWrapper assignmentContainerWrapper){ - super(id, policyRulesModel, assignmentContainerWrapper); + public PolicyRulesPanel(String id, IModel> assignmentContainerWrapperModel){ + super(id, assignmentContainerWrapperModel); } @@ -145,25 +146,25 @@ private AssignmentsTabStorage getPolicyRulesTabStorage(){ @Override protected void newAssignmentClickPerformed(AjaxRequestTarget target) { - // TODO Auto-generated method stub + ContainerWrapperFactory factory = new ContainerWrapperFactory(getPageBase()); - AssignmentType assignment = new AssignmentType(); + PrismContainerValue newAssignment = getModelObject().getItem().createNewValue(); + newAssignment.asContainerable().setPolicyRule(new PolicyRuleType()); - PolicyRuleType policyRule = new PolicyRuleType(getPageBase().getPrismContext()); - policyRule.setDescription(""); - assignment.setPolicyRule(policyRule); - ContainerValueWrapper newAssignmentContainerWrapper = assignmentContainerWrapper.createItem(false); - newAssignmentContainerWrapper.setStatus(ValueStatus.ADDED); - newAssignmentContainerWrapper.getContainerValue().getValue().setupContainerValue(assignment.asPrismContainerValue()); - getModelObject().add(newAssignmentContainerWrapper); - target.add(getAssignmentContainer()); + ContainerValueWrapper valueWrapper = factory.createContainerValueWrapper(getModelObject(), newAssignment, + ValueStatus.ADDED, new ItemPath(FocusType.F_ASSIGNMENT)); + getModelObject().getValues().add(valueWrapper); + + refreshTable(target); } @Override protected ObjectQuery createObjectQuery() { - return null; - } + return QueryBuilder.queryFor(AssignmentType.class, getParentPage().getPrismContext()) + .exists(AssignmentType.F_POLICY_RULE) + .build(); + } @Override protected AbstractAssignmentDetailsPanel createDetailsPanel(String idAssignmentDetails, Form form, IModel> model) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/CheckBoxHeaderColumn.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/CheckBoxHeaderColumn.java index 631e6bd947e..868de5cddce 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/CheckBoxHeaderColumn.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/CheckBoxHeaderColumn.java @@ -124,7 +124,7 @@ protected void onUpdateHeader(AjaxRequestTarget target, boolean selected, DataTa } } - public static boolean shouldBeHeaderSelected(DataTable table) { + public boolean shouldBeHeaderSelected(DataTable table) { boolean selectedAll = true; BaseSortableDataProvider baseProvider = (BaseSortableDataProvider) table.getDataProvider(); @@ -134,15 +134,20 @@ public static boolean shouldBeHeaderSelected(DataTable table) { } for (T object : objects) { - if (object instanceof Selectable) { - Selectable selectable = (Selectable) object; - selectedAll &= selectable.isSelected(); - } + selectedAll &= isTableRowSelected(object); } return selectedAll; } + protected boolean isTableRowSelected(T object){ + if (object instanceof Selectable) { + Selectable selectable = (Selectable) object; + return selectable.isSelected(); + } + return false; + } + /** * This method is called after checkbox in row is updated */ @@ -158,7 +163,7 @@ protected void onUpdateRow(AjaxRequestTarget target, DataTable table, IModel target.add(header); } - public static CheckBoxPanel findCheckBoxColumnHeader(DataTable table) { + public CheckBoxPanel findCheckBoxColumnHeader(DataTable table) { WebMarkupContainer topToolbars = table.getTopToolbars(); ComponentHierarchyIterator iterator = topToolbars.visitChildren(TableHeadersToolbar.class); if (!iterator.hasNext()) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusAssignmentsTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusAssignmentsTabPanel.java index 63c95707521..23eedf63d81 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusAssignmentsTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusAssignmentsTabPanel.java @@ -29,6 +29,7 @@ import org.apache.wicket.Component; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; @@ -65,20 +66,42 @@ private void initLayout() { assignments.setOutputMarkupId(true); add(assignments); - AbstractRoleAssignmentPanel panel = new AbstractRoleAssignmentPanel(ID_ASSIGNMENTS_PANEL, getAssignmentsListModel(assignmentsContainerWrapper), - assignmentsContainerWrapper); + AbstractRoleAssignmentPanel panel = new AbstractRoleAssignmentPanel(ID_ASSIGNMENTS_PANEL, Model.of(assignmentsContainerWrapper)); + +// new AbstractReadOnlyModel>() { +// @Override +// public ContainerWrapper getObject() { +// return assignmentsContainerWrapper; +// } +// }); assignments.add(panel); } private IModel>> getAssignmentsListModel(ContainerWrapper assignmentsContainerWrapper){ - List> assignmentsList = new ArrayList<>(); - assignmentsContainerWrapper.getValues().forEach(a -> { - if (!AssignmentsUtil.isPolicyRuleAssignment(a.getContainerValue().getValue()) && !AssignmentsUtil.isConsentAssignment(a.getContainerValue().getValue()) - && AssignmentsUtil.isAssignmentRelevant(a.getContainerValue().getValue())) { - assignmentsList.add(a); - } - }); + return new IModel>>() { + private static final long serialVersionUID = 1L; + + @Override + public List> getObject() { + List> assignmentsList = new ArrayList<>(); + assignmentsContainerWrapper.getValues().forEach(a -> { + if (!AssignmentsUtil.isPolicyRuleAssignment(a.getContainerValue().getValue()) && !AssignmentsUtil.isConsentAssignment(a.getContainerValue().getValue()) + && AssignmentsUtil.isAssignmentRelevant(a.getContainerValue().getValue())) { + assignmentsList.add(a); + } + }); // Collections.sort(consentsList); - return Model.ofList(assignmentsList); + return assignmentsList; + } + + @Override + public void setObject(List> object){ + assignmentsContainerWrapper.getValues().clear(); + assignmentsContainerWrapper.getValues().addAll(object); + } + + @Override + public void detach(){}; + }; } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusConsentTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusConsentTabPanel.java index e346967b42c..33b135b7ec9 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusConsentTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusConsentTabPanel.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.web.component.prism.ObjectWrapper; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; @@ -38,7 +39,12 @@ public FocusConsentTabPanel(String id, Form> mainForm, Loadable private void initLayout() { ContainerWrapper assignmentsContainerWrapper = getObjectWrapper().findContainerWrapper(new ItemPath(FocusType.F_ASSIGNMENT)); - GdprAssignmentPanel consentRoles = new GdprAssignmentPanel(ID_ROLES, getConsentsModel(assignmentsContainerWrapper), assignmentsContainerWrapper); + GdprAssignmentPanel consentRoles = new GdprAssignmentPanel(ID_ROLES, new AbstractReadOnlyModel>() { + @Override + public ContainerWrapper getObject() { + return assignmentsContainerWrapper; + } + }); add(consentRoles); consentRoles.setOutputMarkupId(true); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusMainPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusMainPanel.java index 2fd5b13ec8a..d4249fe7fb3 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusMainPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusMainPanel.java @@ -161,7 +161,7 @@ protected WebMarkupContainer createTabPanel(String panelId, FormSpecificationTyp if (AbstractFocusTabPanel.class.isAssignableFrom(panelClass)) { Constructor constructor; try { - constructor = panelClass.getConstructor(String.class, Form.class, LoadableModel.class, LoadableModel.class, LoadableModel.class, PageBase.class); + constructor = panelClass.getConstructor(String.class, Form.class, LoadableModel.class, LoadableModel.class, PageBase.class); } catch (NoSuchMethodException | SecurityException e) { throw new SystemException("Unable to locate constructor (String,Form,LoadableModel,LoadableModel,LoadableModel,PageBase) in "+panelClass+": "+e.getMessage(), e); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPolicyRulesTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPolicyRulesTabPanel.java index d0d697990ff..a96cdde1768 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPolicyRulesTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPolicyRulesTabPanel.java @@ -37,6 +37,7 @@ import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; @@ -71,7 +72,12 @@ private void initLayout() { policyRules.setOutputMarkupId(true); add(policyRules); - PolicyRulesPanel policyRulesPanel = new PolicyRulesPanel(ID_POLICY_RULES_PANEL, getPolicyRulesModel(assignmentsContainerWrapper), assignmentsContainerWrapper); + PolicyRulesPanel policyRulesPanel = new PolicyRulesPanel(ID_POLICY_RULES_PANEL, new AbstractReadOnlyModel>() { + @Override + public ContainerWrapper getObject() { + return assignmentsContainerWrapper; + } + }); policyRules.add(policyRulesPanel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapper.java index e57b9a2f5a5..72417cfb036 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapper.java @@ -416,35 +416,17 @@ public void collectDeleteDelta(ObjectDelta delta, Pri } } - public void collectAddDelta(ObjectDelta delta, ItemPath propertyPath, - PrismContainerDefinition def, PrismContext prismContext) throws SchemaException { - - ContainerDelta containerDelta = new ContainerDelta(propertyPath, def.getName(), def, prismContext); - + public void collectAddDelta(ObjectDelta delta, PrismContext prismContext) throws SchemaException { for (ContainerValueWrapper itemWrapper : getValues()) { if (ValueStatus.ADDED.equals(itemWrapper.getStatus())) { - itemWrapper.getContainerValue().applyDefinition(def, false); + ContainerDelta containerDelta = new ContainerDelta(ItemPath.EMPTY_PATH, itemWrapper.getDefinition().getName(), + itemWrapper.getDefinition(), prismContext); containerDelta.addValueToAdd(itemWrapper.getContainerValue().clone()); - + if (!containerDelta.isEmpty()){ + delta.addModification(containerDelta); + } } } -// ContainerDelta containerDelta = delta.createContainerModification(propertyPath); -// -// List containerValuesToAdd = new ArrayList<>(); -// -// for (ContainerValueWrapper itemWrapper : getValues()) { -// if (ValueStatus.ADDED.equals(itemWrapper.getStatus())){ -// containerValuesToAdd.add(itemWrapper.getContainerValue().clone()); - -// ContainerDelta containerDelta = new ContainerDelta(new ItemPath(FocusType.F_ASSIGNMENT), itemWrapper.getDefinition().getName(), -// itemWrapper.getDefinition(), prismContext); -// containerDelta.addValuesToAdd(itemWrapper.getContainerValue().clone()); -// } -// } -// containerDelta.addValuesToAdd(containerValuesToAdd.toArray(new PrismContainerValue[containerValuesToAdd.size()])); - if (!containerDelta.isEmpty()){ - delta.addModification(containerDelta); - } } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapperFactory.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapperFactory.java index 6813c736018..9e265c6909b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapperFactory.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ContainerWrapperFactory.java @@ -116,7 +116,7 @@ private List> createContainer List> containerValueWrappers = new ArrayList<>(); PrismContainer container = cWrapper.getItem(); - if (container.isEmpty()) { + if (container.getValues().isEmpty()) { PrismContainerValue pcv = container.createNewValue(); ContainerValueWrapper containerValueWrapper = createContainerValueWrapper(cWrapper, pcv, ValueStatus.ADDED, cWrapper.getPath()); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapper.java index 4afe877283b..375b8f99053 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapper.java @@ -344,8 +344,7 @@ public ObjectDelta getObjectDelta() throws SchemaException { for (ContainerWrapper containerWrapper : getContainers()) { containerWrapper.collectModifications(delta); containerWrapper.collectDeleteDelta(delta, object.getPrismContext()); -// containerWrapper.collectAddDelta(delta, new ItemPath(FocusType.F_ASSIGNMENT), object.getDefinition().findContainerDefinition(UserType.F_ASSIGNMENT), -// object.getPrismContext()); + containerWrapper.collectAddDelta(delta, object.getPrismContext()); } // returning container to previous order Collections.sort(containers, new ItemWrapperComparator()); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/AssignmentListDataProvider.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/AssignmentListDataProvider.java index c0e59ae9c83..514cfc95eae 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/AssignmentListDataProvider.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/AssignmentListDataProvider.java @@ -17,6 +17,7 @@ package com.evolveum.midpoint.web.component.util; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.util.exception.SchemaException; diff --git a/gui/admin-gui/src/main/webapp/less/admin-lte/less/table.less b/gui/admin-gui/src/main/webapp/less/admin-lte/less/table.less index 339fa4698a3..0e5dd7c7dad 100644 --- a/gui/admin-gui/src/main/webapp/less/admin-lte/less/table.less +++ b/gui/admin-gui/src/main/webapp/less/admin-lte/less/table.less @@ -37,13 +37,13 @@ border: 1px solid @box-border-color; } } - > tr.delete { + > tr.delete, tr.deleted { background-color: @brand-danger; a { color: #FFFFFF !important;; } } - > tr.add { + > tr.add, tr.added { background-color: @brand-success; a { color: #FFFFFF !important;; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/Item.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/Item.java index 4a92db4d7b4..ae13b4482ef 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/Item.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/Item.java @@ -438,7 +438,7 @@ public boolean add(@NotNull V newValue, boolean checkUniqueness) throws SchemaEx } D definition = getDefinition(); if (definition != null) { - if (!isEmpty() && definition.isSingleValue()) { + if (!values.isEmpty() && definition.isSingleValue()) { throw new SchemaException("Attempt to put more than one value to single-valued item " + this + "; newly added value: " + newValue); } newValue.applyDefinition(definition, false); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java index b3d8f436e42..a69fcb94923 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java @@ -33,6 +33,7 @@ import com.evolveum.midpoint.prism.path.ItemPath.CompareResult; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; +import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.Foreachable; @@ -1991,4 +1992,20 @@ public void setOriginTypeRecursive(final OriginType originType) { } }); } + + // TODO move to Item + public static ItemDelta createAddDeltaFor(Item item) { + ItemDelta rv = item.createDelta(item.getPath()); + rv.addValuesToAdd(item.getClonedValues()); + return rv; + } + + // TODO move to Item + @SuppressWarnings("unchecked") + public static ItemDelta createAddDeltaFor(Item item, PrismValue value) { + ItemDelta rv = item.createDelta(item.getPath()); + rv.addValueToAdd((V) CloneUtil.clone(value)); + return rv; + } + } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index 27101dac28e..80eb9c9614b 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -26,11 +26,14 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaType; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; -import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -38,6 +41,8 @@ import java.util.*; import java.util.stream.Collectors; +import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; + /** * Relative difference (delta) of the object. *

@@ -56,6 +61,8 @@ */ public class ObjectDelta implements DebugDumpable, Visitable, PathVisitable, Serializable { + private static final Trace LOGGER = TraceManager.getTrace(ObjectDelta.class); + private static final long serialVersionUID = -528560467958335366L; private ChangeType changeType; @@ -1617,6 +1624,173 @@ public ObjectDelta subtract(@NotNull Collection paths) { return rv; } + public static class FactorOutResult { + public final ObjectDelta remainder; + public final List> offsprings = new ArrayList<>(); + + public FactorOutResult(ObjectDelta remainder) { + this.remainder = remainder; + } + } + + public FactorOutResult factorOut(Collection paths, boolean cloneDelta) { + if (isAdd()) { + return factorOutForAddDelta(paths, cloneDelta); + } else if (isDelete()) { + throw new UnsupportedOperationException("factorOut is not supported for delete deltas"); + } else { + return factorOutForModifyDelta(paths, cloneDelta); + } + } + + public FactorOutResult factorOutValues(ItemPath path, boolean cloneDelta) throws SchemaException { + if (isAdd()) { + return factorOutValuesForAddDelta(path, cloneDelta); + } else if (isDelete()) { + throw new UnsupportedOperationException("factorOut is not supported for delete deltas"); + } else { + return factorOutValuesForModifyDelta(path, cloneDelta); + } + } + + /** + * Works if we are looking e.g. for modification to inducement item, + * and delta contains e.g. REPLACE(inducement[1]/validTo, "..."). + * + * Does NOT work the way around: if we are looking for modification to inducement/validTo and + * delta contains e.g. ADD(inducement, ...). In such a case we would need to do more complex processing, + * involving splitting value-to-be-added into remainder and offspring delta. It's probably doable, + * but some conditions would have to be met, e.g. inducement to be added must have an ID. + */ + private FactorOutResult factorOutForModifyDelta(Collection paths, boolean cloneDelta) { + ObjectDelta remainder = cloneIfRequested(cloneDelta); + FactorOutResult rv = new FactorOutResult<>(remainder); + List> modificationsFound = new ArrayList<>(); + for (Iterator> iterator = remainder.modifications.iterator(); iterator.hasNext(); ) { + ItemDelta modification = iterator.next(); + if (ItemPath.containsSubpathOrEquivalent(paths, modification.getPath())) { + modificationsFound.add(modification); + iterator.remove(); + } + } + if (!modificationsFound.isEmpty()) { + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + modificationsFound.forEach(offspring::addModification); + rv.offsprings.add(offspring); + } + return rv; + } + + private FactorOutResult factorOutForAddDelta(Collection paths, boolean cloneDelta) { + List> itemsFound = new ArrayList<>(); + for (ItemPath path : paths) { + Item item = objectToAdd.findItem(path); + if (item != null && !item.isEmpty()) { + itemsFound.add(item); + } + } + if (itemsFound.isEmpty()) { + return new FactorOutResult<>(this); + } + ObjectDelta remainder = cloneIfRequested(cloneDelta); + FactorOutResult rv = new FactorOutResult<>(remainder); + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + for (Item item : itemsFound) { + remainder.getObjectToAdd().remove(item); + offspring.addModification(ItemDelta.createAddDeltaFor(item)); + } + rv.offsprings.add(offspring); + return rv; + } + + private ObjectDelta cloneIfRequested(boolean cloneDelta) { + return cloneDelta ? clone() : this; + } + + /** + * Works if we are looking e.g. for modification to inducement item, + * and delta contains e.g. REPLACE(inducement[1]/validTo, "..."). + * + * Does NOT work the way around: if we are looking for modification to inducement/validTo and + * delta contains e.g. ADD(inducement, ...). In such a case we would need to do more complex processing, + * involving splitting value-to-be-added into remainder and offspring delta. It's probably doable, + * but some conditions would have to be met, e.g. inducement to be added must have an ID. + */ + private FactorOutResult factorOutValuesForModifyDelta(ItemPath path, boolean cloneDelta) throws SchemaException { + ObjectDelta remainder = cloneIfRequested(cloneDelta); + FactorOutResult rv = new FactorOutResult<>(remainder); + + MultiValuedMap> modificationsForId = new ArrayListValuedHashMap<>(); + PrismObjectDefinition objDef = objectTypeClass != null ? prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(objectTypeClass) : null; + ItemDefinition itemDef = objDef != null ? objDef.findItemDefinition(path) : null; + Boolean isSingle = itemDef != null ? itemDef.isSingleValue() : null; + if (isSingle == null) { + LOGGER.warn("Couldn't find definition for {}:{}", objectTypeClass, path); + isSingle = false; + } + for (Iterator> iterator = remainder.modifications.iterator(); iterator.hasNext(); ) { + ItemDelta modification = iterator.next(); + if (path.equivalent(modification.getPath())) { + if (modification.isReplace()) { + throw new UnsupportedOperationException("Cannot factor out values for replace item delta. Path = " + + path + ", modification = " + modification); + } + for (PrismValue prismValue : emptyIfNull(modification.getValuesToAdd())) { + //noinspection unchecked + createNewDelta(rv, modification).addValueToAdd(prismValue.clone()); + } + for (PrismValue prismValue : emptyIfNull(modification.getValuesToDelete())) { + //noinspection unchecked + createNewDelta(rv, modification).addValueToDelete(prismValue.clone()); + } + iterator.remove(); + } else if (path.isSubPath(modification.getPath())) { + // e.g. factoring inducement, having REPLACE(inducement[x]/activation/validTo, ...) or ADD(inducement[x]/activation) + ItemPath remainingPath = modification.getPath().remainder(path); + Long id = remainingPath.getFirstIdSegment() != null ? remainingPath.getFirstIdSegment().getId() : null; + modificationsForId.put(id, modification); + iterator.remove(); + } + } + if (Boolean.TRUE.equals(isSingle)) { + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + modificationsForId.values().forEach(mod -> offspring.addModification(mod)); + rv.offsprings.add(offspring); + } else { + for (Long id : modificationsForId.keySet()) { + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + modificationsForId.get(id).forEach(mod -> offspring.addModification(mod)); + rv.offsprings.add(offspring); + } + } + return rv; + } + + private ItemDelta createNewDelta(FactorOutResult rv, ItemDelta modification) + throws SchemaException { + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + ItemDelta delta = modification.getDefinition().instantiate().createDelta(modification.getPath()); + offspring.addModification(delta); + rv.offsprings.add(offspring); + return delta; + } + + private FactorOutResult factorOutValuesForAddDelta(ItemPath path, boolean cloneDelta) { + Item item = objectToAdd.findItem(path); + if (item == null || item.isEmpty()) { + return new FactorOutResult<>(this); + } + ObjectDelta remainder = cloneIfRequested(cloneDelta); + remainder.getObjectToAdd().remove(item); + FactorOutResult rv = new FactorOutResult<>(remainder); + for (PrismValue value : item.getValues()) { + ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + offspring.addModification(ItemDelta.createAddDeltaFor(item, value)); + rv.offsprings.add(offspring); + } + return rv; + } + /** * Checks if the delta tries to add (or set) a 'value' for the item identified by 'itemPath'. If yes, it removes it. * @@ -1648,8 +1822,8 @@ public static boolean subtractFromModifications(Collection getDeletedValuesFor(ItemPath itemPath) { } } } + + public void clear() { + if (isAdd()) { + setObjectToAdd(null); + } else if (isModify()) { + modifications.clear(); + } else if (isDelete()) { + // hack: convert to empty ADD delta + setChangeType(ChangeType.ADD); + setObjectToAdd(null); + setOid(null); + } else { + throw new IllegalStateException("Unsupported delta type: " + getChangeType()); + } + } } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java index 0522eefa0ca..beaf6038635 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java @@ -703,18 +703,11 @@ public static QName getName(ItemPathSegment segment) { } public static IdItemPathSegment getFirstIdSegment(ItemPath itemPath) { - ItemPathSegment first = itemPath.first(); - if (first instanceof IdItemPathSegment) { - return (IdItemPathSegment)first; - } - return null; + return itemPath != null ? itemPath.getFirstIdSegment() : null; } public static NameItemPathSegment getFirstNameSegment(ItemPath itemPath) { - if (itemPath == null) { - return null; - } - return itemPath.getFirstNameSegment(); + return itemPath != null ? itemPath.getFirstNameSegment() : null; } public NameItemPathSegment getFirstNameSegment() { @@ -728,6 +721,15 @@ public NameItemPathSegment getFirstNameSegment() { return null; } + public IdItemPathSegment getFirstIdSegment() { + ItemPathSegment first = first(); + if (first instanceof IdItemPathSegment) { + return (IdItemPathSegment)first; + } else { + return null; + } + } + public static QName getFirstName(ItemPath itemPath) { if (itemPath == null) { return null; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ExistsFilter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ExistsFilter.java index 4efe3baf77c..d843609f615 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ExistsFilter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/query/ExistsFilter.java @@ -86,8 +86,23 @@ public ExistsFilter cloneEmpty() { @Override public boolean match(PrismContainerValue value, MatchingRuleRegistry matchingRuleRegistry) throws SchemaException { - throw new UnsupportedOperationException(); - } + Item itemToFind = value.findItem(fullPath); + if (itemToFind == null || itemToFind.getValues().isEmpty()) { + return false; + } + if (!(itemToFind instanceof PrismContainer)) { + throw new SchemaException("Couldn't use exists query to search for items other than containers: " + itemToFind); + } + if (filter == null) { + return true; + } + for (PrismContainerValue pcv : ((PrismContainer) itemToFind).getValues()) { + if (filter.match(pcv, matchingRuleRegistry)) { + return true; + } + } + return false; + } @Override public void checkConsistence(boolean requireDefinitions) { diff --git a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ItemPathType.java b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ItemPathType.java index 21bee537ea6..a93953c64fc 100644 --- a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ItemPathType.java +++ b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ItemPathType.java @@ -30,6 +30,8 @@ import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; /** * @@ -166,4 +168,8 @@ public static ItemPathType asItemPathType(Object value) { throw new IllegalArgumentException("Value " + value + " is neither ItemPath nor ItemPathType."); } } + + public static List toItemPathList(List list) { + return list.stream().map(pt -> pt.getItemPath()).collect(Collectors.toList()); + } } diff --git a/infra/prism/src/test/java/com/evolveum/midpoint/prism/query/TestObjectQuery.java b/infra/prism/src/test/java/com/evolveum/midpoint/prism/query/TestObjectQuery.java index c12e4759c8e..492ae17f246 100644 --- a/infra/prism/src/test/java/com/evolveum/midpoint/prism/query/TestObjectQuery.java +++ b/infra/prism/src/test/java/com/evolveum/midpoint/prism/query/TestObjectQuery.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.prism.PrismInternalTestUtil; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismPropertyDefinitionImpl; +import com.evolveum.midpoint.prism.foo.AssignmentType; import com.evolveum.midpoint.prism.foo.UserType; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.match.MatchingRuleRegistryFactory; @@ -38,6 +39,7 @@ import java.io.IOException; import static com.evolveum.midpoint.prism.PrismInternalTestUtil.DEFAULT_NAMESPACE_PREFIX; +import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; public class TestObjectQuery { @@ -53,9 +55,9 @@ public void setupDebug() throws SchemaException, SAXException, IOException { @Test public void testMatchAndFilter() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); ObjectFilter filter = - QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_GIVEN_NAME).eq("Jack").matchingCaseIgnore() .and().item(UserType.F_FULL_NAME).contains("arr") .buildFilter(); @@ -66,8 +68,8 @@ public void testMatchAndFilter() throws Exception{ @Test public void testMatchOrFilter() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_GIVEN_NAME).eq("Jack") .or().item(UserType.F_GIVEN_NAME).eq("Jackie") .buildFilter(); @@ -77,8 +79,8 @@ public void testMatchOrFilter() throws Exception{ @Test public void testDontMatchEqualFilter() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_GIVEN_NAME).eq("Jackie") .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -87,9 +89,9 @@ public void testDontMatchEqualFilter() throws Exception{ @Test public void testMatchEqualMultivalue() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); - PrismPropertyDefinitionImpl def = new PrismPropertyDefinitionImpl(new QName("indexedString"), DOMUtil.XSD_STRING, PrismTestUtil.getPrismContext()); - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismPropertyDefinitionImpl def = new PrismPropertyDefinitionImpl(new QName("indexedString"), DOMUtil.XSD_STRING, getPrismContext()); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(new ItemPath(UserType.F_EXTENSION, "indexedString"), def).eq("alpha") .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -98,9 +100,9 @@ public void testMatchEqualMultivalue() throws Exception{ @Test public void testMatchEqualNonEmptyAgainstEmptyItem() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); // jack has no locality - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_LOCALITY).eq("some") .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -109,9 +111,9 @@ public void testMatchEqualNonEmptyAgainstEmptyItem() throws Exception{ @Test public void testMatchEqualEmptyAgainstEmptyItem() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); // jack has no locality - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_LOCALITY).isNull() .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -120,9 +122,9 @@ public void testMatchEqualEmptyAgainstEmptyItem() throws Exception{ @Test public void testMatchEqualEmptyAgainstNonEmptyItem() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); // jack has no locality - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_NAME).isNull() .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -131,11 +133,11 @@ public void testMatchEqualEmptyAgainstNonEmptyItem() throws Exception{ @Test public void testComplexMatch() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); // System.out.println("user given name" + user.asObjectable().getGivenName()); System.out.println("definition: " +user.findItem(UserType.F_FAMILY_NAME).getDefinition().debugDump()); ObjectFilter filter = - QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_FAMILY_NAME).eq("Sparrow") .and().item(UserType.F_FULL_NAME).contains("arr") .and() @@ -150,9 +152,9 @@ public void testComplexMatch() throws Exception{ @Test public void testPolystringMatchEqualFilter() throws Exception{ - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); PolyString name = new PolyString("jack", "jack"); - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_NAME).eq(name) .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); @@ -161,13 +163,56 @@ public void testPolystringMatchEqualFilter() throws Exception{ @Test // MID-4120 public void testMatchSubstringAgainstEmptyItem() throws Exception { - PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); // jack has no locality - ObjectFilter filter = QueryBuilder.queryFor(UserType.class, PrismTestUtil.getPrismContext()) + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) .item(UserType.F_LOCALITY).startsWith("C") .buildFilter(); boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); AssertJUnit.assertFalse("filter matches object, but it should not", match); } + @Test // MID-4173 + public void testExistsNegative() throws Exception { + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) + .exists(UserType.F_ASSIGNMENT) + .item(AssignmentType.F_DESCRIPTION).eq("Assignment NONE") + .buildFilter(); + boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); + AssertJUnit.assertFalse("filter matches object, but it should not", match); + } + + @Test // MID-4173 + public void testExistsPositive() throws Exception { + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) + .exists(UserType.F_ASSIGNMENT) + .item(AssignmentType.F_DESCRIPTION).eq("Assignment 2") + .buildFilter(); + boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); + AssertJUnit.assertTrue("filter does not match object, but it should", match); + } + + @Test // MID-4173 + public void testExistsAnyNegative() throws Exception { + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + user.removeContainer(UserType.F_ASSIGNMENT); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) + .exists(UserType.F_ASSIGNMENT) + .buildFilter(); + boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); + AssertJUnit.assertFalse("filter matches object, but it should not", match); + } + + @Test // MID-4173 + public void testExistsAnyPositive() throws Exception { + PrismObject user = PrismTestUtil.parseObject(PrismInternalTestUtil.USER_JACK_FILE_XML); + ObjectFilter filter = QueryBuilder.queryFor(UserType.class, getPrismContext()) + .exists(UserType.F_ASSIGNMENT) + .buildFilter(); + boolean match = ObjectQuery.match(user, filter, matchingRuleRegistry); + AssertJUnit.assertTrue("filter does not match object, but it should", match); + } + } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ObjectTreeDeltas.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ObjectTreeDeltas.java index 13ae778e813..deb7d09e6a9 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ObjectTreeDeltas.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ObjectTreeDeltas.java @@ -23,10 +23,7 @@ import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTreeDeltasType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectionObjectDeltaType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -38,9 +35,9 @@ * * @author mederly */ -public class ObjectTreeDeltas implements DebugDumpable { +public class ObjectTreeDeltas implements DebugDumpable { - private ObjectDelta focusChange; + private ObjectDelta focusChange; private Map> projectionChangeMap = new HashMap<>(); // values are non null here private PrismContext prismContext; @@ -48,12 +45,12 @@ public ObjectTreeDeltas(PrismContext prismContext) { this.prismContext = prismContext; } - public ObjectTreeDeltas(ObjectDelta focusChange, PrismContext prismContext) { + public ObjectTreeDeltas(ObjectDelta focusChange, PrismContext prismContext) { this.focusChange = focusChange; this.prismContext = prismContext; } - public ObjectDelta getFocusChange() { + public ObjectDelta getFocusChange() { return focusChange; } @@ -65,7 +62,7 @@ public Map> getProjectionCh return projectionChangeMap; } - public void setFocusChange(ObjectDelta focusChange) { + public void setFocusChange(ObjectDelta focusChange) { this.focusChange = focusChange; } @@ -105,8 +102,8 @@ public static boolean isEmpty(ObjectTreeDeltasType deltas) { return true; } - public ObjectTreeDeltas clone() { - ObjectTreeDeltas clone = new ObjectTreeDeltas<>(prismContext); + public ObjectTreeDeltas clone() { + ObjectTreeDeltas clone = new ObjectTreeDeltas<>(prismContext); if (focusChange != null) { clone.setFocusChange(focusChange.clone()); } @@ -220,7 +217,7 @@ public String debugDump(int indent) { return sb.toString(); } - public void merge(ObjectTreeDeltas deltasToMerge) throws SchemaException { + public void merge(ObjectTreeDeltas deltasToMerge) throws SchemaException { if (deltasToMerge == null) { return; } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java index 39eb96a8cf8..27dbe156f3a 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java @@ -50,7 +50,9 @@ import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PropertyReferenceListType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CachingMetadataType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstExpressionEvaluatorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectDeltaOperationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; @@ -318,6 +320,59 @@ public static String prettyPrint(ResourceAttributeDefinitionType vc) { sb.append(")"); return sb.toString(); } + + public static String prettyPrint(ExpressionType expressionType) { + if (expressionType == null) { + return "null"; + } + StringBuilder sb = new StringBuilder("ExpressionType("); + appendPropertyIfNotNull(sb, "description", expressionType.getDescription()); + appendPropertyIfNotNull(sb, "extension", expressionType.getExtension()); + appendPropertyIfNotNull(sb, "trace", expressionType.isTrace()); + appendPropertyIfNotNull(sb, "variable", expressionType.getVariable()); + appendPropertyIfNotNull(sb, "returnMultiplicity", expressionType.getReturnMultiplicity()); + appendPropertyIfNotNull(sb, "allowEmptyValues", expressionType.isAllowEmptyValues()); + appendPropertyIfNotNull(sb, "queryInterpretationOfNoValue", expressionType.getQueryInterpretationOfNoValue()); + appendPropertyIfNotNull(sb, "runAsRef", expressionType.getRunAsRef()); + List> expressionEvaluators = expressionType.getExpressionEvaluator(); + sb.append("evaluator").append("="); + if (expressionEvaluators.isEmpty()) { + sb.append("[]"); + } else { + if (expressionEvaluators.size() > 1) { + sb.append("["); + } + for (JAXBElement expressionEvaluator : expressionEvaluators) { + sb.append(expressionEvaluator.getName().getLocalPart()); + sb.append(":"); + sb.append(PrettyPrinter.prettyPrint(expressionEvaluator.getValue())); + if (expressionEvaluators.size() > 1) { + sb.append(", "); + } + } + if (expressionEvaluators.size() > 1) { + sb.append("]"); + } + } + sb.append(")"); + return sb.toString(); + } + + public static String prettyPrint(ConstExpressionEvaluatorType expressionType) { + if (expressionType == null) { + return "null"; + } + StringBuilder sb = new StringBuilder("ConstExpressionEvaluatorType("); + sb.append(expressionType.getValue()); + sb.append(")"); + return sb.toString(); + } + + private static void appendPropertyIfNotNull(StringBuilder sb, String propName, Object value) { + if (value != null) { + sb.append(propName).append("=").append(value).append(","); + } + } public static String prettyPrint(CachingMetadataType cachingMetadata) { if (cachingMetadata == null) { 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 e6f6d19fef8..ec183cbcc60 100644 --- 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 @@ -9507,6 +9507,20 @@ + + + + If set to false (defaul) the specification will apply only to active relations + (e.g active delegations). If set to true then the specificaiton will also be applied + to inactive relations (e.g. expired delegations). + Only partially implemented. Works only for some cases (delegator). + EXPERIMENTAL. Use at your own risk. + + + 3.6.1 + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd index e90e0ff0231..35920ab76b8 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd @@ -602,6 +602,18 @@ + + + + + + + + + + + + @@ -1140,7 +1152,19 @@ - + + + + TODO + EXPERIMENTAL + + + 3.7 + true + + + + @@ -1319,6 +1343,100 @@ + + + + Specification of a process that is to be started. + EXPERIMENTAL + + + + 3.7 + true + + + + + + + + Reference to existing process specification. NOT IMPLEMENTED YET + + + + + + + + Name for this process specification. NOT IMPLEMENTED YET + + + + + + + In what order this process specification is to be considered. + + + + + + + TODO + + + + + + + What approval actions to include during approval schema creation for this process. NOT IMPLEMENTED YET + + + + + + + What approval actions to exclude during approval schema creation for this process. NOT IMPLEMENTED YET + + + + + + + + + + + + TODO + EXPERIMENTAL + + + + 3.7 + true + + + + + + + + Include all modifications of these items as a delta that is to be approved. + + + + + + + Create a delta for each value of this item that is to be added, deleted or modified. + + + + + + + diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java index bb506565e5a..5c0e79f5b8a 100644 --- a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java @@ -22,6 +22,7 @@ import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.DebugUtil; import org.testng.annotations.Test; import com.evolveum.midpoint.prism.delta.ObjectDelta; @@ -40,6 +41,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import static org.testng.AssertJUnit.*; /** @@ -386,4 +389,145 @@ public void testSubtractAssignmentFromModifyDelta() throws Exception { assertTrue("Remaining delta is not a MODIFY delta", delta.isModify()); assertEquals("Wrong # of remaining modifications", 0, delta.getModifications().size()); } + + // subtract of single-valued PCV from multivalued one + @Test + public void testFactorAddDeltaForItem() throws Exception { + final String TEST_NAME = "testFactorAddDeltaForItem"; + displayTestTile(TEST_NAME); + + // GIVEN + PrismObject user = PrismTestUtil.parseObject(USER_BILL_FILE); + ObjectDelta addDelta = ObjectDelta.createAddDelta(user); + + // WHEN + ObjectDelta.FactorOutResult out = addDelta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); + + // THEN + System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); + System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); + System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + + assertTrue("Remaining delta is not an ADD delta", out.remainder.isAdd()); + assertEquals("Wrong # of remaining assignments", 0, out.remainder.getObjectToAdd().asObjectable().getAssignment().size()); + assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); + assertEquals("Wrong # of modifications in offspring", 1, out.offsprings.get(0).getModifications().size()); + assertEquals("Wrong # of assignments to add", 3, out.offsprings.get(0).getModifications().iterator().next().getValuesToAdd().size()); + } + + // subtract of single-valued PCV from multivalued one + @Test + public void testFactorAddDeltaForItems() throws Exception { + final String TEST_NAME = "testFactorAddDeltaForItems"; + displayTestTile(TEST_NAME); + + // GIVEN + PrismObject user = PrismTestUtil.parseObject(USER_BILL_FILE); + ObjectDelta addDelta = ObjectDelta.createAddDelta(user); + + // WHEN + ObjectDelta.FactorOutResult out = addDelta.factorOut(asList(new ItemPath(UserType.F_GIVEN_NAME), new ItemPath(UserType.F_FAMILY_NAME)), true); + + // THEN + System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); + System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); + System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + + assertTrue("Remaining delta is not an ADD delta", out.remainder.isAdd()); + assertEquals("Wrong # of remaining assignments", 3, out.remainder.getObjectToAdd().asObjectable().getAssignment().size()); + assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); + assertEquals("Wrong # of modifications in offspring", 2, out.offsprings.get(0).getModifications().size()); + } + + @Test + public void testFactorAddDeltaForItemValues() throws Exception { + final String TEST_NAME = "testFactorAddDeltaForItemValues"; + displayTestTile(TEST_NAME); + + // GIVEN + PrismObject user = PrismTestUtil.parseObject(USER_BILL_FILE); + ObjectDelta addDelta = ObjectDelta.createAddDelta(user); + + // WHEN + ObjectDelta.FactorOutResult out = addDelta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); + + // THEN + System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); + System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); + System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + + assertTrue("Remaining delta is not an ADD delta", out.remainder.isAdd()); + assertEquals("Wrong # of remaining assignments", 0, out.remainder.getObjectToAdd().asObjectable().getAssignment().size()); + assertEquals("Wrong # of offspring deltas", 3, out.offsprings.size()); + for (ObjectDelta offspring : out.offsprings) { + assertEquals("Wrong # of modifications in offspring", 1, offspring.getModifications().size()); + assertEquals("Wrong # of assignments to add", 1, offspring.getModifications().iterator().next().getValuesToAdd().size()); + } + } + + @Test + public void testFactorModifyDeltaForItem() throws Exception { + final String TEST_NAME = "testFactorModifyDeltaForItem"; + displayTestTile(TEST_NAME); + + // GIVEN + ObjectDelta delta = DeltaBuilder.deltaFor(UserType.class, getPrismContext()) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo("oid-r", ObjectTypes.ROLE, getPrismContext())) + .delete(new AssignmentType().id(101L), new AssignmentType().id(102L)) + .item(UserType.F_ASSIGNMENT, 100L, AssignmentType.F_LIFECYCLE_STATE).replace("draft") + .item(UserType.F_GIVEN_NAME).replace("bill") + .asObjectDeltaCast("oid1"); + + // WHEN + ObjectDelta.FactorOutResult out = delta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); + + // THEN + System.out.println("Delta before operation:\n" + delta.debugDump() + "\n"); + System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); + System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + + assertTrue("Remaining delta is not a MODIFY delta", out.remainder.isModify()); + assertEquals("Wrong # of remaining modifications", 1, out.remainder.getModifications().size()); + assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); + assertEquals("Wrong # of modifications in offspring 0", 2, out.offsprings.get(0).getModifications().size()); + } + + @Test + public void testFactorModifyDeltaForItemValues() throws Exception { + final String TEST_NAME = "testFactorModifyDeltaForItemValues"; + displayTestTile(TEST_NAME); + + // GIVEN + ObjectDelta delta = DeltaBuilder.deltaFor(UserType.class, getPrismContext()) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo("oid-r", ObjectTypes.ROLE, getPrismContext())) + .delete(new AssignmentType().id(101L), new AssignmentType().id(102L)) + .item(UserType.F_ASSIGNMENT, 100L, AssignmentType.F_LIFECYCLE_STATE).replace("draft") + .item(UserType.F_ASSIGNMENT, 100L, AssignmentType.F_DESCRIPTION).replace("descr") + .item(UserType.F_ASSIGNMENT, 77L, AssignmentType.F_LIFECYCLE_STATE).replace("active") + .item(UserType.F_GIVEN_NAME).replace("bill") + .asObjectDeltaCast("oid1"); + + // WHEN + ObjectDelta.FactorOutResult out = delta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); + + // THEN + System.out.println("Delta before operation:\n" + delta.debugDump() + "\n"); + System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); + System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + + assertTrue("Remaining delta is not a MODIFY delta", out.remainder.isModify()); + assertEquals("Wrong # of remaining modifications", 1, out.remainder.getModifications().size()); + assertEquals("Wrong # of offspring deltas", 5, out.offsprings.size()); + assertEquals("Wrong # of modifications in offspring 0", 1, out.offsprings.get(0).getModifications().size()); + assertEquals("Wrong # of assignments to add in offspring 0", 1, out.offsprings.get(0).getModifications().iterator().next().getValuesToAdd().size()); + assertEquals("Wrong # of modifications in offspring 1", 1, out.offsprings.get(1).getModifications().size()); + assertEquals("Wrong # of assignments to delete in offspring 1", 1, out.offsprings.get(1).getModifications().iterator().next().getValuesToDelete().size()); + assertEquals("Wrong # of modifications in offspring 2", 1, out.offsprings.get(2).getModifications().size()); + assertEquals("Wrong # of assignments to delete in offspring 2", 1, out.offsprings.get(2).getModifications().iterator().next().getValuesToDelete().size()); + // fragile - can be swapped if hashcodes change + assertEquals("Wrong # of modifications in offspring 3", 2, out.offsprings.get(3).getModifications().size()); + assertEquals("Wrong # of modifications in offspring 4", 1, out.offsprings.get(4).getModifications().size()); + } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/DebugUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/DebugUtil.java index 1eec6d4f366..fc82041efa8 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/DebugUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/DebugUtil.java @@ -191,7 +191,7 @@ public static String debugDump(Object object, int indent) { } else { StringBuilder sb = new StringBuilder(); indentDebugDump(sb, indent + 1); - sb.append(object.toString()); + sb.append(PrettyPrinter.prettyPrint(object)); return sb.toString(); } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java index 5700889f2d7..a2821e59f90 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java @@ -27,8 +27,10 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; @@ -71,6 +73,8 @@ import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.FailableProcessor; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -220,6 +224,9 @@ public abstract class AbstractSecurityTest extends AbstractInitializedModelInteg protected static final File ROLE_DELEGATOR_FILE = new File(TEST_DIR, "role-delegator.xml"); protected static final String ROLE_DELEGATOR_OID = "00000000-0000-0000-0000-00000000d001"; + + protected static final File ROLE_DELEGATOR_PLUS_FILE = new File(TEST_DIR, "role-delegator-plus.xml"); + protected static final String ROLE_DELEGATOR_PLUS_OID = "00000000-0000-0000-0000-00000000d101"; protected static final File ROLE_ORG_READ_ORGS_MINISTRY_OF_RUM_FILE = new File(TEST_DIR, "role-org-read-orgs-ministry-of-rum.xml"); protected static final String ROLE_ORG_READ_ORGS_MINISTRY_OF_RUM_OID = "00000000-0000-0000-0000-00000000aa0d"; @@ -349,7 +356,7 @@ public abstract class AbstractSecurityTest extends AbstractInitializedModelInteg protected static final XMLGregorianCalendar JACK_VALID_FROM_LONG_AGO = XmlTypeConverter.createXMLGregorianCalendar(10000L); protected static final int NUMBER_OF_ALL_USERS = 11; - protected static final int NUMBER_OF_IMPORTED_ROLES = 63; + protected static final int NUMBER_OF_IMPORTED_ROLES = 64; protected static final int NUMBER_OF_ALL_ORGS = 11; protected String userRumRogersOid; @@ -393,6 +400,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti repoAddObjectFromFile(ROLE_ASSIGN_REQUESTABLE_ROLES_FILE, initResult); repoAddObjectFromFile(ROLE_ASSIGN_ORGRELATION_FILE, initResult); repoAddObjectFromFile(ROLE_DELEGATOR_FILE, initResult); + repoAddObjectFromFile(ROLE_DELEGATOR_PLUS_FILE, initResult); repoAddObjectFromFile(ROLE_ORG_READ_ORGS_MINISTRY_OF_RUM_FILE, initResult); repoAddObjectFromFile(ROLE_FILTER_OBJECT_USER_LOCATION_SHADOWS_FILE, initResult); repoAddObjectFromFile(ROLE_FILTER_OBJECT_USER_TYPE_SHADOWS_FILE, initResult); @@ -615,12 +623,12 @@ protected void cleanupDelete(Class type, String oid, T } } - protected void assertVisibleUsers(int expectedNumAllUsers) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertVisibleUsers(int expectedNumAllUsers) throws Exception { assertSearch(UserType.class, null, expectedNumAllUsers); } - protected void assertReadDeny() throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadDeny() throws Exception { assertReadDeny(0); assertReadDenyRaw(); } @@ -637,7 +645,7 @@ protected void assertReadCertCases(int expectedNumber) throws ObjectNotFoundExce assertContainerSearch(AccessCertificationCaseType.class, null, expectedNumber); } - protected void assertReadDeny(int expectedNumAllUsers) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadDeny(int expectedNumAllUsers) throws Exception { assertGetDeny(UserType.class, USER_JACK_OID); assertGetDeny(UserType.class, USER_JACK_OID, SelectorOptions.createCollection(GetOperationOptions.createRaw())); assertGetDeny(UserType.class, USER_GUYBRUSH_OID); @@ -650,7 +658,7 @@ protected void assertReadDeny(int expectedNumAllUsers) throws ObjectNotFoundExce assertSearch(UserType.class, createNameQuery(USER_GUYBRUSH_USERNAME), SelectorOptions.createCollection(GetOperationOptions.createRaw()), 0); } - protected void assertReadDenyRaw() throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + protected void assertReadDenyRaw() throws Exception { assertGetDeny(UserType.class, USER_JACK_OID, SelectorOptions.createCollection(GetOperationOptions.createRaw())); assertGetDeny(UserType.class, USER_GUYBRUSH_OID, SelectorOptions.createCollection(GetOperationOptions.createRaw())); @@ -659,11 +667,11 @@ protected void assertReadDenyRaw() throws ObjectNotFoundException, SchemaExcepti assertSearchDeny(UserType.class, createNameQuery(USER_GUYBRUSH_USERNAME), SelectorOptions.createCollection(GetOperationOptions.createRaw())); } - protected void assertReadAllow() throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadAllow() throws Exception { assertReadAllow(NUMBER_OF_ALL_USERS); } - protected void assertReadAllow(int expectedNumAllUsers) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadAllow(int expectedNumAllUsers) throws Exception { assertGetAllow(UserType.class, USER_JACK_OID); assertGetAllow(UserType.class, USER_GUYBRUSH_OID); @@ -672,11 +680,11 @@ protected void assertReadAllow(int expectedNumAllUsers) throws ObjectNotFoundExc assertSearch(UserType.class, createNameQuery(USER_GUYBRUSH_USERNAME), 1); } - protected void assertReadAllowRaw() throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadAllowRaw() throws Exception { assertReadAllowRaw(NUMBER_OF_ALL_USERS); } - protected void assertReadAllowRaw(int expectedNumAllUsers) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertReadAllowRaw(int expectedNumAllUsers) throws Exception { assertGetAllow(UserType.class, USER_JACK_OID, SelectorOptions.createCollection(GetOperationOptions.createRaw())); assertGetAllow(UserType.class, USER_GUYBRUSH_OID, SelectorOptions.createCollection(GetOperationOptions.createRaw())); @@ -773,11 +781,11 @@ protected PrismObject assertGetAllow(Class type, St return object; } - protected void assertSearch(Class type, ObjectQuery query, int expectedResults) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertSearch(Class type, ObjectQuery query, int expectedResults) throws Exception { assertSearch(type, query, null, expectedResults); } - protected void assertSearchRaw(Class type, ObjectQuery query, int expectedResults) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + protected void assertSearchRaw(Class type, ObjectQuery query, int expectedResults) throws Exception { assertSearch(type, query, SelectorOptions.createCollection(GetOperationOptions.createRaw()), expectedResults); } @@ -786,7 +794,7 @@ protected void assertContainerSearch(Class type, Ob } protected void assertSearchDeny(Class type, ObjectQuery query, - Collection> options) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + Collection> options) throws Exception { try { assertSearch(type, query, options, 0); } catch (SecurityViolationException e) { @@ -795,21 +803,70 @@ protected void assertSearchDeny(Class type, ObjectQuer } } + + protected void assertSearch(Class type, ObjectQuery query, + Collection> options, int expectedResults) throws Exception { + assertSearch(type, query, options, + new SearchAssertion() { + + @Override + public void assertObjects(String message, List> objects) throws Exception { + if (objects.size() > expectedResults) { + failDeny(message, type, query, expectedResults, objects.size()); + } else if (objects.size() < expectedResults) { + failAllow(message, type, query, expectedResults, objects.size()); + } + } + + @Override + public void assertCount(int count) throws Exception { + if (count > expectedResults) { + failDeny("count", type, query, expectedResults, count); + } else if (count < expectedResults) { + failAllow("count", type, query, expectedResults, count); + } + } + + }); + } + + protected void assertSearch(Class type, ObjectQuery query, String... expectedOids) throws Exception { + assertSearch(type, query, null, expectedOids); + } + + protected void assertSearch(Class type, ObjectQuery query, + Collection> options, String... expectedOids) throws Exception { + assertSearch(type, query, options, + new SearchAssertion() { + + @Override + public void assertObjects(String message, List> objects) throws Exception { + if (!MiscUtil.unorderedCollectionEquals(objects, Arrays.asList(expectedOids), + (object,expectedOid) -> expectedOid.equals(object.getOid()))) { + failAllow(message, type, (query==null?"null":query.toString())+", expected "+Arrays.toString(expectedOids)+", actual "+objects, null); + } + } + + @Override + public void assertCount(int count) throws Exception { + if (count != expectedOids.length) { + failAllow("count", type, query, expectedOids.length, count); + } + } + + }); + } + protected void assertSearch(Class type, ObjectQuery query, - Collection> options, int expectedResults) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + Collection> options, SearchAssertion assertion) throws Exception { Task task = taskManager.createTaskInstance(AbstractSecurityTest.class.getName() + ".assertSearchObjects"); OperationResult result = task.getResult(); try { logAttempt("search", type, query); List> objects = modelService.searchObjects(type, query, options, task, result); display("Search returned", objects.toString()); - if (objects.size() > expectedResults) { - failDeny("search", type, query, expectedResults, objects.size()); - } else if (objects.size() < expectedResults) { - failAllow("search", type, query, expectedResults, objects.size()); - } - result.computeStatus(); - TestUtil.assertSuccess(result); + assertion.assertObjects("search", objects); + assertSuccess(result); } catch (SecurityViolationException e) { // this should not happen result.computeStatus(); @@ -831,13 +888,8 @@ public boolean handle(PrismObject object, OperationResult parentResult) { }; modelService.searchObjectsIterative(type, query, handler, options, task, result); display("Search iterative returned", objects.toString()); - if (objects.size() > expectedResults) { - failDeny("searchIterative", type, query, expectedResults, objects.size()); - } else if (objects.size() < expectedResults) { - failAllow("searchIterative", type, query, expectedResults, objects.size()); - } - result.computeStatus(); - TestUtil.assertSuccess(result); + assertion.assertObjects("searchIterative", objects); + assertSuccess(result); } catch (SecurityViolationException e) { // this should not happen result.computeStatus(); @@ -851,13 +903,8 @@ public boolean handle(PrismObject object, OperationResult parentResult) { logAttempt("count", type, query); int numObjects = modelService.countObjects(type, query, options, task, result); display("Count returned", numObjects); - if (numObjects > expectedResults) { - failDeny("count", type, query, expectedResults, numObjects); - } else if (numObjects < expectedResults) { - failAllow("count", type, query, expectedResults, numObjects); - } - result.computeStatus(); - TestUtil.assertSuccess(result); + assertion.assertCount(numObjects); + assertSuccess(result); } catch (SecurityViolationException e) { // this should not happen result.computeStatus(); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/SearchAssertion.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/SearchAssertion.java new file mode 100644 index 00000000000..079f0245e89 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/SearchAssertion.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.intest.security; + +import java.util.List; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * @author semancik + * + */ +public interface SearchAssertion { + + void assertObjects(String message, List> objects) throws Exception; + + void assertCount(int count) throws Exception; + +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/TestSecurityAdvanced.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/TestSecurityAdvanced.java index 37f22a2ba8e..2e2bc282afd 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/TestSecurityAdvanced.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/TestSecurityAdvanced.java @@ -15,29 +15,30 @@ */ package com.evolveum.midpoint.model.intest.security; -import static com.evolveum.midpoint.test.IntegrationTestTools.display; import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; import java.io.IOException; import java.util.Collection; +import javax.xml.datatype.XMLGregorianCalendar; + import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; -import com.evolveum.midpoint.model.api.RoleSelectionSpecification; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.query.TypeFilter; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.IntegrationTestTools; -import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -46,7 +47,9 @@ 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.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPolicyEnforcementType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; @@ -72,14 +75,14 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti @Test public void test100AutzJackPersonaManagement() throws Exception { final String TEST_NAME = "test100AutzJackPersonaManagement"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_PERSONA_MANAGEMENT_OID); login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetAllow(UserType.class, USER_JACK_OID); assertGetDeny(UserType.class, USER_GUYBRUSH_OID); @@ -99,14 +102,14 @@ public void test100AutzJackPersonaManagement() throws Exception { @Test public void test102AutzLechuckPersonaManagement() throws Exception { final String TEST_NAME = "test102AutzLechuckPersonaManagement"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_LECHUCK_OID); assignRole(USER_LECHUCK_OID, ROLE_PERSONA_MANAGEMENT_OID); login(USER_LECHUCK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetDeny(UserType.class, USER_JACK_OID); assertGetDeny(UserType.class, USER_GUYBRUSH_OID); @@ -127,14 +130,14 @@ public void test102AutzLechuckPersonaManagement() throws Exception { @Test public void test110AutzJackPersonaAdmin() throws Exception { final String TEST_NAME = "test110AutzJackAddPersonaAdmin"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_PERSONA_MANAGEMENT_OID); login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertAllow("assign application role 1 to jack", (task,result) -> assignRole(USER_JACK_OID, ROLE_PERSONA_ADMIN_OID, task, result)); @@ -182,7 +185,7 @@ public void test110AutzJackPersonaAdmin() throws Exception { @Test public void test120AutzJackDelagator() throws Exception { final String TEST_NAME = "test120AutzJackDelagator"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_DELEGATOR_OID); @@ -192,7 +195,7 @@ public void test120AutzJackDelagator() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertReadAllow(NUMBER_OF_ALL_USERS); assertAddDeny(); @@ -234,6 +237,9 @@ public void test120AutzJackDelagator() throws Exception { display("Barbossa delegate", userBarbossa); assertAssignments(userBarbossa, 1); assertAssignedDeputy(userBarbossa, USER_JACK_OID); + + assertDeputySearchDelegatorRef(USER_JACK_OID, USER_BARBOSSA_OID); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); // Non-delegate. We should be able to read just the name. Not the assignments. PrismObject userRum = getUser(userRumRogersOid); @@ -242,7 +248,7 @@ public void test120AutzJackDelagator() throws Exception { login(USER_BARBOSSA_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); display("Logged in as Barbossa"); assertReadAllow(NUMBER_OF_ALL_USERS); @@ -252,7 +258,7 @@ public void test120AutzJackDelagator() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); display("Logged in as Jack"); assertAllow("undelegate from Barbossa", @@ -270,7 +276,7 @@ public void test120AutzJackDelagator() throws Exception { login(USER_BARBOSSA_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); display("Logged in as Barbossa"); assertReadDeny(); @@ -290,11 +296,338 @@ public void test120AutzJackDelagator() throws Exception { assertGlobalStateUntouched(); } + + /** + * Assign a deputy, but this time with validFrom and validTo set to the future. + * The delegator role does NOT allow access to inactive delegations. + * MID-4172 + */ + @Test + public void test122AutzJackDelagatorValidity() throws Exception { + final String TEST_NAME = "test122AutzJackDelagatorValidity"; + displayTestTitle(TEST_NAME); + // GIVEN + cleanupAutzTest(USER_JACK_OID); + assignRole(USER_JACK_OID, ROLE_DELEGATOR_OID); - @Test + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + + login(USER_JACK_USERNAME); + + // WHEN + displayWhen(TEST_NAME); + + PrismObject userJack = getUser(USER_JACK_OID); + assertAssignments(userJack, 1); + assertAssignedRole(userJack, ROLE_DELEGATOR_OID); + + PrismObject userBarbossa = getUser(USER_BARBOSSA_OID); + assertNoAssignments(userBarbossa); + + XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); + + ActivationType activationType = new ActivationType(); + activationType.setValidFrom(XmlTypeConverter.addDuration(startTs, "PT2H")); + activationType.setValidTo(XmlTypeConverter.addDuration(startTs, "P1D")); + + // Good direction + assertAllow("delegate to Barbossa", + (task, result) -> { + assignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, + assignment -> assignment.setActivation(activationType), task, result); + }); + + userJack = getUser(USER_JACK_OID); + display("Jack delegator", userJack); + assertAssignments(userJack, 1); + + userBarbossa = getUser(USER_BARBOSSA_OID); + display("Barbossa delegate", userBarbossa); + // Delegation is not active yet. Therefore jack cannot see it. + assertAssignments(userBarbossa, 0); + + assertDeputySearchDelegatorRef(USER_JACK_OID /* nothing */); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); // WRONG!!! +// assertDeputySearchAssignmentTarget(USER_JACK_OID /* nothing */); + + // Non-delegate. We should be able to read just the name. Not the assignments. + PrismObject userRum = getUser(userRumRogersOid); + display("User Rum Rogers", userRum); + assertNoAssignments(userRum); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + // Delegation is not active yet. No access. + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + clockForward("PT3H"); + + login(USER_ADMINISTRATOR_USERNAME); + recomputeUser(USER_BARBOSSA_OID); + + // Delegation is active now + + login(USER_JACK_USERNAME); + // WHEN + + userBarbossa = getUser(USER_BARBOSSA_OID); + display("Barbossa delegate", userBarbossa); + assertAssignments(userBarbossa, 1); + assertAssignedDeputy(userBarbossa, USER_JACK_OID); + + assertDeputySearchDelegatorRef(USER_JACK_OID, USER_BARBOSSA_OID); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + assertReadAllow(NUMBER_OF_ALL_USERS); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + clockForward("P1D"); + + login(USER_ADMINISTRATOR_USERNAME); + recomputeUser(USER_BARBOSSA_OID); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + // Delegation is not active any more. No access. + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + + login(USER_JACK_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Jack"); + + assertAllow("undelegate from Barbossa", + (task, result) -> { + unassignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, + assignment -> assignment.setActivation(activationType), task, result); + }); + + userJack = getUser(USER_JACK_OID); + assertAssignments(userJack, 1); + + userBarbossa = getUser(USER_BARBOSSA_OID); + assertNoAssignments(userBarbossa); + + assertGlobalStateUntouched(); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + assertDeny("delegate to Jack", + (task, result) -> { + assignDeputy(USER_JACK_OID, USER_BARBOSSA_OID, task, result); + }); + + assertDeny("delegate from Jack to Barbossa", + (task, result) -> { + assignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, task, result); + }); + + assertGlobalStateUntouched(); + } + + /** + * Assign a deputy with validity. But this time there is a role that allows + * access to inactive delegations. + * MID-4172 + */ + @Test + public void test124AutzJackDelagatorPlusValidity() throws Exception { + final String TEST_NAME = "test124AutzJackDelagatorPlusValidity"; + displayTestTitle(TEST_NAME); + // GIVEN + cleanupAutzTest(USER_JACK_OID); + assignRole(USER_JACK_OID, ROLE_DELEGATOR_PLUS_OID); + + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + + login(USER_JACK_USERNAME); + + // WHEN + displayWhen(TEST_NAME); + + PrismObject userJack = getUser(USER_JACK_OID); + assertAssignments(userJack, 1); + assertAssignedRole(userJack, ROLE_DELEGATOR_PLUS_OID); + + PrismObject userBarbossa = getUser(USER_BARBOSSA_OID); + assertNoAssignments(userBarbossa); + + XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); + + ActivationType activationType = new ActivationType(); + activationType.setValidFrom(XmlTypeConverter.addDuration(startTs, "PT2H")); + activationType.setValidTo(XmlTypeConverter.addDuration(startTs, "P1D")); + + // Good direction + assertAllow("delegate to Barbossa", + (task, result) -> { + assignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, + assignment -> assignment.setActivation(activationType), task, result); + }); + + userJack = getUser(USER_JACK_OID); + display("Jack delegator", userJack); + assertAssignments(userJack, 1); + + userBarbossa = getUser(USER_BARBOSSA_OID); + display("Barbossa delegate", userBarbossa); + assertAssignments(userBarbossa, 1); + assertAssignedDeputy(userBarbossa, USER_JACK_OID); + + // delegatorRef is allowed, but returns nothing. The delegation is not yet active, it is not in the delgatorRef. + assertDeputySearchDelegatorRef(USER_JACK_OID /* nothing */); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); + + // Non-delegate. We should be able to read just the name. Not the assignments. + PrismObject userRum = getUser(userRumRogersOid); + display("User Rum Rogers", userRum); + assertNoAssignments(userRum); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + // Delegation is not active yet. No access. + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + clockForward("PT3H"); + + login(USER_ADMINISTRATOR_USERNAME); + recomputeUser(USER_BARBOSSA_OID); + + // Delegation is active now + + login(USER_JACK_USERNAME); + // WHEN + + userBarbossa = getUser(USER_BARBOSSA_OID); + display("Barbossa delegate", userBarbossa); + assertAssignments(userBarbossa, 1); + assertAssignedDeputy(userBarbossa, USER_JACK_OID); + + assertDeputySearchDelegatorRef(USER_JACK_OID, USER_BARBOSSA_OID); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + assertReadAllow(NUMBER_OF_ALL_USERS); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + clockForward("P1D"); + + login(USER_ADMINISTRATOR_USERNAME); + recomputeUser(USER_BARBOSSA_OID); + + // Delegation no longer active + + login(USER_JACK_USERNAME); + // WHEN + + userBarbossa = getUser(USER_BARBOSSA_OID); + display("Barbossa delegate", userBarbossa); + assertAssignments(userBarbossa, 1); + assertAssignedDeputy(userBarbossa, USER_JACK_OID); + + // delegatorRef is allowed, but returns nothing. The delegation is not yet active, it is not in the delgatorRef. + assertDeputySearchDelegatorRef(USER_JACK_OID /* nothing */); + assertDeputySearchAssignmentTarget(USER_JACK_OID, USER_BARBOSSA_OID); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + // Delegation is not active any more. No access. + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + + login(USER_JACK_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Jack"); + + assertAllow("undelegate from Barbossa", + (task, result) -> { + unassignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, + assignment -> assignment.setActivation(activationType), task, result); + }); + + userJack = getUser(USER_JACK_OID); + assertAssignments(userJack, 1); + + userBarbossa = getUser(USER_BARBOSSA_OID); + assertNoAssignments(userBarbossa); + + assertGlobalStateUntouched(); + + login(USER_BARBOSSA_USERNAME); + // WHEN + displayWhen(TEST_NAME); + display("Logged in as Barbossa"); + + assertReadDeny(); + assertAddDeny(); + assertModifyDeny(); + assertDeleteDeny(); + + assertDeny("delegate to Jack", + (task, result) -> { + assignDeputy(USER_JACK_OID, USER_BARBOSSA_OID, task, result); + }); + + assertDeny("delegate from Jack to Barbossa", + (task, result) -> { + assignDeputy(USER_BARBOSSA_OID, USER_JACK_OID, task, result); + }); + + assertGlobalStateUntouched(); + } + + + @Test public void test150AutzJackApproverUnassignRoles() throws Exception { final String TEST_NAME = "test150AutzJackApproverUnassignRoles"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_APPROVER_UNASSIGN_ROLES_OID); @@ -307,7 +640,7 @@ public void test150AutzJackApproverUnassignRoles() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertGetDeny(RoleType.class, ROLE_PERSONA_ADMIN_OID); // no assignment @@ -339,7 +672,7 @@ public void test150AutzJackApproverUnassignRoles() throws Exception { @Test public void test151AutzJackApproverUnassignRolesAndRead() throws Exception { final String TEST_NAME = "test151AutzJackApproverUnassignRolesAndRead"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_APPROVER_UNASSIGN_ROLES_OID); @@ -349,7 +682,7 @@ public void test151AutzJackApproverUnassignRolesAndRead() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertGetAllow(RoleType.class, ROLE_PERSONA_ADMIN_OID); // no assignment @@ -383,7 +716,7 @@ public void test151AutzJackApproverUnassignRolesAndRead() throws Exception { @Test public void test154AutzJackApproverRead() throws Exception { final String TEST_NAME = "test154AutzJackApproverRead"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_BASIC_ITEMS_OID); @@ -392,7 +725,7 @@ public void test154AutzJackApproverRead() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject roleOrdinary = assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertNoRoleMembershipRef(roleOrdinary); @@ -449,7 +782,7 @@ public void test154AutzJackApproverRead() throws Exception { @Test public void test155AutzJackApproverSelf() throws Exception { final String TEST_NAME = "test155AutzJackApproverSelf"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_SELF_OID); @@ -458,7 +791,7 @@ public void test155AutzJackApproverSelf() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetDeny(RoleType.class, ROLE_ORDINARY_OID); assertGetDeny(RoleType.class, ROLE_PERSONA_ADMIN_OID); @@ -507,7 +840,7 @@ public void test155AutzJackApproverSelf() throws Exception { @Test public void test157AutzJackReadRoleMembers() throws Exception { final String TEST_NAME = "test157AutzJackReadRoleMembers"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_ROLE_MEMBERS_OID); @@ -515,7 +848,7 @@ public void test157AutzJackReadRoleMembers() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject roleOrdinary = assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertNoRoleMembershipRef(roleOrdinary); @@ -560,7 +893,7 @@ public void test157AutzJackReadRoleMembers() throws Exception { @Test public void test158AutzJackReadRoleMembersWrong() throws Exception { final String TEST_NAME = "test158AutzJackReadRoleMembersWrong"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_ROLE_MEMBERS_WRONG_OID); @@ -568,7 +901,7 @@ public void test158AutzJackReadRoleMembersWrong() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject roleOrdinary = assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertNoRoleMembershipRef(roleOrdinary); @@ -613,7 +946,7 @@ public void test158AutzJackReadRoleMembersWrong() throws Exception { @Test public void test159AutzJackReadRoleMembersNone() throws Exception { final String TEST_NAME = "test159AutzJackReadRoleMembersNone"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_ROLE_MEMBERS_NONE_OID); @@ -621,7 +954,7 @@ public void test159AutzJackReadRoleMembersNone() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); PrismObject roleOrdinary = assertGetAllow(RoleType.class, ROLE_ORDINARY_OID); assertNoRoleMembershipRef(roleOrdinary); @@ -716,7 +1049,7 @@ private void assert15xCommon() throws Exception { @Test public void test200AutzJackModifyOrgunit() throws Exception { final String TEST_NAME = "test200AutzJackModifyOrgunit"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_SELF_MODIFY_ORGUNIT_OID); @@ -726,7 +1059,7 @@ public void test200AutzJackModifyOrgunit() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetAllow(UserType.class, USER_JACK_OID); assertAddDeny(); @@ -777,7 +1110,7 @@ public void test200AutzJackModifyOrgunit() throws Exception { @Test public void test202AutzJackModifyOrgunitAndAssignRole() throws Exception { final String TEST_NAME = "test202AutzJackModifyOrgunitAndAssignRole"; - TestUtil.displayTestTitle(this, TEST_NAME); + displayTestTitle(TEST_NAME); // GIVEN cleanupAutzTest(USER_JACK_OID); assignRole(USER_JACK_OID, ROLE_READ_SELF_MODIFY_ORGUNIT_OID); @@ -788,7 +1121,7 @@ public void test202AutzJackModifyOrgunitAndAssignRole() throws Exception { login(USER_JACK_USERNAME); // WHEN - TestUtil.displayWhen(TEST_NAME); + displayWhen(TEST_NAME); assertGetAllow(UserType.class, USER_JACK_OID); assertAddDeny(); @@ -857,4 +1190,20 @@ protected void cleanupAutzTest(String userOid) throws ObjectNotFoundException, S assignRole(userCobbOid, ROLE_UNINTERESTING_OID, task, result); } + + private void assertDeputySearchDelegatorRef(String delegatorOid, String... expectedDeputyOids) throws Exception { + PrismReferenceValue rval = new PrismReferenceValue(delegatorOid, UserType.COMPLEX_TYPE); + rval.setRelation(SchemaConstants.ORG_DEPUTY); + ObjectQuery query = queryFor(UserType.class).item(UserType.F_DELEGATED_REF).ref(rval).build(); + assertSearch(UserType.class, query, expectedDeputyOids); + } + + private void assertDeputySearchAssignmentTarget(String delegatorOid, String... expectedDeputyOids) throws Exception { + PrismReferenceValue rval = new PrismReferenceValue(delegatorOid, UserType.COMPLEX_TYPE); + rval.setRelation(SchemaConstants.ORG_DEPUTY); + ObjectQuery query = queryFor(UserType.class) + .item(new ItemPath(UserType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF)).ref(rval).build(); + assertSearch(UserType.class, query, expectedDeputyOids); + } + } diff --git a/model/model-intest/src/test/resources/security/role-delegator-plus.xml b/model/model-intest/src/test/resources/security/role-delegator-plus.xml new file mode 100644 index 00000000000..c43b3a513a6 --- /dev/null +++ b/model/model-intest/src/test/resources/security/role-delegator-plus.xml @@ -0,0 +1,87 @@ + + + Universal Self Delegator Pl + Like delegator, but can also access inactive delegations + true + + authz-read-basic + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#read + name + + + authz-read-self + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#read + + self + + + + authz-read-delagate-assignments + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#read + + UserType + + self + true + + + assignment + roleMembershipRef + delegatedRef + + + delegator-req + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#delegate + request + + UserType + + + self + + + + delegator-exec-user + + Quite strong universal execution rights are needed here. We are going to modify other users + (deputy assignments are in the delegate user, not delegator). + + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#modify + execution + + UserType + + + + delegator-exec-shadow + + Quite strong universal execution rights are needed here. We are going to modify other users + (deputy assignments are in the delegate user, not delegator). Modification of other users + may mean also creation/modification/deletion of their accounts. + + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#add + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#modify + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#delete + execution + + ShadowType + + + diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 2e3ee15bd64..ca3dc2f56ae 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -814,6 +814,13 @@ protected void renameObject(Class type, String oid, St modifyObjectReplaceProperty(type, oid, ObjectType.F_NAME, task, result, createPolyString(newName)); } + protected void recomputeUser(String userOid) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { + Task task = createTask("recomputeUser"); + OperationResult result = task.getResult(); + modelService.recompute(UserType.class, userOid, null, task, result); + assertSuccess(result); + } + protected void recomputeUser(String userOid, Task task, OperationResult result) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { modelService.recompute(UserType.class, userOid, null, task, result); } @@ -1819,7 +1826,7 @@ protected void modifyDeputyAssignmentLimits(String userDeputyOid, String userTar protected void assertAssignedDeputy(PrismObject focus, String targetUserOid) { MidPointAsserts.assertAssigned(focus, targetUserOid, UserType.COMPLEX_TYPE, SchemaConstants.ORG_DEPUTY); } - + protected static void assertAssignedOrgs(PrismObject user, String... orgOids) { MidPointAsserts.assertAssignedOrgs(user, orgOids); } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ModelInvocationContext.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ModelInvocationContext.java index 091ba3caeb9..115b9ef6e2d 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ModelInvocationContext.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ModelInvocationContext.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WfConfigurationType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,13 +27,13 @@ /** * @author mederly */ -public class ModelInvocationContext { +public class ModelInvocationContext { @NotNull public final PrismContext prismContext; - @NotNull public final ModelContext modelContext; + @NotNull public final ModelContext modelContext; @Nullable public final WfConfigurationType wfConfiguration; @NotNull public final Task taskFromModel; - public ModelInvocationContext(@NotNull PrismContext prismContext, @NotNull ModelContext modelContext, + public ModelInvocationContext(@NotNull PrismContext prismContext, @NotNull ModelContext modelContext, @Nullable WfConfigurationType wfConfiguration, @NotNull Task taskFromModel) { this.prismContext = prismContext; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java index 8650d923f7b..0dba9606f42 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java @@ -44,8 +44,11 @@ */ public class PcpChildWfTaskCreationInstruction extends WfTaskCreationInstruction { + @SuppressWarnings("unused") private static final Trace LOGGER = TraceManager.getTrace(PcpChildWfTaskCreationInstruction.class); + private ObjectTreeDeltas deltasToProcess; + protected PcpChildWfTaskCreationInstruction(ChangeProcessor changeProcessor, PI processInstruction) { super(changeProcessor, new PrimaryChangeProcessorSpecificContent(changeProcessor.getPrismContext()), processInstruction); } @@ -68,13 +71,13 @@ public void prepareCommonAttributes(PrimaryChangeAspect aspect, ModelContext setRequesterRef(requester); } - processorContent.setExecuteApprovedChangeImmediately(ModelExecuteOptions.isExecuteImmediatelyAfterApproval(((LensContext) modelContext).getOptions())); + processorContent.setExecuteApprovedChangeImmediately(ModelExecuteOptions.isExecuteImmediatelyAfterApproval(modelContext.getOptions())); processorContent.createProcessorSpecificState().setChangeAspect(aspect.getClass().getName()); if (isExecuteApprovedChangeImmediately()) { // actually, context should be emptied anyway; but to be sure, let's do it here as well - setTaskModelContext(((PrimaryChangeProcessor) getChangeProcessor()).contextCopyWithNoDelta((LensContext) modelContext)); + setTaskModelContext(((PrimaryChangeProcessor) getChangeProcessor()).contextCopyWithNoDelta(modelContext)); setExecuteModelOperationHandler(true); } @@ -87,11 +90,12 @@ public void prepareCommonAttributes(PrimaryChangeAspect aspect, ModelContext wfContext.getEvent().add(event); } - public void setDeltasToProcess(ObjectDelta delta) { + public void setDeltasToProcess(ObjectDelta delta) { setDeltasToProcesses(new ObjectTreeDeltas<>(delta, getChangeProcessor().getPrismContext())); } public void setDeltasToProcesses(ObjectTreeDeltas objectTreeDeltas) { + deltasToProcess = objectTreeDeltas; try { processorContent.createProcessorSpecificState().setDeltasToProcess(ObjectTreeDeltas.toObjectTreeDeltasType(objectTreeDeltas)); } catch (SchemaException e) { @@ -110,4 +114,8 @@ public String debugDump(int indent) { sb.append(super.debugDump(indent+1)); return sb.toString(); } + + public boolean isObjectCreationInstruction() { + return deltasToProcess != null && deltasToProcess.getFocusChange() != null && deltasToProcess.getFocusChange().isAdd(); + } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java index efb6bc4903a..af58cebe29b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java @@ -221,17 +221,21 @@ private HookOperationMode submitTasks(List in WfTask rootWfTask = submitRootTask(context, changesWithoutApproval, taskFromModel, executionMode, wfConfigurationType, result); WfTask wfTask0 = submitTask0(context, changesWithoutApproval, rootWfTask, executionMode, wfConfigurationType, result); + WfTask objectCreationTask = null; + // start the jobs List wfTasks = new ArrayList<>(instructions.size()); for (PcpChildWfTaskCreationInstruction instruction : instructions) { if (instruction.startsWorkflowProcess() && instruction.isExecuteApprovedChangeImmediately()) { // if we want to execute approved changes immediately in this instruction, we have to wait for - // task0 (if there is any) and then to update our model context with the results (if there are any) - // TODO CONSIDER THIS... when OID is no longer transferred + // task0 and/or object creation task instruction.addHandlersAfterWfProcessAtEnd(WfTaskUtil.WAIT_FOR_TASKS_HANDLER_URI, WfPrepareChildOperationTaskHandler.HANDLER_URI); } WfTask wfTask = wfTaskController.submitWfTask(instruction, rootWfTask.getTask(), wfConfigurationType, null, result); wfTasks.add(wfTask); + if (instruction.isObjectCreationInstruction()) { + objectCreationTask = wfTask; + } } // all jobs depend on job0 (if there is one) @@ -241,6 +245,15 @@ private HookOperationMode submitTasks(List in } wfTask0.commitChanges(result); } + // all jobs depend on object creation task (if there is one) + if (objectCreationTask != null) { + for (WfTask wfTask : wfTasks) { + if (wfTask != objectCreationTask) { + objectCreationTask.addDependent(wfTask); + } + } + objectCreationTask.commitChanges(result); + } baseModelInvocationProcessingHelper.logJobsBeforeStart(rootWfTask, result); rootWfTask.startWaitingForSubtasks(result); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/WfPrepareRootOperationTaskHandler.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/WfPrepareRootOperationTaskHandler.java index 083a85d7697..194d2144ccf 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/WfPrepareRootOperationTaskHandler.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/WfPrepareRootOperationTaskHandler.java @@ -38,6 +38,7 @@ import javax.annotation.PostConstruct; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -83,6 +84,8 @@ public TaskRunResult run(Task task) { LensContext rootContext = (LensContext) rootWfTask.retrieveModelContext(result); + List deltasToMerge = new ArrayList<>(); + boolean changed = false; for (WfTask child : children) { @@ -100,41 +103,49 @@ public TaskRunResult run(Task task) { LOGGER.trace("Child job {} returned {} deltas", child, deltas != null ? deltas.getDeltaList().size() : 0); } if (deltas != null) { - LensFocusContext focusContext = rootContext.getFocusContext(); - ObjectDelta focusDelta = deltas.getFocusChange(); - if (focusDelta != null) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Adding delta from job {} to root model context; delta = {}", child, focusDelta.debugDump(0)); - } - if (focusContext.getPrimaryDelta() != null && !focusContext.getPrimaryDelta().isEmpty()) { - focusContext.addPrimaryDelta(focusDelta); - } else { - focusContext.setPrimaryDelta(focusDelta); - } - changed = true; - } - Set>> entries = deltas.getProjectionChangeMapEntries(); - for (Map.Entry> entry : entries) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Adding projection delta from job {} to root model context; rsd = {}, delta = {}", child, entry.getKey(), - entry.getValue().debugDump()); - } - ModelProjectionContext projectionContext = rootContext.findProjectionContext(entry.getKey()); - if (projectionContext == null) { - // TODO more liberal treatment? - throw new IllegalStateException("No projection context for " + entry.getKey()); - } - if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { - projectionContext.addPrimaryDelta(entry.getValue()); - } else { - projectionContext.setPrimaryDelta(entry.getValue()); - } - changed = true; + if (deltas.getFocusChange() != null && deltas.getFocusChange().isAdd()) { + deltasToMerge.add(0, deltas); // "add" must go first + } else { + deltasToMerge.add(deltas); } } } } + for (ObjectTreeDeltas deltaToMerge : deltasToMerge) { + LensFocusContext focusContext = rootContext.getFocusContext(); + ObjectDelta focusDelta = deltaToMerge.getFocusChange(); + if (focusDelta != null) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Adding delta to root model context; delta = {}", focusDelta.debugDump(0)); + } + if (focusContext.getPrimaryDelta() != null && !focusContext.getPrimaryDelta().isEmpty()) { + focusContext.addPrimaryDelta(focusDelta); + } else { + focusContext.setPrimaryDelta(focusDelta); + } + changed = true; + } + Set>> entries = deltaToMerge.getProjectionChangeMapEntries(); + for (Map.Entry> entry : entries) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Adding projection delta to root model context; rsd = {}, delta = {}", entry.getKey(), + entry.getValue().debugDump()); + } + ModelProjectionContext projectionContext = rootContext.findProjectionContext(entry.getKey()); + if (projectionContext == null) { + // TODO more liberal treatment? + throw new IllegalStateException("No projection context for " + entry.getKey()); + } + if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { + projectionContext.addPrimaryDelta(entry.getValue()); + } else { + projectionContext.setPrimaryDelta(entry.getValue()); + } + changed = true; + } + } + if (!rootContext.hasAnyPrimaryChange()) { rootContext = null; // deletes the model context diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java index 9cb8ff3ed40..dd5c2866853 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java @@ -157,7 +157,7 @@ public PrimaryChangeProcessor getChangeProcessor() { @Override public boolean isEnabledByDefault() { - return false; // overriden in selected aspects + return false; // overridden in selected aspects } @Override diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspect.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspect.java index 6ddf47da2c5..7177469c6e1 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspect.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspect.java @@ -25,6 +25,7 @@ import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction; import com.evolveum.midpoint.wf.impl.processors.primary.PcpWfTask; 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.PrimaryChangeProcessorConfigurationType; import org.jetbrains.annotations.NotNull; @@ -64,8 +65,8 @@ public interface PrimaryChangeAspect { * @return list of start process instructions @see WfTaskCreationInstruction */ @NotNull - List prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas, - ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException, ObjectNotFoundException; + List> prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas, + ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException, ObjectNotFoundException; /** * On process instance end, prepares deltaOut based in deltaIn and information gathered during approval process. diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java new file mode 100644 index 00000000000..332cebe377f --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.wf.impl.processors.primary.policy; + +import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.prism.Objectable; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; +import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; +import com.evolveum.midpoint.prism.delta.builder.S_ValuesEntry; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.schema.ObjectTreeDeltas; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ApprovalSchemaHelper; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ItemApprovalProcessInterface; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ItemApprovalSpecificContent; +import com.evolveum.midpoint.wf.impl.processors.BaseConfigurationHelper; +import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext; +import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction; +import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.Validate; +import org.apache.velocity.util.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; +import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName; +import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectOid; + +/** + * Part of PolicyRuleBasedAspect related to assignments. + * + * @author mederly + */ +@Component +public class AssignmentPolicyAspectPart { + + private static final Trace LOGGER = TraceManager.getTrace(AssignmentPolicyAspectPart.class); + + @Autowired private PolicyRuleBasedAspect main; + @Autowired protected ApprovalSchemaHelper approvalSchemaHelper; + @Autowired protected MiscDataUtil miscDataUtil; + @Autowired protected PrismContext prismContext; + @Autowired protected ItemApprovalProcessInterface itemApprovalProcessInterface; + @Autowired protected BaseConfigurationHelper baseConfigurationHelper; + + void extractAssignmentBasedInstructions(ObjectTreeDeltas objectTreeDeltas, PrismObject requester, + List> instructions, ModelInvocationContext ctx, OperationResult result) + throws SchemaException { + + DeltaSetTriple evaluatedAssignmentTriple = ((LensContext) ctx.modelContext).getEvaluatedAssignmentTriple(); + LOGGER.trace("Processing evaluatedAssignmentTriple:\n{}", DebugUtil.debugDumpLazily(evaluatedAssignmentTriple)); + if (evaluatedAssignmentTriple == null) { + return; + } + + for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getPlusSet()) { + CollectionUtils.addIgnoreNull(instructions, + createInstructionFromAssignment(newAssignment, PlusMinusZero.PLUS, objectTreeDeltas, requester, ctx, result)); + } + for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getMinusSet()) { + CollectionUtils.addIgnoreNull(instructions, + createInstructionFromAssignment(newAssignment, PlusMinusZero.MINUS, objectTreeDeltas, requester, ctx, result)); + } + // Note: to implement assignment modifications we would need to fix subtractFromModification method below + } + + private PcpChildWfTaskCreationInstruction createInstructionFromAssignment( + EvaluatedAssignment evaluatedAssignment, PlusMinusZero assignmentMode, @NotNull ObjectTreeDeltas objectTreeDeltas, + PrismObject requester, ModelInvocationContext ctx, OperationResult result) throws SchemaException { + + assert assignmentMode == PlusMinusZero.PLUS || assignmentMode == PlusMinusZero.MINUS; + + // We collect all target rules; hoping that only relevant ones are triggered. + // For example, if we have assignment policy rule on induced role, it will get here. + // But projector will take care not to trigger it unless the rule is capable (e.g. configured) + // to be triggered in such a situation + List triggeredApprovalActionRules = main.selectTriggeredApprovalActionRules(evaluatedAssignment.getAllTargetsPolicyRules()); + logApprovalActions(evaluatedAssignment, triggeredApprovalActionRules, assignmentMode); + + // Currently we can deal only with assignments that have a specific target + PrismObject targetObject = evaluatedAssignment.getTarget(); + if (targetObject == null) { + if (!triggeredApprovalActionRules.isEmpty()) { + throw new IllegalStateException("No target in " + evaluatedAssignment + ", but with " + + triggeredApprovalActionRules.size() + " triggered approval action rule(s)"); + } else { + return null; + } + } + + // Let's construct the approval schema plus supporting triggered approval policy rule information + ApprovalSchemaBuilder.Result approvalSchemaResult = createSchemaWithRules(triggeredApprovalActionRules, assignmentMode, + evaluatedAssignment, ctx, result); + if (approvalSchemaHelper.shouldBeSkipped(approvalSchemaResult.schemaType)) { + return null; + } + + // Cut assignment from delta, prepare task instruction + @SuppressWarnings("unchecked") + PrismContainerValue assignmentValue = evaluatedAssignment.getAssignmentType().asPrismContainerValue(); + boolean assignmentRemoved; + switch (assignmentMode) { + case PLUS: assignmentRemoved = false; break; + case MINUS: assignmentRemoved = true; break; + default: throw new UnsupportedOperationException("Processing assignment zero set is not yet supported."); + } + boolean removed = objectTreeDeltas.subtractFromFocusDelta(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, + false); + if (!removed) { + ObjectDelta secondaryDelta = ctx.modelContext.getFocusContext().getSecondaryDelta(); + if (secondaryDelta != null && secondaryDelta.subtract(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, true)) { + LOGGER.trace("Assignment to be added/deleted was not found in primary delta. It is present in secondary delta, so there's nothing to be approved."); + return null; + } + String message = "Assignment to be added/deleted was not found in primary nor secondary delta." + + "\nAssignment:\n" + assignmentValue.debugDump() + + "\nPrimary delta:\n" + objectTreeDeltas.debugDump(); + throw new IllegalStateException(message); + } + ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); + if (focusDelta.isAdd()) { + miscDataUtil.generateFocusOidIfNeeded(ctx.modelContext, focusDelta); + } + return prepareAssignmentRelatedTaskInstruction(approvalSchemaResult, evaluatedAssignment, assignmentRemoved, ctx.modelContext, requester, result); + } + + private void logApprovalActions(EvaluatedAssignment newAssignment, + List triggeredApprovalActionRules, PlusMinusZero plusMinusZero) { + if (LOGGER.isDebugEnabled() && !triggeredApprovalActionRules.isEmpty()) { + LOGGER.trace("-------------------------------------------------------------"); + LOGGER.debug("Assignment to be {}: {}: {} this target policy rules, {} triggered approval actions:", + plusMinusZero == PlusMinusZero.PLUS ? "added" : "deleted", + newAssignment, newAssignment.getThisTargetPolicyRules().size(), triggeredApprovalActionRules.size()); + for (EvaluatedPolicyRule t : triggeredApprovalActionRules) { + LOGGER.debug(" - Approval actions: {}", t.getActions().getApproval()); + for (EvaluatedPolicyRuleTrigger trigger : t.getTriggers()) { + LOGGER.debug(" - {}", trigger); + } + } + } + } + + private ApprovalSchemaBuilder.Result createSchemaWithRules(List triggeredApprovalRules, + PlusMinusZero assignmentMode, @NotNull EvaluatedAssignment evaluatedAssignment, ModelInvocationContext ctx, + OperationResult result) throws SchemaException { + + PrismObject targetObject = evaluatedAssignment.getTarget(); + ApprovalSchemaBuilder builder = new ApprovalSchemaBuilder(main, approvalSchemaHelper); + + // (1) legacy approvers (only if adding) + LegacyApproversSpecificationUsageType configuredUseLegacyApprovers = + baseConfigurationHelper.getUseLegacyApproversSpecification(ctx.wfConfiguration); + boolean useLegacyApprovers = configuredUseLegacyApprovers == LegacyApproversSpecificationUsageType.ALWAYS + || configuredUseLegacyApprovers == LegacyApproversSpecificationUsageType.IF_NO_EXPLICIT_APPROVAL_POLICY_ACTION + && triggeredApprovalRules.isEmpty(); + + if (assignmentMode == PlusMinusZero.PLUS && useLegacyApprovers && targetObject.asObjectable() instanceof AbstractRoleType) { + AbstractRoleType abstractRole = (AbstractRoleType) targetObject.asObjectable(); + if (abstractRole.getApprovalSchema() != null) { + builder.addPredefined(targetObject, abstractRole.getApprovalSchema().clone()); + LOGGER.trace("Added legacy approval schema for {}", evaluatedAssignment); + } else if (!abstractRole.getApproverRef().isEmpty() || !abstractRole.getApproverExpression().isEmpty()) { + ApprovalStageDefinitionType level = new ApprovalStageDefinitionType(prismContext); + level.getApproverRef().addAll(CloneUtil.cloneCollectionMembers(abstractRole.getApproverRef())); + level.getApproverExpression().addAll(CloneUtil.cloneCollectionMembers(abstractRole.getApproverExpression())); + level.setAutomaticallyApproved(abstractRole.getAutomaticallyApproved()); + // consider default (if expression returns no approvers) -- currently it is "reject"; it is probably correct + builder.addPredefined(targetObject, level); + LOGGER.trace("Added legacy approval schema (from approverRef, approverExpression, automaticallyApproved) for {}", evaluatedAssignment); + } + } + + // (2) default policy action (only if adding) + if (triggeredApprovalRules.isEmpty() && assignmentMode == PlusMinusZero.PLUS + && baseConfigurationHelper.getUseDefaultApprovalPolicyRules(ctx.wfConfiguration) != DefaultApprovalPolicyRulesUsageType.NEVER) { + if (builder.addPredefined(targetObject, SchemaConstants.ORG_APPROVER, result)) { + LOGGER.trace("Added default approval action, as no explicit one was found for {}", evaluatedAssignment); + } + } + + // (3) actions from triggered rules + for (EvaluatedPolicyRule approvalRule : triggeredApprovalRules) { + for (ApprovalPolicyActionType approvalAction : approvalRule.getActions().getApproval()) { + builder.add(main.getSchemaFromAction(approvalAction), approvalAction.getCompositionStrategy(), targetObject, approvalRule); + } + } + return builder.buildSchema(ctx, result); + } + + private PcpChildWfTaskCreationInstruction prepareAssignmentRelatedTaskInstruction( + ApprovalSchemaBuilder.Result builderResult, + EvaluatedAssignment evaluatedAssignment, boolean assignmentRemoved, ModelContext modelContext, + PrismObject requester, OperationResult result) throws SchemaException { + + String objectOid = getFocusObjectOid(modelContext); + String objectName = getFocusObjectName(modelContext); + + @SuppressWarnings("unchecked") + PrismObject target = (PrismObject) evaluatedAssignment.getTarget(); + Validate.notNull(target, "assignment target is null"); + + String targetName = target.getName() != null ? target.getName().getOrig() : "(unnamed)"; + String operation = (assignmentRemoved + ? "unassigning " + targetName + " from " : + "assigning " + targetName + " to ") + + objectName; + String approvalTaskName = "Approve " + operation; + + PcpChildWfTaskCreationInstruction instruction = + PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(main.getChangeProcessor(), approvalTaskName, + builderResult.schemaType, builderResult.attachedRules); + + instruction.prepareCommonAttributes(main, modelContext, requester); + + ObjectDelta delta = assignmentToDelta(modelContext.getFocusClass(), + evaluatedAssignment.getAssignmentType(), assignmentRemoved, objectOid); + instruction.setDeltasToProcess(delta); + + instruction.setObjectRef(modelContext, result); + instruction.setTargetRef(createObjectRef(target), result); + + String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : ""; + instruction.setTaskName("Approval " + andExecuting + "of " + operation); + instruction.setProcessInstanceName(StringUtils.capitalizeFirstLetter(operation)); + + itemApprovalProcessInterface.prepareStartInstruction(instruction); + + return instruction; + } + + // creates an ObjectDelta that will be executed after successful approval of the given assignment + @SuppressWarnings("unchecked") + private ObjectDelta assignmentToDelta(Class focusClass, + AssignmentType assignmentType, boolean assignmentRemoved, String objectOid) throws SchemaException { + PrismContainerValue value = assignmentType.clone().asPrismContainerValue(); + S_ValuesEntry item = DeltaBuilder.deltaFor(focusClass, prismContext) + .item(FocusType.F_ASSIGNMENT); + S_ItemEntry op = assignmentRemoved ? item.delete(value) : item.add(value); + return (ObjectDelta) op.asObjectDelta(objectOid); + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java new file mode 100644 index 00000000000..136866ce16b --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.wf.impl.processors.primary.policy; + +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.ObjectTreeDeltas; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.OidUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ApprovalSchemaHelper; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ItemApprovalProcessInterface; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ItemApprovalSpecificContent; +import com.evolveum.midpoint.wf.impl.processors.BaseConfigurationHelper; +import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext; +import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction; +import com.evolveum.midpoint.wf.impl.processors.primary.policy.ProcessSpecifications.ProcessSpecification; +import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.velocity.util.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static com.evolveum.midpoint.util.DebugUtil.debugDumpLazily; +import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName; +import static java.util.Collections.singletonList; + +/** + * @author mederly + */ +@Component +public class ObjectPolicyAspectPart { + + private static final Trace LOGGER = TraceManager.getTrace(ObjectPolicyAspectPart.class); + + @Autowired private PolicyRuleBasedAspect main; + @Autowired protected ApprovalSchemaHelper approvalSchemaHelper; + @Autowired protected MiscDataUtil miscDataUtil; + @Autowired protected PrismContext prismContext; + @Autowired protected ItemApprovalProcessInterface itemApprovalProcessInterface; + @Autowired protected BaseConfigurationHelper baseConfigurationHelper; + + void extractObjectBasedInstructions(@NotNull ObjectTreeDeltas objectTreeDeltas, + @Nullable PrismObject requester, @NotNull List> instructions, + @NotNull ModelInvocationContext ctx, @NotNull OperationResult result) + throws SchemaException { + + ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); + LensFocusContext focusContext = (LensFocusContext) ctx.modelContext.getFocusContext(); + PrismObject object = focusContext.getObjectOld() != null ? + focusContext.getObjectOld() : focusContext.getObjectNew(); + + List triggeredApprovalActionRules = main.selectTriggeredApprovalActionRules(focusContext.getPolicyRules()); + LOGGER.trace("extractObjectBasedInstructions: triggeredApprovalActionRules:\n{}", debugDumpLazily(triggeredApprovalActionRules)); + + // default rule + if (!triggeredApprovalActionRules.isEmpty()) { + addObjectOidIfNeeded(focusDelta, ctx.modelContext); + ProcessSpecifications processSpecifications = ProcessSpecifications.createFromRules(triggeredApprovalActionRules); + for (ProcessSpecification processSpecificationEntry : processSpecifications.getSpecifications()) { + if (focusDelta.isEmpty()) { + break; // we're done + } + WfProcessSpecificationType processSpecification = processSpecificationEntry.basicSpec; + List> deltasToApprove = getDeltasToApprove(focusDelta, processSpecification); + LOGGER.trace("Deltas to approve:\n{}", debugDumpLazily(deltasToApprove)); + if (deltasToApprove.isEmpty()) { + continue; + } + LOGGER.trace("Remaining delta:\n{}", debugDumpLazily(focusDelta)); + ApprovalSchemaBuilder builder = new ApprovalSchemaBuilder(main, approvalSchemaHelper); + for (Pair actionWithRule : processSpecificationEntry.actionsWithRules) { + ApprovalPolicyActionType approvalAction = actionWithRule.getLeft(); + builder.add(main.getSchemaFromAction(approvalAction), approvalAction.getCompositionStrategy(), object, actionWithRule.getRight()); + } + buildSchemaForObject(requester, instructions, ctx, result, deltasToApprove, builder); + } + } else if (baseConfigurationHelper.getUseDefaultApprovalPolicyRules(ctx.wfConfiguration) != DefaultApprovalPolicyRulesUsageType.NEVER) { + ApprovalSchemaBuilder builder = new ApprovalSchemaBuilder(main, approvalSchemaHelper); + if (builder.addPredefined(object, SchemaConstants.ORG_OWNER, result)) { + LOGGER.trace("Added default approval action, as no explicit one was found"); + addObjectOidIfNeeded(focusDelta, ctx.modelContext); + List> deltasToApprove = singletonList(focusDelta.clone()); + focusDelta.clear(); + buildSchemaForObject(requester, instructions, ctx, result, deltasToApprove, builder); + } + } + } + + private void addObjectOidIfNeeded(ObjectDelta focusDelta, ModelContext modelContext) { + if (focusDelta.isAdd()) { + if (focusDelta.getObjectToAdd().getOid() == null) { + String newOid = OidUtil.generateOid(); + focusDelta.getObjectToAdd().setOid(newOid); + ((LensFocusContext) modelContext.getFocusContext()).setOid(newOid); + } + } + } + + private void buildSchemaForObject(PrismObject requester, + List> instructions, ModelInvocationContext ctx, + @NotNull OperationResult result, List> deltasToApprove, + ApprovalSchemaBuilder builder) throws SchemaException { + ApprovalSchemaBuilder.Result builderResult = builder.buildSchema(ctx, result); + if (!approvalSchemaHelper.shouldBeSkipped(builderResult.schemaType)) { + prepareObjectRelatedTaskInstructions(instructions, builderResult, deltasToApprove, ctx.modelContext, requester, result); + } + } + + private List> getDeltasToApprove(ObjectDelta focusDelta, WfProcessSpecificationType processSpecification) + throws SchemaException { + List> rv = new ArrayList<>(); + if (processSpecification == null || processSpecification.getDeltaFrom().isEmpty()) { + return addWholeDelta(focusDelta, rv); + } + for (DeltaSourceSpecificationType sourceSpec : processSpecification.getDeltaFrom()) { + if (sourceSpec == null || sourceSpec.getItem().isEmpty() && sourceSpec.getItemValue() == null) { + return addWholeDelta(focusDelta, rv); + } else if (!sourceSpec.getItem().isEmpty()) { + ObjectDelta.FactorOutResult out = focusDelta.factorOut(ItemPathType.toItemPathList(sourceSpec.getItem()), false); + rv.addAll(out.offsprings); + } else { + assert sourceSpec.getItemValue() != null; + ObjectDelta.FactorOutResult out = focusDelta.factorOutValues(sourceSpec.getItemValue().getItemPath(), false); + rv.addAll(out.offsprings); + } + } + return rv; + } + + @NotNull + private List> addWholeDelta(ObjectDelta focusDelta, List> rv) { + rv.add(focusDelta.clone()); + focusDelta.clear(); + return rv; + } + + private void prepareObjectRelatedTaskInstructions( + List> instructions, ApprovalSchemaBuilder.Result builderResult, + List> deltasToApprove, ModelContext modelContext, + PrismObject requester, OperationResult result) throws SchemaException { + + for (ObjectDelta deltaToApprove : deltasToApprove) { + //String objectOid = getFocusObjectOid(modelContext); + String objectName = getFocusObjectName(modelContext); + + String opName; + if (deltaToApprove.isAdd()) { + opName = "addition"; + } else if (deltaToApprove.isDelete()) { + opName = "deletion"; + } else { + opName = "modification"; + } + + String approvalTaskName = "Approve " + opName + " of " + objectName; + + PcpChildWfTaskCreationInstruction instruction = + PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(main.getChangeProcessor(), approvalTaskName, + builderResult.schemaType, builderResult.attachedRules); + + instruction.prepareCommonAttributes(main, modelContext, requester); + + instruction.setDeltasToProcess(deltaToApprove); + + instruction.setObjectRef(modelContext, result); + + String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : ""; + instruction.setTaskName("Approval " + andExecuting + "of " + opName + " of " + objectName); + instruction.setProcessInstanceName(StringUtils.capitalizeFirstLetter(opName) + " of " + objectName); + + itemApprovalProcessInterface.prepareStartInstruction(instruction); + + instructions.add(instruction); + } + } + +// private ObjectDelta subtractModifications(@NotNull ObjectDelta focusDelta, @NotNull Set itemPaths) { +// if (itemPaths.isEmpty()) { +// ObjectDelta originalDelta = focusDelta.clone(); +// focusDelta.clear(); +// return originalDelta; +// } +// if (!focusDelta.isModify()) { +// throw new IllegalStateException("Not a MODIFY delta; delta = " + focusDelta); +// } +// return focusDelta.subtract(itemPaths); +// } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java index 51d46bf1f76..b6634543e1a 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java @@ -16,52 +16,28 @@ package com.evolveum.midpoint.wf.impl.processors.primary.policy; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.prism.Objectable; -import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.ChangeType; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PlusMinusZero; -import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; -import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; -import com.evolveum.midpoint.prism.delta.builder.S_ValuesEntry; -import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.schema.ObjectTreeDeltas; -import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.OidUtil; -import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.impl.processes.itemApproval.*; import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext; import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction; import com.evolveum.midpoint.wf.impl.processors.primary.aspect.BasePrimaryChangeAspect; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.Validate; -import org.apache.velocity.util.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; -import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName; -import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectOid; - /** * * @author mederly @@ -69,13 +45,12 @@ @Component public class PolicyRuleBasedAspect extends BasePrimaryChangeAspect { + @SuppressWarnings("unused") private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleBasedAspect.class); - @Autowired - protected PrismContext prismContext; - - @Autowired - private ItemApprovalProcessInterface itemApprovalProcessInterface; + @Autowired protected PrismContext prismContext; + @Autowired private AssignmentPolicyAspectPart assignmentPolicyAspectPart; + @Autowired private ObjectPolicyAspectPart objectPolicyAspectPart; //region ------------------------------------------------------------ Things that execute on request arrival @@ -91,152 +66,25 @@ protected boolean isFirst() { @NotNull @Override - public List prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas, - ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException { + public List> prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas, + ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException { - List instructions = new ArrayList<>(); + List> instructions = new ArrayList<>(); if (objectTreeDeltas.getFocusChange() != null) { PrismObject requester = baseModelInvocationProcessingHelper.getRequester(ctx.taskFromModel, result); - extractAssignmentBasedInstructions(objectTreeDeltas, requester, instructions, ctx, result); - extractObjectBasedInstructions(objectTreeDeltas, requester, instructions, ctx, result); + assignmentPolicyAspectPart.extractAssignmentBasedInstructions(objectTreeDeltas, requester, instructions, ctx, result); + objectPolicyAspectPart.extractObjectBasedInstructions(objectTreeDeltas, requester, instructions, ctx, result); } return instructions; } - private void extractAssignmentBasedInstructions(ObjectTreeDeltas objectTreeDeltas, PrismObject requester, - List instructions, ModelInvocationContext ctx, OperationResult result) - throws SchemaException { - - DeltaSetTriple evaluatedAssignmentTriple = ((LensContext) ctx.modelContext).getEvaluatedAssignmentTriple(); - LOGGER.trace("Processing evaluatedAssignmentTriple:\n{}", DebugUtil.debugDumpLazily(evaluatedAssignmentTriple)); - if (evaluatedAssignmentTriple == null) { - return; - } - - for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getPlusSet()) { - CollectionUtils.addIgnoreNull(instructions, - createInstructionFromAssignment(newAssignment, PlusMinusZero.PLUS, objectTreeDeltas, requester, ctx, result)); - } - for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getMinusSet()) { - CollectionUtils.addIgnoreNull(instructions, - createInstructionFromAssignment(newAssignment, PlusMinusZero.MINUS, objectTreeDeltas, requester, ctx, result)); - } - // Note: to implement assignment modifications we would need to fix subtractFromModification method below - } - - private PcpChildWfTaskCreationInstruction createInstructionFromAssignment( - EvaluatedAssignment evaluatedAssignment, PlusMinusZero plusMinusZero, @NotNull ObjectTreeDeltas objectTreeDeltas, - PrismObject requester, ModelInvocationContext ctx, OperationResult result) throws SchemaException { - - assert plusMinusZero == PlusMinusZero.PLUS || plusMinusZero == PlusMinusZero.MINUS; - - // We collect all target rules; hoping that only relevant ones are triggered. - // For example, if we have assignment policy rule on induced role, it will get here. - // But projector will take care not to trigger it unless the rule is capable (e.g. configured) - // to be triggered in such a situation - List triggeredApprovalActionRules = getApprovalActionRules(evaluatedAssignment.getAllTargetsPolicyRules()); - logApprovalActions(evaluatedAssignment, triggeredApprovalActionRules, plusMinusZero); - - // Currently we can deal only with assignments that have a specific target - PrismObject targetObject = evaluatedAssignment.getTarget(); - if (targetObject == null) { - if (!triggeredApprovalActionRules.isEmpty()) { - throw new IllegalStateException("No target in " + evaluatedAssignment + ", but with " - + triggeredApprovalActionRules.size() + " triggered approval action rule(s)"); - } else { - return null; - } - } - - // Let's construct the approval schema plus supporting triggered approval policy rule information - ApprovalSchemaBuilder.Result approvalSchemaResult = createSchemaWithRules(triggeredApprovalActionRules, plusMinusZero, - evaluatedAssignment, ctx, result); - if (approvalSchemaHelper.shouldBeSkipped(approvalSchemaResult.schemaType)) { - return null; - } - - // Cut assignment from delta, prepare task instruction - @SuppressWarnings("unchecked") - PrismContainerValue assignmentValue = evaluatedAssignment.getAssignmentType().asPrismContainerValue(); - boolean assignmentRemoved; - switch (plusMinusZero) { - case PLUS: assignmentRemoved = false; break; - case MINUS: assignmentRemoved = true; break; - default: throw new UnsupportedOperationException("Processing assignment zero set is not yet supported."); - } - boolean removed = objectTreeDeltas.subtractFromFocusDelta(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, - false); - if (!removed) { - ObjectDelta secondaryDelta = ctx.modelContext.getFocusContext().getSecondaryDelta(); - if (secondaryDelta != null && secondaryDelta.subtract(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, true)) { - LOGGER.trace("Assignment to be added/deleted was not found in primary delta. It is present in secondary delta, so there's nothing to be approved."); - return null; - } - String message = "Assignment to be added/deleted was not found in primary nor secondary delta." - + "\nAssignment:\n" + assignmentValue.debugDump() - + "\nPrimary delta:\n" + objectTreeDeltas.debugDump(); - throw new IllegalStateException(message); - } - ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); - if (focusDelta.isAdd()) { - miscDataUtil.generateFocusOidIfNeeded(ctx.modelContext, focusDelta); - } - return prepareAssignmentRelatedTaskInstruction(approvalSchemaResult, evaluatedAssignment, assignmentRemoved, ctx.modelContext, requester, result); - } - - private List getApprovalActionRules(Collection rules) { + List selectTriggeredApprovalActionRules(Collection rules) { return rules.stream() .filter(r -> r.isTriggered() && r.getActions() != null && !r.getActions().getApproval().isEmpty()) .collect(Collectors.toList()); } - private ApprovalSchemaBuilder.Result createSchemaWithRules(List triggeredApprovalRules, - PlusMinusZero plusMinusZero, @NotNull EvaluatedAssignment evaluatedAssignment, ModelInvocationContext ctx, OperationResult result) throws SchemaException { - - PrismObject targetObject = evaluatedAssignment.getTarget(); - ApprovalSchemaBuilder builder = new ApprovalSchemaBuilder(this, approvalSchemaHelper); - - // (1) legacy approvers (only if adding) - LegacyApproversSpecificationUsageType configuredUseLegacyApprovers = - baseConfigurationHelper.getUseLegacyApproversSpecification(ctx.wfConfiguration); - boolean useLegacyApprovers = configuredUseLegacyApprovers == LegacyApproversSpecificationUsageType.ALWAYS - || configuredUseLegacyApprovers == LegacyApproversSpecificationUsageType.IF_NO_EXPLICIT_APPROVAL_POLICY_ACTION - && triggeredApprovalRules.isEmpty(); - - if (plusMinusZero == PlusMinusZero.PLUS && useLegacyApprovers && targetObject.asObjectable() instanceof AbstractRoleType) { - AbstractRoleType abstractRole = (AbstractRoleType) targetObject.asObjectable(); - if (abstractRole.getApprovalSchema() != null) { - builder.addPredefined(targetObject, abstractRole.getApprovalSchema().clone()); - LOGGER.trace("Added legacy approval schema for {}", evaluatedAssignment); - } else if (!abstractRole.getApproverRef().isEmpty() || !abstractRole.getApproverExpression().isEmpty()) { - ApprovalStageDefinitionType level = new ApprovalStageDefinitionType(prismContext); - level.getApproverRef().addAll(CloneUtil.cloneCollectionMembers(abstractRole.getApproverRef())); - level.getApproverExpression().addAll(CloneUtil.cloneCollectionMembers(abstractRole.getApproverExpression())); - level.setAutomaticallyApproved(abstractRole.getAutomaticallyApproved()); - // consider default (if expression returns no approvers) -- currently it is "reject"; it is probably correct - builder.addPredefined(targetObject, level); - LOGGER.trace("Added legacy approval schema (from approverRef, approverExpression, automaticallyApproved) for {}", evaluatedAssignment); - } - } - - // (2) default policy action (only if adding) - if (triggeredApprovalRules.isEmpty() && plusMinusZero == PlusMinusZero.PLUS - && baseConfigurationHelper.getUseDefaultApprovalPolicyRules(ctx.wfConfiguration) != DefaultApprovalPolicyRulesUsageType.NEVER) { - if (builder.addPredefined(targetObject, SchemaConstants.ORG_APPROVER, result)) { - LOGGER.trace("Added default approval action, as no explicit one was found for {}", evaluatedAssignment); - } - } - - // (3) actions from triggered rules - for (EvaluatedPolicyRule approvalRule : triggeredApprovalRules) { - for (ApprovalPolicyActionType approvalAction : approvalRule.getActions().getApproval()) { - builder.add(getSchemaFromAction(approvalAction), approvalAction.getCompositionStrategy(), targetObject, approvalRule); - } - } - return builder.buildSchema(ctx, result); - } - - private ApprovalSchemaType getSchemaFromAction(@NotNull ApprovalPolicyActionType approvalAction) { + ApprovalSchemaType getSchemaFromAction(@NotNull ApprovalPolicyActionType approvalAction) { // TODO approval process if (approvalAction.getApprovalSchema() != null) { return approvalAction.getApprovalSchema().clone(); @@ -252,224 +100,5 @@ private ApprovalSchemaType getSchemaFromAction(@NotNull ApprovalPolicyActionType return rv; } } - - private void logApprovalActions(EvaluatedAssignment newAssignment, - List triggeredApprovalActionRules, PlusMinusZero plusMinusZero) { - if (LOGGER.isDebugEnabled() && !triggeredApprovalActionRules.isEmpty()) { - LOGGER.trace("-------------------------------------------------------------"); - LOGGER.debug("Assignment to be {}: {}: {} this target policy rules, {} triggered approval actions:", - plusMinusZero == PlusMinusZero.PLUS ? "added" : "deleted", - newAssignment, newAssignment.getThisTargetPolicyRules().size(), triggeredApprovalActionRules.size()); - for (EvaluatedPolicyRule t : triggeredApprovalActionRules) { - LOGGER.debug(" - Approval actions: {}", t.getActions().getApproval()); - for (EvaluatedPolicyRuleTrigger trigger : t.getTriggers()) { - LOGGER.debug(" - {}", trigger); - } - } - } - } - - private void extractObjectBasedInstructions(@NotNull ObjectTreeDeltas objectTreeDeltas, PrismObject requester, - List instructions, ModelInvocationContext ctx, @NotNull OperationResult result) - throws SchemaException { - - ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); - LensFocusContext focusContext = (LensFocusContext) ctx.modelContext.getFocusContext(); - PrismObject object = focusContext.getObjectOld() != null ? - focusContext.getObjectOld() : focusContext.getObjectNew(); - Map, ApprovalSchemaBuilder> schemaBuilders = new HashMap<>(); - - List approvalActionRules = getApprovalActionRules(focusContext.getPolicyRules()); - LOGGER.trace("extractObjectBasedInstructions: approvalActionRules:\n{}", DebugUtil.debugDumpLazily(approvalActionRules)); - for (EvaluatedPolicyRule rule : approvalActionRules) { - Set key = Collections.emptySet();; -// if (focusDelta.isAdd() || focusDelta.isDelete()) { -// key = Collections.emptySet(); -// } else { -// Set items = getAffectedItems(rule.getTriggers()); -// Set affectedItems; -// if (!items.isEmpty()) { -// affectedItems = items; // all items in triggered constraints were modified (that's how the constraints work) -// } else { -// affectedItems = new HashSet<>(focusDelta.getModifiedItems()); // whole object -// } -// key = affectedItems; -// } - ApprovalSchemaBuilder builder = schemaBuilders.computeIfAbsent(key, k -> new ApprovalSchemaBuilder(this, - approvalSchemaHelper)); - for (ApprovalPolicyActionType approvalAction : rule.getActions().getApproval()) { - builder.add(getSchemaFromAction(approvalAction), approvalAction.getCompositionStrategy(), object, rule); - } - } - // default rule - if (approvalActionRules.isEmpty() - && baseConfigurationHelper.getUseDefaultApprovalPolicyRules(ctx.wfConfiguration) != DefaultApprovalPolicyRulesUsageType.NEVER) { - ApprovalSchemaBuilder builder = new ApprovalSchemaBuilder(this, approvalSchemaHelper); - if (builder.addPredefined(object, SchemaConstants.ORG_OWNER, result)) { - LOGGER.trace("Added default approval action, as no explicit one was found"); - schemaBuilders.put(Collections.emptySet(), builder); - } - } - // create approval requests; also test for overlaps - Set itemsProcessed = null; - for (Map.Entry, ApprovalSchemaBuilder> entry : schemaBuilders.entrySet()) { - ApprovalSchemaBuilder.Result builderResult = entry.getValue().buildSchema(ctx, result); - if (approvalSchemaHelper.shouldBeSkipped(builderResult.schemaType)) { - continue; - } - Set items = entry.getKey(); - if (itemsProcessed != null) { - if (items.isEmpty() || itemsProcessed.isEmpty() || CollectionUtils.containsAny(itemsProcessed, items)) { - throw new IllegalStateException("Overlapping modification-related policy rules. " - + "Items processed = " + itemsProcessed + ", current items = " + items); - } - itemsProcessed.addAll(items); - } else { - itemsProcessed = items; - } - instructions.add( - prepareObjectRelatedTaskInstruction(builderResult, focusDelta, items, ctx.modelContext, requester, result)); - } - } - - private Set getAffectedItems(Collection> triggers) { - Set rv = new HashSet<>(); - for (EvaluatedPolicyRuleTrigger trigger : triggers) { - if (trigger.getConstraint() instanceof ModificationPolicyConstraintType) { - ModificationPolicyConstraintType modConstraint = (ModificationPolicyConstraintType) trigger.getConstraint(); - if (modConstraint.getItem().isEmpty()) { - return Collections.emptySet(); // all items - } else { - modConstraint.getItem().forEach( - itemPathType -> rv.add(itemPathType.getItemPath())); - } - } - } - return rv; - } - - private PcpChildWfTaskCreationInstruction prepareAssignmentRelatedTaskInstruction( - ApprovalSchemaBuilder.Result builderResult, - EvaluatedAssignment evaluatedAssignment, boolean assignmentRemoved, ModelContext modelContext, - PrismObject requester, OperationResult result) throws SchemaException { - - String objectOid = getFocusObjectOid(modelContext); - String objectName = getFocusObjectName(modelContext); - - @SuppressWarnings("unchecked") - PrismObject target = (PrismObject) evaluatedAssignment.getTarget(); - Validate.notNull(target, "assignment target is null"); - - String targetName = target.getName() != null ? target.getName().getOrig() : "(unnamed)"; - String operation = (assignmentRemoved - ? "unassigning " + targetName + " from " : - "assigning " + targetName + " to ") - + objectName; - String approvalTaskName = "Approve " + operation; - - PcpChildWfTaskCreationInstruction instruction = - PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(getChangeProcessor(), approvalTaskName, - builderResult.schemaType, builderResult.attachedRules); - - instruction.prepareCommonAttributes(this, modelContext, requester); - - ObjectDelta delta = assignmentToDelta(modelContext.getFocusClass(), - evaluatedAssignment.getAssignmentType(), assignmentRemoved, objectOid); - instruction.setDeltasToProcess(delta); - - instruction.setObjectRef(modelContext, result); - instruction.setTargetRef(createObjectRef(target), result); - - String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : ""; - instruction.setTaskName("Approval " + andExecuting + "of " + operation); - instruction.setProcessInstanceName(StringUtils.capitalizeFirstLetter(operation)); - - itemApprovalProcessInterface.prepareStartInstruction(instruction); - - return instruction; - } - - private PcpChildWfTaskCreationInstruction prepareObjectRelatedTaskInstruction(ApprovalSchemaBuilder.Result builderResult, - ObjectDelta focusDelta, Set paths, ModelContext modelContext, - PrismObject requester, OperationResult result) throws SchemaException { - - //String objectOid = getFocusObjectOid(modelContext); - String objectName = getFocusObjectName(modelContext); - - String opName; - if (focusDelta.isAdd()) { - opName = "addition"; - } else if (focusDelta.isDelete()) { - opName = "deletion"; - } else { - opName = "modification"; - } - - if (focusDelta.isAdd()) { - if (focusDelta.getObjectToAdd().getOid() == null) { - String newOid = OidUtil.generateOid(); - focusDelta.getObjectToAdd().setOid(newOid); - ((LensFocusContext) modelContext.getFocusContext()).setOid(newOid); - } - } - - String approvalTaskName = "Approve " + opName + " of " + objectName; - - PcpChildWfTaskCreationInstruction instruction = - PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(getChangeProcessor(), approvalTaskName, - builderResult.schemaType, builderResult.attachedRules); - - instruction.prepareCommonAttributes(this, modelContext, requester); - - @SuppressWarnings("unchecked") - ObjectDelta delta = (ObjectDelta) subtractModifications(focusDelta, paths); - instruction.setDeltasToProcess(delta); - - instruction.setObjectRef(modelContext, result); - - String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : ""; - instruction.setTaskName("Approval " + andExecuting + "of " + opName + " of " + objectName); - instruction.setProcessInstanceName(StringUtils.capitalizeFirstLetter(opName) + " of " + objectName); - - itemApprovalProcessInterface.prepareStartInstruction(instruction); - - return instruction; - } - - private ObjectDelta subtractModifications(@NotNull ObjectDelta focusDelta, @NotNull Set itemPaths) { - if (itemPaths.isEmpty()) { - ObjectDelta originalDelta = focusDelta.clone(); - if (focusDelta.isAdd()) { - focusDelta.setObjectToAdd(null); - } else if (focusDelta.isModify()) { - focusDelta.getModifications().clear(); - } else if (focusDelta.isDelete()) { - // hack: convert to empty ADD delta - focusDelta.setChangeType(ChangeType.ADD); - focusDelta.setObjectToAdd(null); - focusDelta.setOid(null); - } else { - throw new IllegalStateException("Unsupported delta type: " + focusDelta.getChangeType()); - } - return originalDelta; - } - if (!focusDelta.isModify()) { - throw new IllegalStateException("Not a MODIFY delta; delta = " + focusDelta); - } - return focusDelta.subtract(itemPaths); - } - - // creates an ObjectDelta that will be executed after successful approval of the given assignment - @SuppressWarnings("unchecked") - private ObjectDelta assignmentToDelta(Class focusClass, - AssignmentType assignmentType, boolean assignmentRemoved, String objectOid) throws SchemaException { - PrismContainerValue value = assignmentType.clone().asPrismContainerValue(); - S_ValuesEntry item = DeltaBuilder.deltaFor(focusClass, prismContext) - .item(FocusType.F_ASSIGNMENT); - S_ItemEntry op = assignmentRemoved ? item.delete(value) : item.add(value); - return (ObjectDelta) op.asObjectDelta(objectOid); - } - - //endregion - + //endregion } \ No newline at end of file diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ProcessSpecifications.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ProcessSpecifications.java new file mode 100644 index 00000000000..e64147fbe2c --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ProcessSpecifications.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.wf.impl.processors.primary.policy; + +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalPolicyActionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WfProcessSpecificationType; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Maintains "process specifications" i.e. recipes how to + * - analyze incoming deltas (WfProcessSpecificationType.deltaFrom), + * - create approval processes + * - fill-in their approval schema (list of approval actions with policy rules) + * + * @author mederly + */ +public class ProcessSpecifications { + + private final LinkedHashMap>> specifications = new LinkedHashMap<>(); + + static class ProcessSpecification { + final WfProcessSpecificationType basicSpec; + final List> actionsWithRules; + + public ProcessSpecification(Map.Entry>> entry) { + this.basicSpec = entry.getKey(); + this.actionsWithRules = entry.getValue(); + } + } + + static ProcessSpecifications createFromRules(List rules) { + LinkedHashMap>> collected = new LinkedHashMap<>(); + for (EvaluatedPolicyRule rule : rules) { + for (ApprovalPolicyActionType approvalAction : rule.getActions().getApproval()) { + WfProcessSpecificationType spec = approvalAction.getProcessSpecification(); + collected.computeIfAbsent(spec, s -> new ArrayList<>()).add(new ImmutablePair<>(approvalAction, rule)); + } + } + // todo resolve references + // todo distribute non-process-attached actions to process-attached ones + ProcessSpecifications rv = new ProcessSpecifications(); + collected.entrySet().stream() + .sorted((ps1, ps2) -> { + Integer order1 = ps1.getKey() != null ? ps1.getKey().getOrder() : null; + Integer order2 = ps2.getKey() != null ? ps2.getKey().getOrder() : null; + if (order1 != null || order2 != null) { + return Comparator.nullsLast(Comparator.naturalOrder()).compare(order1, order2); + } else if (ps1.getKey() != null && ps2.getKey() == null) { + return -1; + } else if (ps1.getKey() == null && ps2.getKey() != null) { + return 1; + } else { + return 0; + } + }).forEach(e -> rv.specifications.put(e.getKey(), e.getValue())); + return rv; + } + + Collection getSpecifications() { + return specifications.entrySet().stream() + .map(ProcessSpecification::new) + .collect(Collectors.toList()); + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java index fd424af94d1..2e6b5112bd0 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskCreationInstruction.java @@ -63,36 +63,36 @@ public class WfTaskCreationInstruction taskOwner; // if null, owner from parent task will be taken (if there's no parent task, exception will be thrown) - private PolyStringType taskName; // name of task to be created/updated (applies only if the task has no name already) - e.g. "Approve adding role R to U" + protected PrismObject taskObject; // object to be attached to the task; this object must have its definition available + protected PrismObject taskOwner; // if null, owner from parent task will be taken (if there's no parent task, exception will be thrown) + protected PolyStringType taskName; // name of task to be created/updated (applies only if the task has no name already) - e.g. "Approve adding role R to U" - private boolean executeModelOperationHandler; // should the task contain model operation to be executed? - private boolean noProcess; // should the task provide no wf process (only direct execution of model operation)? + protected boolean executeModelOperationHandler; // should the task contain model operation to be executed? + protected boolean noProcess; // should the task provide no wf process (only direct execution of model operation)? - private boolean simple; // is workflow process simple? (i.e. such that requires periodic watching of its state) - private boolean sendStartConfirmation = true; // should we send explicit "process started" event when the process was started by midPoint? + protected boolean simple; // is workflow process simple? (i.e. such that requires periodic watching of its state) + protected boolean sendStartConfirmation = true; // should we send explicit "process started" event when the process was started by midPoint? // for listener-enabled processes this can be misleading, because "process started" event could come // after "process finished" one (for immediately-finishing processes) // // unfortunately, it seems we have to live with this (unless we define a "process started" listener) - private TaskExecutionStatus taskInitialState = TaskExecutionStatus.RUNNABLE; + protected TaskExecutionStatus taskInitialState = TaskExecutionStatus.RUNNABLE; // what should be executed at a given occasion (in the order of being in this list) - private final List handlersAfterModelOperation = new ArrayList<>(); - private final List handlersBeforeModelOperation = new ArrayList<>(); - private final List handlersAfterWfProcess = new ArrayList<>(); + protected final List handlersAfterModelOperation = new ArrayList<>(); + protected final List handlersBeforeModelOperation = new ArrayList<>(); + protected final List handlersAfterWfProcess = new ArrayList<>(); //region Constructors protected WfTaskCreationInstruction(ChangeProcessor changeProcessor, PRC processorContent, PCS processContent) { diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java index e63d4f52b46..8edc5817ac1 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java @@ -580,6 +580,7 @@ protected void executeTest(String testName, TestDetails te assertNotNull("Root task OID is not set in model task", rootTaskOid); Task rootTask = taskManager.getTask(rootTaskOid, result); + display("Root task after first clockwork.run", rootTask); assertTrue("Root task is not persistent", rootTask.isPersistent()); UriStack uriStack = rootTask.getOtherHandlersUriStack(); @@ -997,7 +998,7 @@ protected void assertNoObject(PrismObject object) throws S protected void assertObject(T object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { PrismObject objectFromRepo = searchObjectByName((Class) object.getClass(), object.getName().getOrig()); - assertNotNull("Object " + object + " was not created", object); + assertNotNull("Object " + object + " was not created", objectFromRepo); objectFromRepo.removeItem(new ItemPath(ObjectType.F_METADATA), Item.class); objectFromRepo.removeItem(new ItemPath(ObjectType.F_OPERATION_EXECUTION), Item.class); assertEquals("Object is different from the one that was expected", object, objectFromRepo.asObjectable()); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/AbstractTestLifecycle.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/AbstractTestLifecycle.java index 48f553c47fb..11d1476665c 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/AbstractTestLifecycle.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/AbstractTestLifecycle.java @@ -59,8 +59,10 @@ public abstract class AbstractTestLifecycle extends AbstractWfTestPolicy { protected static final File TEST_LIFECYCLE_RESOURCE_DIR = new File("src/test/resources/policy/lifecycle"); protected static final File USER_PIRATE_OWNER_FILE = new File(TEST_LIFECYCLE_RESOURCE_DIR, "user-pirate-owner.xml"); + protected static final File USER_JUDGE_OWNER_FILE = new File(TEST_LIFECYCLE_RESOURCE_DIR, "user-judge-owner.xml"); protected String userPirateOwnerOid; + protected String userJudgeOwnerOid; String rolePirateOid; @@ -68,6 +70,7 @@ public abstract class AbstractTestLifecycle extends AbstractWfTestPolicy { public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); userPirateOwnerOid = addAndRecomputeUser(USER_PIRATE_OWNER_FILE, initTask, initResult); + userJudgeOwnerOid = addAndRecomputeUser(USER_JUDGE_OWNER_FILE, initTask, initResult); } protected boolean approveObjectAdd() { diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java index 0a990e9d796..8ec80873cbb 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java @@ -16,20 +16,34 @@ package com.evolveum.midpoint.wf.impl.policy.lifecycle.global; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.wf.impl.policy.lifecycle.AbstractTestLifecycle; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; +import org.testng.annotations.Test; import javax.xml.namespace.QName; import java.util.List; -import static com.evolveum.midpoint.schema.constants.ObjectTypes.USER; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; /** * Tests role lifecycle with global policy rules. @@ -47,44 +61,95 @@ protected boolean approveObjectAdd() { public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); - GlobalPolicyRuleType ruleAll = new GlobalPolicyRuleType(prismContext) - .beginPolicyConstraints() - .beginModification() - .end() - .end() - .beginPolicyActions() - .beginApproval() - .beginApprovalSchema() - .beginLevel() - .approverRelation(new QName("owner")) // intentionally no namespace - .outcomeIfNoApprovers(ApprovalLevelOutcomeType.APPROVE) - .end() - .end() - .end() - .end() - .beginFocusSelector() - .type(RoleType.COMPLEX_TYPE) - .end(); - - GlobalPolicyRuleType ruleAdd = new GlobalPolicyRuleType(prismContext) - .beginPolicyConstraints() - .beginModification() - .operation(ChangeTypeType.ADD) - .end() - .end() - .beginPolicyActions() - .beginApproval() - .approverRef(userLead1Oid, UserType.COMPLEX_TYPE) - .end() - .end() - .beginFocusSelector() - .type(RoleType.COMPLEX_TYPE) - .end(); + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); + + // couldn't use updateSystemConfiguration because users' OIDs are not known yet at that time + GlobalPolicyRuleType ruleAll = + new GlobalPolicyRuleType(prismContext) + .name("all-modifications") + .beginPolicyConstraints() + .beginModification() + .end() + .end() + .beginPolicyActions() + .beginApproval() + .beginApprovalSchema() + .beginLevel() + .approverRelation(new QName("owner")) // intentionally no namespace + .outcomeIfNoApprovers(ApprovalLevelOutcomeType.APPROVE) + .end() + .end() + .end() + .end() + .beginFocusSelector() + .type(RoleType.COMPLEX_TYPE) + .end(); + + GlobalPolicyRuleType ruleAllAdditions = + new GlobalPolicyRuleType(prismContext) + .name("all-additions") + .beginPolicyConstraints() + .beginModification() + .operation(ChangeTypeType.ADD) + .end() + .end() + .beginPolicyActions() + .beginApproval() + .approverRef(userLead1Oid, UserType.COMPLEX_TYPE) + .end() + .end() + .beginFocusSelector() + .type(RoleType.COMPLEX_TYPE) + .end(); + + GlobalPolicyRuleType ruleModificationOfRiskLevel = + new GlobalPolicyRuleType(prismContext) + .name("modification-of-risk-level") + .beginPolicyConstraints() + .beginModification() + .item(new ItemPath(RoleType.F_RISK_LEVEL).asItemPathType()) + .end() + .end() + .beginPolicyActions() + .beginApproval() + .beginProcessSpecification() + .beginDeltaFrom() + .item(new ItemPath(RoleType.F_RISK_LEVEL).asItemPathType()) + .end() + .end() + .approverRef(userLead2Oid, UserType.COMPLEX_TYPE) + .end() + .end() + .beginFocusSelector() + .type(RoleType.COMPLEX_TYPE) + .end(); + + GlobalPolicyRuleType ruleModificationOfApproverRef = + new GlobalPolicyRuleType(prismContext) + .name("modification-of-approverRef") // to be multivalued + .beginPolicyConstraints() + .beginModification() + .item(new ItemPath(RoleType.F_APPROVER_REF).asItemPathType()) + .end() + .end() + .beginPolicyActions() + .beginApproval() + .beginProcessSpecification() + .beginDeltaFrom() + .itemValue(new ItemPath(RoleType.F_APPROVER_REF).asItemPathType()) + .end() + .end() + .approverRef(userLead3Oid, UserType.COMPLEX_TYPE) + .end() + .end() + .beginFocusSelector() + .type(RoleType.COMPLEX_TYPE) + .end(); List> deltas = DeltaBuilder.deltaFor(SystemConfigurationType.class, prismContext) .item(SystemConfigurationType.F_GLOBAL_POLICY_RULE) - .replace(ruleAll, ruleAdd) + .replace(ruleAll, ruleAllAdditions, ruleModificationOfRiskLevel, ruleModificationOfApproverRef) .asItemDeltas(); repositoryService.modifyObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value(), deltas, initResult); @@ -93,4 +158,425 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti IntegrationTestTools.display("System configuration", getObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value())); } + + private String roleJudgeOid; + private String roleCaptainOid; + private String roleThiefOid; + + @Test + public void test500CreateRoleJudge() throws Exception { + final String TEST_NAME = "test500CreateRoleJudge"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + RoleType judge = new RoleType(prismContext) + .name("judge") + .riskLevel("high"); + + ObjectDelta addObjectDelta = ObjectDelta.createAddDelta(judge.asPrismObject()); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, addObjectDelta); + lensContext.setOptions(ModelExecuteOptions.createExecuteImmediatelyAfterApproval()); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + assertFalse("There is model context in the root task (it should not be there)", + wfTaskUtil.hasModelContext(rootTask)); + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + assertNoObject(judge); + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + assertObject(judge); + } + + @Override + protected boolean executeImmediately() { + return true; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 2); + + // TODO some more asserts + + PrismObject judgeAfter = searchObjectByName(RoleType.class, "judge"); + roleJudgeOid = judgeAfter.getOid(); + + PrismReferenceValue judgeOwner = new PrismReferenceValue(roleJudgeOid, RoleType.COMPLEX_TYPE); + judgeOwner.setRelation(SchemaConstants.ORG_OWNER); + executeChanges(DeltaBuilder.deltaFor(UserType.class, prismContext) + .item(UserType.F_ASSIGNMENT).add(ObjectTypeUtil.createAssignmentTo(judgeAfter, SchemaConstants.ORG_OWNER)) + .asObjectDeltaCast(userJudgeOwnerOid), + null, task, result); + + display("Judge role", judgeAfter); + display("Judge owner", getUser(userJudgeOwnerOid)); + + assertEquals("Wrong risk level", "high", judgeAfter.asObjectable().getRiskLevel()); + } + + @Test + public void test510AddApproversToJudge() throws Exception { + final String TEST_NAME = "test510AddApproversToJudge"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + ObjectDelta judgeDelta = DeltaBuilder.deltaFor(RoleType.class, prismContext) + .item(RoleType.F_APPROVER_REF) + .add(new ObjectReferenceType().oid("oid1").type(RoleType.COMPLEX_TYPE), + new ObjectReferenceType().oid("oid2").type(RoleType.COMPLEX_TYPE)) + .item(RoleType.F_DESCRIPTION) + .replace("hi") + .asObjectDeltaCast(roleJudgeOid); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, judgeDelta); + lensContext.setOptions(ModelExecuteOptions.createExecuteImmediatelyAfterApproval()); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + assertFalse("There is model context in the root task (it should not be there)", + wfTaskUtil.hasModelContext(rootTask)); + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected boolean executeImmediately() { + return true; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 3); + + // TODO some more asserts + + PrismObject judgeAfter = searchObjectByName(RoleType.class, "judge"); + + display("Judge role", judgeAfter); + + assertEquals("Wrong risk level", "high", judgeAfter.asObjectable().getRiskLevel()); + assertEquals("Wrong description", "hi", judgeAfter.asObjectable().getDescription()); + PrismAsserts.assertReferenceValues(judgeAfter.findReference(RoleType.F_APPROVER_REF), "oid1", "oid2"); + } + + @Test + public void test600CreateRoleCaptain() throws Exception { + final String TEST_NAME = "test600CreateRoleCaptain"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + RoleType captain = new RoleType(prismContext) + .name("captain") + .description("something") + .riskLevel("high") + .approverRef(new ObjectReferenceType().oid("oid1").type(UserType.COMPLEX_TYPE)) + .approverRef(new ObjectReferenceType().oid("oid2").type(UserType.COMPLEX_TYPE)); + + ObjectDelta addObjectDelta = ObjectDelta.createAddDelta(captain.asPrismObject()); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, addObjectDelta); + lensContext.setOptions(ModelExecuteOptions.createExecuteImmediatelyAfterApproval()); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + assertFalse("There is model context in the root task (it should not be there)", + wfTaskUtil.hasModelContext(rootTask)); + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + assertNoObject(captain); + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + assertObject(captain); + } + + @Override + protected boolean executeImmediately() { + return true; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 4); + + // TODO some more asserts + + PrismObject captainAfter = searchObjectByName(RoleType.class, "captain"); + roleCaptainOid = captainAfter.getOid(); + display("Captain role", captainAfter); + + assertEquals("Wrong risk level", "high", captainAfter.asObjectable().getRiskLevel()); + PrismAsserts.assertReferenceValues(captainAfter.findReference(RoleType.F_APPROVER_REF), "oid1", "oid2"); + } + + @Test + public void test610DeleteApproversFromCaptain() throws Exception { + final String TEST_NAME = "test610DeleteApproversFromCaptain"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject captainBefore = getRole(roleCaptainOid); + + ObjectDelta captainDelta = DeltaBuilder.deltaFor(RoleType.class, prismContext) + .item(RoleType.F_APPROVER_REF) + .delete(CloneUtil.cloneCollectionMembers(captainBefore.findReference(RoleType.F_APPROVER_REF).getValues())) + .asObjectDeltaCast(roleCaptainOid); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, captainDelta); + lensContext.setOptions(ModelExecuteOptions.createExecuteImmediatelyAfterApproval()); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + assertFalse("There is model context in the root task (it should not be there)", + wfTaskUtil.hasModelContext(rootTask)); + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected boolean executeImmediately() { + return true; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 2); + + // TODO some more asserts + + PrismObject captainAfter = getRole(roleCaptainOid); + + display("Captain role", captainAfter); + + assertEquals("Wrong risk level", "high", captainAfter.asObjectable().getRiskLevel()); + PrismAsserts.assertReferenceValues(captainAfter.findOrCreateReference(RoleType.F_APPROVER_REF)); + } + + @Test + public void test700CreateRoleThief() throws Exception { + final String TEST_NAME = "test700CreateRoleThief"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + RoleType thief = new RoleType(prismContext) + .name("thief") + .description("something") + .riskLevel("high") + .approverRef(new ObjectReferenceType().oid("oid1").type(UserType.COMPLEX_TYPE)) + .approverRef(new ObjectReferenceType().oid("oid2").type(UserType.COMPLEX_TYPE)); + + ObjectDelta addObjectDelta = ObjectDelta.createAddDelta(thief.asPrismObject()); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, addObjectDelta); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + assertNoObject(thief); + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + assertObject(thief); + } + + @Override + protected boolean executeImmediately() { + return false; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 4); + + // TODO some more asserts + + PrismObject thiefAfter = searchObjectByName(RoleType.class, "thief"); + roleThiefOid = thiefAfter.getOid(); + display("Thief role", thiefAfter); + + assertEquals("Wrong risk level", "high", thiefAfter.asObjectable().getRiskLevel()); + PrismAsserts.assertReferenceValues(thiefAfter.findReference(RoleType.F_APPROVER_REF), "oid1", "oid2"); + } + + @Test + public void test710DeleteApproversFromThief() throws Exception { + final String TEST_NAME = "test710DeleteApproversFromThief"; + TestUtil.displayTestTitle(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject thiefBefore = getRole(roleThiefOid); + + ObjectDelta captainDelta = DeltaBuilder.deltaFor(RoleType.class, prismContext) + .item(RoleType.F_APPROVER_REF) + .delete(CloneUtil.cloneCollectionMembers(thiefBefore.findReference(RoleType.F_APPROVER_REF).getValues())) + .asObjectDeltaCast(roleThiefOid); + + executeTest(TEST_NAME, new TestDetails() { + @Override + protected LensContext createModelContext(OperationResult result) throws Exception { + LensContext lensContext = createLensContext(RoleType.class); + addFocusDeltaToContext(lensContext, captainDelta); + return lensContext; + } + + @Override + protected void afterFirstClockworkRun(Task rootTask, List subtasks, List workItems, + OperationResult result) throws Exception { + display("subtasks", subtasks); + display("work items", workItems); + // todo some asserts here + } + + @Override + protected void afterTask0Finishes(Task task, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected void afterRootTaskFinishes(Task task, List subtasks, OperationResult result) throws Exception { + // nothing here + } + + @Override + protected boolean executeImmediately() { + return false; + } + + @Override + protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { + login(userAdministrator); + return true; + } + }, 2); + + // TODO some more asserts + + PrismObject thiefAfter = getRole(roleThiefOid); + + display("Thief role", thiefAfter); + + assertEquals("Wrong risk level", "high", thiefAfter.asObjectable().getRiskLevel()); + PrismAsserts.assertReferenceValues(thiefAfter.findOrCreateReference(RoleType.F_APPROVER_REF)); + } + + // TODO test that contains task0 that adds an object (i.e. rule for 'add' is not applied) + + @Test + public void zzzMarkAsNotInitialized() { + display("Setting class as not initialized"); + unsetSystemInitialized(); + } } diff --git a/model/workflow-impl/src/test/resources/policy/lifecycle/user-judge-owner.xml b/model/workflow-impl/src/test/resources/policy/lifecycle/user-judge-owner.xml new file mode 100644 index 00000000000..7f6cd9e9f57 --- /dev/null +++ b/model/workflow-impl/src/test/resources/policy/lifecycle/user-judge-owner.xml @@ -0,0 +1,26 @@ + + + + judge-owner + Judge owner + diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java index 1186e2494b8..435948becab 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceManager.java @@ -181,6 +181,7 @@ private PrismObject loadAndCacheResource(PrismObject if (!isComplete(completedResource)) { // No not cache non-complete resources (e.g. those retrieved with noFetch) + LOGGER.trace("Not putting resource {} ({}) into cache because it's not complete", repositoryObject.getName(), repositoryObject.getOid()); return completedResource; } @@ -198,6 +199,9 @@ private PrismObject loadAndCacheResource(PrismObject if (completeResourceResult.isSuccess()) { // Cache only resources that are completely OK resourceCache.put(completedResource); + } else { + LOGGER.trace("Not putting {} into cache because the completeResource operation status is {}", + ObjectTypeUtil.toShortString(repositoryObject), completeResourceResult.getStatus()); } InternalMonitor.getResourceCacheStats().recordMiss(); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java index 5b2f25819ec..27c8ec68408 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java @@ -40,6 +40,9 @@ import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; @@ -75,8 +78,9 @@ public class ResourceObjectReferenceResolver { private static final Trace LOGGER = TraceManager.getTrace(ResourceObjectReferenceResolver.class); - @Autowired(required = true) - private PrismContext prismContext; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private ShadowManager shadowManager; @Autowired(required = true) @Qualifier("cacheRepositoryService") @@ -86,9 +90,6 @@ public class ResourceObjectReferenceResolver { @Qualifier("shadowCacheProvisioner") private ShadowCache shadowCache; - @Autowired(required = true) - private ShadowManager shadowManager; - PrismObject resolve(ProvisioningContext ctx, ResourceObjectReferenceType resourceObjectReference, QName objectClass, final String desc, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, @@ -118,8 +119,11 @@ PrismObject resolve(ProvisioningContext ctx, ResourceObjectReference subctx.assertDefinition(); ObjectQuery refQuery = QueryJaxbConvertor.createObjectQuery(ShadowType.class, resourceObjectReference.getFilter(), prismContext); + // No variables. At least not now. We expect that mostly constants will be used here. + ExpressionVariables variables = new ExpressionVariables(); + ObjectQuery evaluatedRefQuery = ExpressionUtil.evaluateQueryExpressions(refQuery, variables, expressionFactory, prismContext, desc, ctx.getTask(), result); ObjectFilter baseFilter = ObjectQueryUtil.createResourceAndObjectClassFilter(ctx.getResource().getOid(), objectClass, prismContext); - ObjectFilter filter = AndFilter.createAnd(baseFilter, refQuery.getFilter()); + ObjectFilter filter = AndFilter.createAnd(baseFilter, evaluatedRefQuery.getFilter()); ObjectQuery query = ObjectQuery.createObjectQuery(filter); // TODO: implement "repo" search strategies diff --git a/provisioning/ucf-impl-connid/src/test/resources/connector-ldap.xml b/provisioning/ucf-impl-connid/src/test/resources/connector-ldap.xml index 66cd6dca038..d0b8940fc0d 100644 --- a/provisioning/ucf-impl-connid/src/test/resources/connector-ldap.xml +++ b/provisioning/ucf-impl-connid/src/test/resources/connector-ldap.xml @@ -23,7 +23,7 @@ ICF com.evolveum.polygon.connector.ldap.LdapConnector http://midpoint.evolveum.com/xml/ns/public/connector/icf-1 com.evolveum.polygon.connector.ldap.LdapConnector - 1.5-SNAPSHOT + 1.5 com.evolveum.polygon.connector-ldap http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/bundle/com.evolveum.polygon.connector-ldap/com.evolveum.polygon.connector.ldap.LdapConnector diff --git a/repo/repo-test-util/src/main/resources/test-config.xml b/repo/repo-test-util/src/main/resources/test-config.xml index 9d7a6f6e518..9f36869304a 100644 --- a/repo/repo-test-util/src/main/resources/test-config.xml +++ b/repo/repo-test-util/src/main/resources/test-config.xml @@ -72,6 +72,8 @@ Bla bla bla dc=example,dc=com secret + DC=ad,DC=evolveum,DC=com + CN=Users,DC=ad,DC=evolveum,DC=com \ No newline at end of file diff --git a/repo/security-impl/src/main/java/com/evolveum/midpoint/security/impl/SecurityEnforcerImpl.java b/repo/security-impl/src/main/java/com/evolveum/midpoint/security/impl/SecurityEnforcerImpl.java index 09e19a7cd63..ce039b46193 100644 --- a/repo/security-impl/src/main/java/com/evolveum/midpoint/security/impl/SecurityEnforcerImpl.java +++ b/repo/security-impl/src/main/java/com/evolveum/midpoint/security/impl/SecurityEnforcerImpl.java @@ -33,6 +33,7 @@ import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.security.api.*; import com.evolveum.midpoint.util.Producer; +import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.AuthorizationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; @@ -511,10 +512,28 @@ private boolean isApplicable(SubjectedObjectSelectorType } } if (!found) { - LOGGER.trace(" {}: delegator object spec not applicable for {}, object OID {} because delegator does not match", - autzHumanReadableDesc, desc, object.getOid()); - return false; + if (BooleanUtils.isTrue(delegatorSpec.isAllowInactive())) { + for (AssignmentType objectAssignment: ((UserType)object.asObjectable()).getAssignment()) { + ObjectReferenceType objectAssignmentTargetRef = objectAssignment.getTargetRef(); + if (objectAssignmentTargetRef == null) { + continue; + } + if (principal.getOid().equals(objectAssignmentTargetRef.getOid())) { + if (QNameUtil.match(SchemaConstants.ORG_DEPUTY, objectAssignmentTargetRef.getRelation())) { + found = true; + break; + } + } + } + } + + if (!found) { + LOGGER.trace(" {}: delegator object spec not applicable for {}, object OID {} because delegator does not match", + autzHumanReadableDesc, desc, object.getOid()); + return false; + } } + } } diff --git a/repo/system-init/src/main/java/com/evolveum/midpoint/init/ConfigurablePrismContextFactory.java b/repo/system-init/src/main/java/com/evolveum/midpoint/init/ConfigurablePrismContextFactory.java index d8da9093935..caa21bd3493 100644 --- a/repo/system-init/src/main/java/com/evolveum/midpoint/init/ConfigurablePrismContextFactory.java +++ b/repo/system-init/src/main/java/com/evolveum/midpoint/init/ConfigurablePrismContextFactory.java @@ -38,7 +38,16 @@ public class ConfigurablePrismContextFactory extends MidPointPrismContextFactory private static final String EXTENSION_DIR = "extensionDir"; private MidpointConfiguration configuration; - ConfigurablePrismContextFactory() { + // This is a hack to facilitate having separate extension schema directories for individual tests. + // It would be better to declare it as instance attribute but then it's not easy to set it up + // in TestNG tests (before midPoint is started). + private static String extensionDirOverride; + + public static void setExtensionDirOverride(String extensionDirOverride) { + ConfigurablePrismContextFactory.extensionDirOverride = extensionDirOverride; + } + + ConfigurablePrismContextFactory() { super(); } @@ -55,9 +64,16 @@ protected void registerExtensionSchemas(SchemaRegistryImpl schemaRegistry) throw Configuration config = configuration.getConfiguration(CONFIGURATION_GLOBAL); if (config == null) { LOGGER.warn("Global part 'midpoint.global' is not defined in configuration file."); - return; } - String extensionDir = config.getString(EXTENSION_DIR); + + String extensionDir; + if (extensionDirOverride != null) { + extensionDir = extensionDirOverride; + } else if (config != null) { + extensionDir = config.getString(EXTENSION_DIR); // potentially null + } else { + extensionDir = null; + } if (StringUtils.isEmpty(extensionDir)) { if (StringUtils.isNotEmpty(configuration.getMidpointHome())) { diff --git a/samples/book/5/resource-csv-hr.xml b/samples/book/5/resource-csv-hr.xml index f8100eda51a..13bd1004c1d 100644 --- a/samples/book/5/resource-csv-hr.xml +++ b/samples/book/5/resource-csv-hr.xml @@ -16,7 +16,7 @@ --> - - HR System + HR System HR resource using CSV connector. This is the HR feed (source) resource. @@ -35,29 +35,27 @@ book, chapter 5. - - - - c:connectorType - com.evolveum.polygon.connector.csv.CsvConnector - - - - - - - - - /var/opt/midpoint-book/resources/hr.csv - utf-8 - , - ; - empno - password - - - + + + + c:connectorType + com.evolveum.polygon.connector.csv.CsvConnector + + + + + + + + /var/opt/midpoint-book/resources/hr.csv + utf-8 + , + ; + empno + password + + @@ -168,5 +166,5 @@ - + diff --git a/samples/book/5/task-hr-import.xml b/samples/book/5/task-hr-import.xml new file mode 100644 index 00000000000..befd0f98397 --- /dev/null +++ b/samples/book/5/task-hr-import.xml @@ -0,0 +1,34 @@ + + + + + HR Import + + account + + 7c57adc2-a857-11e7-83ac-0f212d965f5b + + runnable + http://midpoint.evolveum.com/xml/ns/public/model/synchronization/task/import/handler-3 + + single + diff --git a/samples/book/5/task-hr-livesync.xml b/samples/book/5/task-hr-livesync.xml new file mode 100644 index 00000000000..51156303579 --- /dev/null +++ b/samples/book/5/task-hr-livesync.xml @@ -0,0 +1,37 @@ + + + + + HR Live Synchronization + + account + + 7c57adc2-a857-11e7-83ac-0f212d965f5b + + runnable + http://midpoint.evolveum.com/xml/ns/public/model/synchronization/task/live-sync/handler-3 + + recurring + + 10 + + diff --git a/samples/book/5/task-hr-recon.xml b/samples/book/5/task-hr-recon.xml new file mode 100644 index 00000000000..88eb6388b97 --- /dev/null +++ b/samples/book/5/task-hr-recon.xml @@ -0,0 +1,38 @@ + + + + + HR Reconciliation + + account + + bbe4ceac-a85c-11e7-a49f-0f5777d22906 + + runnable + http://midpoint.evolveum.com/xml/ns/public/model/synchronization/task/reconciliation/handler-3 + + recurring + + 0 0 1 ? * SAT + executeImmediately + + diff --git a/testing/conntest/src/test/resources/ad-ldap-multidomain/resource-chimera.xml b/testing/conntest/src/test/resources/ad-ldap-multidomain/resource-chimera.xml index b5097d22ad7..aef6b2f4bad 100644 --- a/testing/conntest/src/test/resources/ad-ldap-multidomain/resource-chimera.xml +++ b/testing/conntest/src/test/resources/ad-ldap-multidomain/resource-chimera.xml @@ -37,7 +37,7 @@ chimera.ad.evolveum.com 636 - DC=ad,DC=evolveum,DC=com + adBaseDn CN=midpoint,CN=Users,DC=ad,DC=evolveum,DC=com ssl @@ -87,7 +87,7 @@ attributes/dn - CN=Users,DC=ad,DC=evolveum,DC=com + adUsersDn @@ -102,7 +102,7 @@ diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestDelivery.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestDelivery.java new file mode 100644 index 00000000000..00246a536e0 --- /dev/null +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestDelivery.java @@ -0,0 +1,1356 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.testing.story; + +import com.evolveum.midpoint.init.ConfigurablePrismContextFactory; +import com.evolveum.midpoint.model.api.WorkflowService; +import com.evolveum.midpoint.model.test.DummyTransport; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.PolicyViolationException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.Arrays; + +/** + * + * @author mederly + * + */ + +@SuppressWarnings("FieldCanBeLocal") +@ContextConfiguration(locations = {"classpath:ctx-story-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestDelivery extends AbstractStoryTest { + + @Autowired private WorkflowService workflowService; + @Autowired private DummyTransport dummyTransport; + + private static final String TEST_DIR = "src/test/resources/delivery"; + private static final String EXTENSION_SCHEMA_DIR = "src/test/resources/delivery/schema"; + private static final String ORG_DIR = TEST_DIR + "/orgs"; + private static final String ROLES_DIR = TEST_DIR + "/roles"; + private static final String RULES_DIR = TEST_DIR + "/rules"; + private static final String ROLES_SPECIFIC_DIR = TEST_DIR + "/roles-specific"; + private static final String USERS_DIR = TEST_DIR + "/users"; + + public static final String NS_EXT = "http://midpoint.evolveum.com/xml/ns/story/delivery/ext"; + + private static final File ORG_MONKEY_ISLAND_FILE = new File(ORG_DIR, "0-org-monkey-island-modified.xml"); +// private static final File ORG_TEAMS_FILE = new File(ORG_DIR, "1-teams.xml"); +// private static String orgTeamsOid; +// private static final File ORG_ROLE_CATALOG_FILE = new File(ORG_DIR, "2-role-catalog.xml"); +// private static String orgRoleCatalogOid; +// private static final File ORG_SECURITY_APPROVERS_FILE = new File(ORG_DIR, "security-approvers.xml"); +// private static String orgSecurityApproversOid; +// private static final File ORG_SOD_APPROVERS_FILE = new File(ORG_DIR, "sod-approvers.xml"); +// private static String orgSodApproversOid; + + private static final File ROLE_END_USER_FILE = new File(ROLES_DIR, "role-end-user.xml"); + private static String roleEndUserOid; + private static final File RULE_K10_FILE = new File(RULES_DIR, "k10.xml"); + private static String ruleK10_oid; + private static final File RULE_K10_TPU_10_FILE = new File(RULES_DIR, "k10-tpu-10.xml"); + private static String ruleK10_tpu_10_oid; + private static final File RULE_K10_TPU_10_REM_ELAINE_FILE = new File(RULES_DIR, "k10-tpu-10-rem-elaine.xml"); + private static String ruleK10_tpu_10_rem_elaine_oid; + + private static final File ROLE_IT_1_FILE = new File(ROLES_DIR, "role-it-1.xml"); + private static String roleIt1Oid; + private static final File ROLE_IT_2_FILE = new File(ROLES_DIR, "role-it-2.xml"); + private static String roleIt2Oid; + + // +// private static final File ROLE_A_TEST_1 = new File(ROLES_SPECIFIC_DIR, "a-test-1.xml"); +// private static String roleATest1Oid; +// private static final File ROLE_A_TEST_2A = new File(ROLES_SPECIFIC_DIR, "a-test-2a.xml"); +// private static String roleATest2aOid; +// private static final File ROLE_A_TEST_2B = new File(ROLES_SPECIFIC_DIR, "a-test-2b.xml"); +// private static String roleATest2bOid; +// private static final File ROLE_A_TEST_3A = new File(ROLES_SPECIFIC_DIR, "a-test-3a.xml"); +// private static String roleATest3aOid; +// private static final File ROLE_A_TEST_3B = new File(ROLES_SPECIFIC_DIR, "a-test-3b.xml"); +// private static String roleATest3bOid; +// private static final File ROLE_A_TEST_3X = new File(ROLES_SPECIFIC_DIR, "a-test-3x.xml"); +// private static String roleATest3xOid; +// private static final File ROLE_A_TEST_3Y = new File(ROLES_SPECIFIC_DIR, "a-test-3y.xml"); +// private static String roleATest3yOid; +// private static final File ROLE_A_TEST_4 = new File(ROLES_SPECIFIC_DIR, "a-test-4.xml"); +// private static String roleATest4Oid; +// + private static final File USER_BARKEEPER_FILE = new File(USERS_DIR, "barkeeper.xml"); + private static String userBarkeeperOid; + private static final File USER_BOB_FILE = new File(USERS_DIR, "bob.xml"); + private static String userBobOid; + private static final File USER_CARLA_FILE = new File(USERS_DIR, "carla.xml"); + private static String userCarlaOid; + private static final File USER_CHEESE_FILE = new File(USERS_DIR, "cheese.xml"); + private static String userCheeseOid; + private static final File USER_CHEF_FILE = new File(USERS_DIR, "chef.xml"); + private static String userChefOid; + private static final File USER_ELAINE_FILE = new File(USERS_DIR, "elaine.xml"); + private static String userElaineOid; + private static final File USER_GUYBRUSH_FILE = new File(USERS_DIR, "guybrush.xml"); + private static String userGuybrushOid; + private static final File USER_LECHUCK_FILE = new File(USERS_DIR, "lechuck.xml"); + private static String userLechuckOid; +// private static final File USER_LECHUCK_DEPUTY_FILE = new File(USERS_DIR, "lechuck-deputy.xml"); +// private static String userLechuckDeputyOid; +// private static final File USER_LECHUCK_DEPUTY_DEPUTY_FILE = new File(USERS_DIR, "lechuck-deputy-deputy.xml"); +// private static String userLechuckDeputyDeputyOid; +// private static final File USER_LECHUCK_DEPUTY_LIMITED_FILE = new File(USERS_DIR, "lechuck-deputy-limited.xml"); +// private static String userLechuckDeputyLimitedOid; +// +// private static final File CONFIG_WITH_GLOBAL_RULES_FILE = new File(ROLES_DIR, "global-policy-rules.xml"); +// +// private static final String DUMMY_WORK_ITEM_LIFECYCLE = "dummy:workItemLifecycle"; +// private static final String DUMMY_WORK_ITEM_ALLOCATION = "dummy:workItemAllocation"; +// private static final String DUMMY_WORK_ITEM_CUSTOM = "dummy:workItemCustom"; +// private static final String DUMMY_PROCESS = "dummy:process"; +// +// protected static final int TASK_WAIT_TIMEOUT = 40000; + + @BeforeSuite + public void setSchema() throws Exception { + System.out.println("Setting extension schema dir"); + ConfigurablePrismContextFactory.setExtensionDirOverride(EXTENSION_SCHEMA_DIR); + } + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + +// transplantGlobalPolicyRulesAdd(CONFIG_WITH_GLOBAL_RULES_FILE, initTask, initResult); + + // we don't need these + taskManager.suspendAndDeleteTasks(Arrays.asList(TASK_VALIDITY_SCANNER_OID, TASK_TRIGGER_SCANNER_OID), 60000L, true, initResult); + + // import of story objects + repoAddObjectsFromFile(ORG_MONKEY_ISLAND_FILE, OrgType.class, initResult); +// orgTeamsOid = repoAddObjectFromFile(ORG_TEAMS_FILE, initResult).getOid(); +// orgRoleCatalogOid = repoAddObjectFromFile(ORG_ROLE_CATALOG_FILE, initResult).getOid(); +// orgSecurityApproversOid = repoAddObjectFromFile(ORG_SECURITY_APPROVERS_FILE, initResult).getOid(); +// orgSodApproversOid = repoAddObjectFromFile(ORG_SOD_APPROVERS_FILE, initResult).getOid(); +// + roleEndUserOid = repoAddObjectFromFile(ROLE_END_USER_FILE, initResult).getOid(); + ruleK10_oid = addAndRecompute(RULE_K10_FILE, initTask, initResult); + ruleK10_tpu_10_oid = addAndRecompute(RULE_K10_TPU_10_FILE, initTask, initResult); + ruleK10_tpu_10_rem_elaine_oid = addAndRecompute(RULE_K10_TPU_10_REM_ELAINE_FILE, initTask, initResult); + roleIt1Oid = addAndRecompute(ROLE_IT_1_FILE, initTask, initResult); + roleIt2Oid = addAndRecompute(ROLE_IT_2_FILE, initTask, initResult); + +// metaroleApprovalRoleApproversFirstOid = repoAddObjectFromFile(METAROLE_APPROVAL_ROLE_APPROVERS_FIRST_FILE, initResult).getOid(); +// metaroleApprovalRoleApproversFormOid = repoAddObjectFromFile(METAROLE_APPROVAL_ROLE_APPROVERS_FORM_FILE, initResult).getOid(); +// metaroleApprovalSecurityOid = repoAddObjectFromFile(METAROLE_APPROVAL_SECURITY_FILE, initResult).getOid(); +// +// roleATest1Oid = addAndRecompute(ROLE_A_TEST_1, initTask, initResult); +// roleATest2aOid = addAndRecompute(ROLE_A_TEST_2A, initTask, initResult); +// roleATest2bOid = addAndRecompute(ROLE_A_TEST_2B, initTask, initResult); +// roleATest3aOid = addAndRecompute(ROLE_A_TEST_3A, initTask, initResult); +// roleATest3bOid = addAndRecompute(ROLE_A_TEST_3B, initTask, initResult); +// roleATest3xOid = addAndRecompute(ROLE_A_TEST_3X, initTask, initResult); +// roleATest3yOid = addAndRecompute(ROLE_A_TEST_3Y, initTask, initResult); +// roleATest4Oid = addAndRecompute(ROLE_A_TEST_4, initTask, initResult); +// + userBarkeeperOid = addAndRecomputeUser(USER_BARKEEPER_FILE, initTask, initResult); + userBobOid = addAndRecomputeUser(USER_BOB_FILE, initTask, initResult); + userCarlaOid = addAndRecomputeUser(USER_CARLA_FILE, initTask, initResult); + userCheeseOid = addAndRecomputeUser(USER_CHEESE_FILE, initTask, initResult); + userChefOid = addAndRecomputeUser(USER_CHEF_FILE, initTask, initResult); + userElaineOid = addAndRecomputeUser(USER_ELAINE_FILE, initTask, initResult); + userGuybrushOid = addAndRecomputeUser(USER_GUYBRUSH_FILE, initTask, initResult); + userLechuckOid = addAndRecomputeUser(USER_LECHUCK_FILE, initTask, initResult); +// userLechuckDeputyOid = addAndRecomputeUser(USER_LECHUCK_DEPUTY_FILE, initTask, initResult); +// userLechuckDeputyDeputyOid = addAndRecomputeUser(USER_LECHUCK_DEPUTY_DEPUTY_FILE, initTask, initResult); +// userLechuckDeputyLimitedOid = addAndRecomputeUser(USER_LECHUCK_DEPUTY_LIMITED_FILE, initTask, initResult); + + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); + } + + @Override + protected PrismObject getDefaultActor() { + return userAdministrator; + } + + @Test + public void test100Assign_IT_2_fail() throws Exception { + final String TEST_NAME = "test100Assign_IT_2_fail"; + TestUtil.displayTestTitle(TEST_NAME); + + Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + try { + assignRole(userBobOid, roleIt2Oid, task, result); // hard constraint + fail("unexpected success"); + } catch (PolicyViolationException e) { + System.out.println("Got expected exception: " + e); + } + } + +// @Test +// public void test000Sanity() throws Exception { +// final String TEST_NAME = "test000Sanity"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// +// // TODO +// } +// +// //region Basic approval +// @Test +// public void test100SimpleAssignmentStart() throws Exception { +// final String TEST_NAME = "test100SimpleAssignmentStart"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// assignRole(userBobOid, roleATest1Oid, task, task.getResult()); +// +// // THEN +// assertNotAssignedRole(getUser(userBobOid), roleATest1Oid); +// +// WorkItemType workItem = getWorkItem(task, result); +// display("Work item", workItem); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItem).getOid()); +// display("wfTask", wfTask); +// +// assertTriggers(wfTask, 2); +// +// ItemApprovalProcessStateType info = WfContextUtil.getItemApprovalProcessInfo(wfTask.asObjectable().getWorkflowContext()); +// ApprovalSchemaType schema = info.getApprovalSchema(); +// assertEquals("Wrong # of approval levels", 3, schema.getStage().size()); +// assertApprovalLevel(schema, 1, "Line managers", "P5D", 2); +// assertApprovalLevel(schema, 2, "Security", "P7D", 1); +// assertApprovalLevel(schema, 3, "Role approvers (all)", "P5D", 2); +// assertStage(wfTask, 1, 3, "Line managers", null); +// assertAssignee(workItem, userLechuckOid, userLechuckOid); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// +// assertEquals("Wrong # of work items lifecycle messages", 3, lifecycleMessages.size()); +// Map sorted = sortByRecipientsSingle(lifecycleMessages); +// assertMessage(sorted.get("lechuck@evolveum.com"), "lechuck@evolveum.com", "A new work item has been created", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// assertMessage(sorted.get("lechuck-deputy@evolveum.com"), "lechuck-deputy@evolveum.com", "A new work item has been created", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// assertMessage(sorted.get("lechuck-deputy-deputy@evolveum.com"), "lechuck-deputy-deputy@evolveum.com", "A new work item has been created", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// +// assertEquals("Wrong # of work items allocation messages", 3, allocationMessages.size()); +// Map sorted2 = sortByRecipientsSingle(allocationMessages); +// assertMessage(sorted2.get("lechuck@evolveum.com"), "lechuck@evolveum.com", "Work item has been allocated to you", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// assertMessage(sorted2.get("lechuck-deputy@evolveum.com"), "lechuck-deputy@evolveum.com", "Work item has been allocated to you", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// assertMessage(sorted2.get("lechuck-deputy-deputy@evolveum.com"), "lechuck-deputy-deputy@evolveum.com", "Work item has been allocated to you", +// "Stage: Line managers (1/3)", "Allocated to: Captain LeChuck (lechuck)", "(in 5 days)"); +// +// assertEquals("Wrong # of process messages", 1, processMessages.size()); +// assertMessage(processMessages.get(0), "administrator@evolveum.com", "Workflow process instance has been started", +// "Process instance name: Assigning a-test-1 to bob", "Stage: Line managers (1/3)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test102SimpleAssignmentApproveByLechuck() throws Exception { +// final String TEST_NAME = "test102SimpleAssignmentApproveByLechuck"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// WorkItemType workItem = getWorkItem(task, result); +// +// // WHEN +// PrismObject lechuck = getUserFromRepo(userLechuckOid); +// login(lechuck); +// workflowService.completeWorkItem(workItem.getExternalId(), true, "OK. LeChuck", null, result); +// +// // THEN +// login(userAdministrator); +// +// List workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 1st approval", workItems); +// assertEquals("Wrong # of work items on level 2", 2, workItems.size()); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItem).getOid()); +// display("wfTask after 1st approval", wfTask); +// +// assertStage(wfTask, 2, 3, "Security", null); +// assertTriggers(wfTask, 4); +// +// // notifications +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 5, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 5, allocationMessages.size()); +// assertNull("process messages", processMessages); +// +// Map sorted = sortByRecipientsSingle(lifecycleMessages); +// assertMessage(sorted.get("lechuck@evolveum.com"), "lechuck@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("lechuck-deputy@evolveum.com"), "lechuck-deputy@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("lechuck-deputy-deputy@evolveum.com"), "lechuck-deputy-deputy@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("elaine@evolveum.com"), "elaine@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", +// "Allocated to: Elaine Marley (elaine)", "(in 7 days)", "^Result:"); +// assertMessage(sorted.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "(in 7 days)", "^Result:"); +// +// Map sorted2 = sortByRecipientsSingle(allocationMessages); +// assertMessage(sorted2.get("lechuck@evolveum.com"), "lechuck@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("lechuck-deputy@evolveum.com"), "lechuck-deputy@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("lechuck-deputy-deputy@evolveum.com"), "lechuck-deputy-deputy@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Line managers (1/3)", +// "Allocated to: Captain LeChuck (lechuck)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("elaine@evolveum.com"), "elaine@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", "Allocated to: Elaine Marley (elaine)", +// "(in 7 days)", "^Result:"); +// assertMessage(sorted2.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "(in 7 days)", "^Result:"); +// +// // events +// List events = assertEvents(wfTask, 2); +// assertCompletionEvent(events.get(1), userLechuckOid, userLechuckOid, 1, "Line managers", WorkItemOutcomeType.APPROVE, "OK. LeChuck"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test104SimpleAssignmentApproveByAdministrator() throws Exception { +// final String TEST_NAME = "test104SimpleAssignmentApproveByAdministrator"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// List workItems = getWorkItems(task, result); +// WorkItemType firstWorkItem = workItems.get(0); +// +// // WHEN +// // Second approval +// workflowService.completeWorkItem(firstWorkItem.getExternalId(), true, "OK. Security.", null, result); +// +// // THEN +// workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 2nd approval", workItems); +// assertEquals("Wrong # of work items on level 3", 2, workItems.size()); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItems.get(0)).getOid()); +// display("wfTask after 2nd approval", wfTask); +// +// assertStage(wfTask, 3, 3, "Role approvers (all)", null); +// assertTriggers(wfTask, 4); +// +// Map workItemsMap = sortByOriginalAssignee(workItems); +// assertNotNull("chef is not an approver", workItemsMap.get(userChefOid)); +// assertNotNull("cheese is not an approver", workItemsMap.get(userCheeseOid)); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 4, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 4, allocationMessages.size()); +// assertNull("process messages", processMessages); +// +// Map sorted = sortByRecipientsSingle(lifecycleMessages); +// assertMessage(sorted.get("elaine@evolveum.com"), "elaine@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", "Allocated to: Elaine Marley (elaine)", +// "Carried out by: midPoint Administrator (administrator)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "Work item has been cancelled", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "^Result:", "^Deadline:", "^Carried out by:"); +// assertMessage(sorted.get("cheese@evolveum.com"), "cheese@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Ignatius Cheese (cheese)", "^Result:", "(in 5 days)"); +// assertMessage(sorted.get("chef@evolveum.com"), "chef@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Scumm Bar Chef (chef)", "^Result:", "(in 5 days)"); +// +// Map sorted2 = sortByRecipientsSingle(allocationMessages); +// assertMessage(sorted2.get("elaine@evolveum.com"), "elaine@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", "Allocated to: Elaine Marley (elaine)", +// "Carried out by: midPoint Administrator (administrator)", "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "Work item has been cancelled", +// "Work item: Approve assigning a-test-1 to bob", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "^Result:", "^Deadline:", "^Carried out by:"); +// assertMessage(sorted2.get("cheese@evolveum.com"), "cheese@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Ignatius Cheese (cheese)", "^Result:", "(in 5 days)"); +// assertMessage(sorted2.get("chef@evolveum.com"), "chef@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Scumm Bar Chef (chef)", "^Result:", "(in 5 days)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test106SimpleAssignmentApproveByCheese() throws Exception { +// final String TEST_NAME = "test106SimpleAssignmentApproveByCheese"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// List workItems = getWorkItems(task, result); +// Map workItemsMap = sortByOriginalAssignee(workItems); +// +// // WHEN +// login(getUser(userCheeseOid)); +// workflowService.completeWorkItem(workItemsMap.get(userCheeseOid).getExternalId(), true, "OK. Cheese.", null, result); +// +// // THEN +// login(userAdministrator); +// workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 3rd approval", workItems); +// assertEquals("Wrong # of work items on level 3", 1, workItems.size()); +// workItemsMap = sortByOriginalAssignee(workItems); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItems.get(0)).getOid()); +// display("wfTask after 3rd approval", wfTask); +// +// assertStage(wfTask, 3, 3, "Role approvers (all)", null); +// assertTriggers(wfTask, 2); +// +// assertNotNull("chef is not an approver", workItemsMap.get(userChefOid)); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 1, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 1, allocationMessages.size()); +// assertNull("process messages", processMessages); +// +// assertMessage(lifecycleMessages.get(0), "cheese@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Ignatius Cheese (cheese)", "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(allocationMessages.get(0), "cheese@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Ignatius Cheese (cheese)", "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test108SimpleAssignmentApproveByChef() throws Exception { +// final String TEST_NAME = "test108SimpleAssignmentApproveByChef"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// // GIVEN +// login(userAdministrator); +// List workItems = getWorkItems(task, result); +// String taskOid = WfContextUtil.getTask(workItems.get(0)).getOid(); +// Map workItemsMap = sortByOriginalAssignee(workItems); +// +// // WHEN +// login(getUser(userChefOid)); +// String workItemId = workItemsMap.get(userChefOid).getExternalId(); +// workflowService.completeWorkItem(workItemId, true, "OK. Chef.", null, result); +// +// // THEN +// login(userAdministrator); +// workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 4th approval", workItems); +// assertEquals("Wrong # of work items on level 3", 0, workItems.size()); +// PrismObject wfTask = getTask(taskOid); +// display("wfTask after 4th approval", wfTask); +// +// Task parent = getParentTask(wfTask, result); +// waitForTaskFinish(parent, false, 60000); +// +// assertAssignedRole(getUser(userBobOid), roleATest1Oid); +// +// assertTriggers(wfTask, 0); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 1, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 1, allocationMessages.size()); +// assertEquals("Wrong # of process messages", 1, processMessages.size()); +// +// assertMessage(lifecycleMessages.get(0), "chef@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Scumm Bar Chef (chef)", "Carried out by: Scumm Bar Chef (chef)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(allocationMessages.get(0), "chef@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to bob", "Role approvers (all) (3/3)", +// "Allocated to: Scumm Bar Chef (chef)", "Carried out by: Scumm Bar Chef (chef)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(processMessages.get(0), "administrator@evolveum.com", "Workflow process instance has finished", +// "Process instance name: Assigning a-test-1 to bob", "Result: APPROVED"); +// +// display("audit", dummyAuditService); +// +// List workItemEvents = filter(getParamAuditRecords( +// WorkflowConstants.AUDIT_WORK_ITEM_ID, workItemId, result), AuditEventStage.EXECUTION); +// assertAuditReferenceValue(workItemEvents, WorkflowConstants.AUDIT_OBJECT, userBobOid, UserType.COMPLEX_TYPE, "bob"); +// assertAuditTarget(workItemEvents.get(0), userBobOid, UserType.COMPLEX_TYPE, "bob"); +// assertAuditReferenceValue(workItemEvents.get(0), WorkflowConstants.AUDIT_TARGET, roleATest1Oid, RoleType.COMPLEX_TYPE, "a-test-1"); +// // TODO other items +// List processEvents = filter(getParamAuditRecords( +// WorkflowConstants.AUDIT_PROCESS_INSTANCE_ID, wfTask.asObjectable().getWorkflowContext().getProcessInstanceId(), result), +// AuditEventType.WORKFLOW_PROCESS_INSTANCE, AuditEventStage.EXECUTION); +// assertAuditReferenceValue(processEvents, WorkflowConstants.AUDIT_OBJECT, userBobOid, UserType.COMPLEX_TYPE, "bob"); +// assertAuditTarget(processEvents.get(0), userBobOid, UserType.COMPLEX_TYPE, "bob"); +// assertAuditReferenceValue(processEvents.get(0), WorkflowConstants.AUDIT_TARGET, roleATest1Oid, RoleType.COMPLEX_TYPE, "a-test-1"); +// // TODO other items +// } +// +// //endregion +// +// //region Testing escalation +// @Test +// public void test200EscalatedApprovalStart() throws Exception { +// final String TEST_NAME = "test200EscalatedApprovalStart"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// assignRole(userCarlaOid, roleATest1Oid, task, task.getResult()); +// +// // THEN +// assertNotAssignedRole(getUser(userCarlaOid), roleATest1Oid); +// +// WorkItemType workItem = getWorkItem(task, result); +// display("Work item", workItem); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItem).getOid()); +// display("wfTask", wfTask); +// +// assertTriggers(wfTask, 2); +// +// assertStage(wfTask, 1, 3, "Line managers", null); +// assertAssignee(workItem, userGuybrushOid, userGuybrushOid); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items messages", 1, lifecycleMessages.size()); +// assertMessage(lifecycleMessages.get(0), "guybrush@evolveum.com", "A new work item has been created", "Stage: Line managers (1/3)", "Allocated to: Guybrush Threepwood (guybrush)"); +// assertMessage(allocationMessages.get(0), "guybrush@evolveum.com", "Work item has been allocated to you", "Stage: Line managers (1/3)", "Allocated to: Guybrush Threepwood (guybrush)"); +// +// assertEquals("Wrong # of work items allocation messages", 1, allocationMessages.size()); +// //assertMessage(lifecycleMessages.get(0), "guybrush@evolveum.com", "A new work item has been created", "Stage: Line managers (1/3)", "Guybrush Threepwood (guybrush)"); +// +// assertEquals("Wrong # of process messages", 1, processMessages.size()); +// assertMessage(processMessages.get(0), "administrator@evolveum.com", "Workflow process instance has been started", +// "Process instance name: Assigning a-test-1 to carla", "Stage: Line managers (1/3)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test202FourDaysLater() throws Exception { +// final String TEST_NAME = "test202FourDaysLater"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// clock.overrideDuration("P4D"); +// waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); +// +// // THEN +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertNull("lifecycle messages", lifecycleMessages); +// assertEquals("Wrong # of work items allocation messages", 1, allocationMessages.size()); +// assertMessage(allocationMessages.get(0), "guybrush@evolveum.com", "Work item will be automatically escalated in 1 day", +// "Stage: Line managers (1/3)", "Allocated to (before escalation): Guybrush Threepwood (guybrush)"); +// assertNull("process messages", processMessages); +// +// display("audit", dummyAuditService); +// } +// +// // escalation should occur here +// @Test +// public void test204SixDaysLater() throws Exception { +// final String TEST_NAME = "test204SixDaysLater"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// clock.resetOverride(); +// clock.overrideDuration("P6D"); +// waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); +// +// // THEN +// List workItems = getWorkItems(task, result); +// displayWorkItems("Work items after timed escalation", workItems); +// assertEquals("Wrong # of work items after timed escalation", 1, workItems.size()); +// String taskOid = WfContextUtil.getTask(workItems.get(0)).getOid(); +// PrismObject wfTask = getTask(taskOid); +// display("wfTask after timed escalation", wfTask); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// // asserts - work item +// WorkItemType workItem = workItems.get(0); +// PrismAsserts.assertReferenceValues(ref(workItem.getAssigneeRef()), userGuybrushOid, userCheeseOid); +// PrismAsserts.assertDuration("Wrong duration between now and deadline", "P9D", System.currentTimeMillis(), workItem.getDeadline(), null); +// PrismAsserts.assertReferenceValue(ref(workItem.getOriginalAssigneeRef()), userGuybrushOid); +// assertEquals("Wrong stage #", (Integer) 1, workItem.getStageNumber()); +// assertEquals("Wrong escalation level #", 1, WfContextUtil.getEscalationLevelNumber(workItem)); +// assertEquals("Wrong escalation level name", "Line manager escalation", WfContextUtil.getEscalationLevelName(workItem)); +// +// List events = assertEvents(wfTask, 2); +// assertEscalationEvent(events.get(1), userAdministrator.getOid(), userGuybrushOid, 1, "Line managers", +// Collections.singletonList(userGuybrushOid), Collections.singletonList(userCheeseOid), WorkItemDelegationMethodType.ADD_ASSIGNEES, +// 1, "Line manager escalation"); +// +// // asserts - notifications +// assertNull("lifecycle messages", lifecycleMessages); +// assertNull("process messages", processMessages); +// assertEquals("Wrong # of work items allocation messages", 3, allocationMessages.size()); +// +// ArrayListValuedHashMap sorted = sortByRecipients(allocationMessages); +// assertMessage(sorted.get("guybrush@evolveum.com").get(0), "guybrush@evolveum.com", "Work item has been escalated", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Allocated to (before escalation): Guybrush Threepwood (guybrush)", +// "(in 5 days)"); +// assertMessage(sorted.get("guybrush@evolveum.com").get(1), "guybrush@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to (after escalation): Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to (after escalation): Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// assertMessage(sorted.get("cheese@evolveum.com").get(0), "cheese@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to (after escalation): Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to (after escalation): Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test205EightDaysLater() throws Exception { +// final String TEST_NAME = "test205EightDaysLater"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// clock.resetOverride(); +// clock.overrideDuration("P8D"); +// waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); +// +// // THEN +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertNull("lifecycle messages", lifecycleMessages); +// assertNull("process messages", processMessages); +// assertEquals("Wrong # of work items allocation messages", 4, allocationMessages.size()); +// ArrayListValuedHashMap sorted = sortByRecipients(allocationMessages); +// assertMessage(sorted.get("guybrush@evolveum.com").get(0), "guybrush@evolveum.com", "Work item will be automatically completed in 2 days 12 hours", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// assertMessage(sorted.get("guybrush@evolveum.com").get(1), "guybrush@evolveum.com", "Work item will be automatically completed in 2 days", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// assertMessage(sorted.get("cheese@evolveum.com").get(0), "cheese@evolveum.com", "Work item will be automatically completed in 2 days 12 hours", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// assertMessage(sorted.get("cheese@evolveum.com").get(1), "cheese@evolveum.com", "Work item will be automatically completed in 2 days", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "(in 9 days)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test206ApproveByCheese() throws Exception { +// final String TEST_NAME = "test206ApproveByCheese"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// clock.resetOverride(); +// WorkItemType workItem = getWorkItem(task, result); +// PrismObject cheese = getUserFromRepo(userCheeseOid); +// login(cheese); +// +// // WHEN +// workflowService.completeWorkItem(workItem.getExternalId(), true, "OK. Cheese.", null, result); +// +// // THEN +// login(userAdministrator); +// +// List workItems = getWorkItems(task, result); +// assertEquals("Wrong # of work items on level 2", 2, workItems.size()); +// displayWorkItems("Work item after 1st approval", workItems); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItem).getOid()); +// display("wfTask after 1st approval", wfTask); +// +// assertStage(wfTask, 2, 3, "Security", null); +// assertTriggers(wfTask, 4); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 4, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 4, allocationMessages.size()); +// assertNull("process messages", processMessages); +// +// Map sorted = sortByRecipientsSingle(lifecycleMessages); +// assertMessage(sorted.get("guybrush@evolveum.com"), "guybrush@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "Originally allocated to: Guybrush Threepwood (guybrush)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("cheese@evolveum.com"), "cheese@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "Originally allocated to: Guybrush Threepwood (guybrush)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted.get("elaine@evolveum.com"), "elaine@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Security (2/3)", +// "Allocated to: Elaine Marley (elaine)", "(in 7 days)", "^Result:"); +// assertMessage(sorted.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "A new work item has been created", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "(in 7 days)", "^Result:"); +// +// Map sorted2 = sortByRecipientsSingle(allocationMessages); +// assertMessage(sorted2.get("guybrush@evolveum.com"), "guybrush@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "Originally allocated to: Guybrush Threepwood (guybrush)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("cheese@evolveum.com"), "cheese@evolveum.com", "Work item has been completed", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Line managers (1/3)", +// "Escalation level: Line manager escalation (1)", +// "Originally allocated to: Guybrush Threepwood (guybrush)", +// "|Allocated to: Guybrush Threepwood (guybrush), Ignatius Cheese (cheese)|Allocated to: Ignatius Cheese (cheese), Guybrush Threepwood (guybrush)", +// "Carried out by: Ignatius Cheese (cheese)", +// "Result: APPROVED", "^Deadline:"); +// assertMessage(sorted2.get("elaine@evolveum.com"), "elaine@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Security (2/3)", +// "Allocated to: Elaine Marley (elaine)", "(in 7 days)", "^Result:"); +// assertMessage(sorted2.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", "Work item has been allocated to you", +// "Work item: Approve assigning a-test-1 to carla", "Stage: Security (2/3)", +// "Allocated to: Horridly Scarred Barkeep (barkeeper)", "(in 7 days)", "^Result:"); +// +// display("audit", dummyAuditService); +// } +// +// // notification should be send +// @Test +// public void test208SixDaysLater() throws Exception { +// final String TEST_NAME = "test208SixDaysLater"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// clock.resetOverride(); +// resetTriggerTask(TASK_TRIGGER_SCANNER_OID, TASK_TRIGGER_SCANNER_FILE, result); +// clock.overrideDuration("P6D"); +// +// // WHEN +// waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); +// +// // THEN +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertNull("lifecycle messages", lifecycleMessages); +// assertNull("process messages", processMessages); +// assertEquals("Wrong # of work items allocation messages", 2, allocationMessages.size()); +// Map sorted = sortByRecipientsSingle(allocationMessages); +// +// assertMessage(sorted.get("elaine@evolveum.com"), "elaine@evolveum.com", +// "Work item will be automatically completed in 2 days", +// "Security (2/3)", "Allocated to: Elaine Marley (elaine)", "(in 7 days)"); +// assertMessage(sorted.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", +// "Work item will be automatically completed in 2 days", +// "Security (2/3)", "Allocated to: Horridly Scarred Barkeep (barkeeper)", "(in 7 days)"); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test209EightDaysLater() throws Exception { +// final String TEST_NAME = "test209EightDaysLater"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// clock.resetOverride(); +// clock.overrideDuration("P8D"); +// +// // WHEN +// waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); +// +// // THEN +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// assertEquals("Wrong # of work items lifecycle messages", 2, lifecycleMessages.size()); +// assertEquals("Wrong # of work items allocation messages", 2, allocationMessages.size()); +// assertEquals("Wrong # of process messages", 1, processMessages.size()); +// checkTwoCompleted(lifecycleMessages); +// checkTwoCompleted(allocationMessages); +// assertMessage(processMessages.get(0), "administrator@evolveum.com", "Workflow process instance has finished", +// "Process instance name: Assigning a-test-1 to carla", "Result: REJECTED"); +// +// display("audit", dummyAuditService); +// } +// +// private void checkTwoCompleted(List lifecycleMessages) { +// Map sorted = sortByRecipientsSingle(lifecycleMessages); +// +// assertMessage(sorted.get("elaine@evolveum.com"), "elaine@evolveum.com", +// null, +// "Security (2/3)", "Allocated to: Elaine Marley (elaine)"); +// assertMessage(sorted.get("barkeeper@evolveum.com"), "barkeeper@evolveum.com", +// null, +// "Security (2/3)", "Allocated to: Horridly Scarred Barkeep (barkeeper)"); +// int completed; +// assertMessage(lifecycleMessages.get(0), null, "Work item has been completed", +// "^Carried out by:", +// "Reason: Timed action", +// "Result: REJECTED"); +// assertMessage(lifecycleMessages.get(1), null, "Work item has been completed", +// "^Carried out by:", +// "Reason: Timed action", +// "Result: REJECTED"); +// } +// +// @Test +// public void test220FormRoleAssignmentStart() throws Exception { +// final String TEST_NAME = "test220FormRoleAssignmentStart"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// PrismObject bob = getUserFromRepo(userBobOid); +// login(bob); +// +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// task.setOwner(bob); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// assignRole(userBobOid, roleATest4Oid, task, task.getResult()); +// +// // THEN +// login(userAdministrator); +// assertNotAssignedRole(getUser(userBobOid), roleATest4Oid); +// +// List workItems = getWorkItems(task, result); +// displayWorkItems("Work item after start", workItems); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItems.get(0)).getOid()); +// display("wfTask", wfTask); +// +//// assertTargetTriggers(wfTask, 2); +// +// ItemApprovalProcessStateType info = WfContextUtil.getItemApprovalProcessInfo(wfTask.asObjectable().getWorkflowContext()); +// ApprovalSchemaType schema = info.getApprovalSchema(); +// assertEquals("Wrong # of approval levels", 2, schema.getStage().size()); +// assertApprovalLevel(schema, 1, "Line managers", "P5D", 2); +// assertApprovalLevel(schema, 2, "Role approvers (first)", "P5D", 2); +// assertStage(wfTask, 1, 2, "Line managers", null); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// +// display("audit", dummyAuditService); +// } +// +// @Test +// public void test221FormApproveByLechuck() throws Exception { +// final String TEST_NAME = "test221FormApproveByLechuck"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// WorkItemType workItem = getWorkItem(task, result); +// +// // WHEN +// PrismObject lechuck = getUserFromRepo(userLechuckOid); +// login(lechuck); +// +// workflowService.completeWorkItem(workItem.getExternalId(), true, "OK. LeChuck", null, result); +// +// // THEN +// login(userAdministrator); +// assertNotAssignedRole(getUser(userBobOid), roleATest4Oid); +// List workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 1st approval", workItems); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItems.get(0)).getOid()); +// assertStage(wfTask, 2, 2, "Role approvers (first)", null); +// +// ApprovalStageDefinitionType level = WfContextUtil.getCurrentStageDefinition(wfTask.asObjectable().getWorkflowContext()); +// assertEquals("Wrong evaluation strategy", LevelEvaluationStrategyType.FIRST_DECIDES, level.getEvaluationStrategy()); +// +// // notifications +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// dummyTransport.clearMessages(); +// +// display("audit", dummyAuditService); +// } +// +// +// @Test +// public void test222FormApproveByCheese() throws Exception { +// final String TEST_NAME = "test222FormApproveByCheese"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // GIVEN +// login(userAdministrator); +// SearchResultList workItems = getWorkItems(task, result); +// WorkItemType workItem = sortByOriginalAssignee(workItems).get(userCheeseOid); +// assertNotNull("No work item for cheese", workItem); +// +// // WHEN +// PrismObject cheese = getUserFromRepo(userCheeseOid); +// login(cheese); +// ObjectDelta formDelta = DeltaBuilder.deltaFor(UserType.class, prismContext) +// .item(UserType.F_DESCRIPTION).replace("Hello") +// .asObjectDelta(userBobOid); +// workflowService.completeWorkItem(workItem.getExternalId(), true, "OK. LeChuck", formDelta, result); +// +// // THEN +// login(userAdministrator); +// +// workItems = getWorkItems(task, result); +// displayWorkItems("Work item after 2nd approval", workItems); +// assertEquals("Wrong # of work items after 2nd approval", 0, workItems.size()); +// +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItem).getOid()); +// display("wfTask after 2nd approval", wfTask); +// +// assertStage(wfTask, 2, 2, "Role approvers (first)", null); +// // assertTargetTriggers(wfTask, 4); +// +// // notifications +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// +// // audit +// display("audit", dummyAuditService); +// List records = dummyAuditService.getRecords(); +// assertEquals("Wrong # of audit records", 4, records.size()); +// AuditEventRecord record = records.get(0); +// Collection> deltas = record.getDeltas(); +// assertEquals("Wrong # of deltas in audit record", 1, deltas.size()); +// ObjectDeltaOperation delta = deltas.iterator().next(); +// assertEquals("Wrong # of modifications in audit record delta", 1, delta.getObjectDelta().getModifications().size()); +// ItemDelta itemDelta = delta.getObjectDelta().getModifications().iterator().next(); +// if (!new ItemPath(UserType.F_DESCRIPTION).equivalent(itemDelta.getPath())) { +// fail("Wrong item path in delta: expected: "+new ItemPath(UserType.F_DESCRIPTION)+", found: "+itemDelta.getPath()); +// } +// assertEquals("Wrong value in delta", "Hello", itemDelta.getValuesToReplace().iterator().next().getRealValue()); +// +// // record #1, #2: cancellation of work items of other approvers +// // record #3: finishing process execution +// +// Task rootTask = taskManager.getTaskByIdentifier(wfTask.asObjectable().getParent(), result); +// waitForTaskCloseOrSuspend(rootTask.getOid(), TASK_WAIT_TIMEOUT); +// assertAssignedRole(getUser(userBobOid), roleATest4Oid); +// } +// +// @Test +// public void test250ApproverAssignment() throws Exception { +// final String TEST_NAME = "test250ApproverAssignment"; +// TestUtil.displayTestTitle(this, TEST_NAME); +// Task task = createTask(TestDelivery.class.getName() + "." + TEST_NAME); +// OperationResult result = task.getResult(); +// +// dummyAuditService.clear(); +// dummyTransport.clearMessages(); +// +// // WHEN +// assignRole(userBobOid, roleATest1Oid, SchemaConstants.ORG_APPROVER, task, task.getResult()); +// +// // THEN +// assertNull("bob has assigned a-test-1 as an approver", getUserAssignment(userBobOid, roleATest1Oid, SchemaConstants.ORG_APPROVER)); +// +// List workItems = getWorkItems(task, result); +// displayWorkItems("Work item after start", workItems); +// PrismObject wfTask = getTask(WfContextUtil.getTask(workItems.get(0)).getOid()); +// display("wfTask", wfTask); +// +// ItemApprovalProcessStateType info = WfContextUtil.getItemApprovalProcessInfo(wfTask.asObjectable().getWorkflowContext()); +// ApprovalSchemaType schema = info.getApprovalSchema(); +// assertEquals("Wrong # of approval levels", 3, schema.getStage().size()); +// assertApprovalLevel(schema, 1, "Line managers", "P5D", 2); +// assertApprovalLevel(schema, 2, "Security", "P7D", 1); +// assertApprovalLevel(schema, 3, "Role approvers (all)", "P5D", 2); +// assertStage(wfTask, 1, 3, "Line managers", null); +// +// List lifecycleMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_LIFECYCLE); +// List allocationMessages = dummyTransport.getMessages(DUMMY_WORK_ITEM_ALLOCATION); +// List processMessages = dummyTransport.getMessages(DUMMY_PROCESS); +// display("work items lifecycle notifications", lifecycleMessages); +// display("work items allocation notifications", allocationMessages); +// display("processes notifications", processMessages); +// +// display("audit", dummyAuditService); +// } +// +// +// //endregion +// +// +// +// +// +// +// +// +// //region TODO deduplicate with AbstractWfTestPolicy +// +// private void displayWorkItems(String title, List workItems) { +// workItems.forEach(wi -> display(title, wi)); +// } +// +// protected WorkItemType getWorkItem(Task task, OperationResult result) throws Exception { +// SearchResultList itemsAll = getWorkItems(task, result); +// if (itemsAll.size() != 1) { +// System.out.println("Unexpected # of work items: " + itemsAll.size()); +// for (WorkItemType workItem : itemsAll) { +// System.out.println(PrismUtil.serializeQuietly(prismContext, workItem)); +// } +// } +// assertEquals("Wrong # of total work items", 1, itemsAll.size()); +// return itemsAll.get(0); +// } +// +// private SearchResultList getWorkItems(Task task, OperationResult result) throws Exception { +// return modelService.searchContainers(WorkItemType.class, null, null, task, result); +// } +// +// protected ObjectReferenceType ort(String oid) { +// return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER); +// } +// +// protected PrismReferenceValue prv(String oid) { +// return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER).asReferenceValue(); +// } +// +// protected PrismReference ref(List orts) { +// PrismReference rv = new PrismReference(new QName("dummy")); +// orts.forEach(ort -> rv.add(ort.asReferenceValue().clone())); +// return rv; +// } +// +// protected PrismReference ref(ObjectReferenceType ort) { +// return ref(Collections.singletonList(ort)); +// } +// +// protected Map sortByOriginalAssignee(Collection workItems) { +// Map rv = new HashMap<>(); +// workItems.forEach(wi -> rv.put(wi.getOriginalAssigneeRef().getOid(), wi)); +// return rv; +// } +// //endregion +// +// private void assertMessage(Message message, String recipient, String subject, String... texts) { +// assertNotNull("No message for " + recipient, message); +// assertEquals("Wrong # of recipients", 1, message.getTo().size()); +// if (recipient != null) { +// assertEquals("Wrong recipient", recipient, message.getTo().get(0)); +// } +// if (subject != null) { +// assertEquals("Wrong subject", subject, message.getSubject()); +// } +// condition: for (String text : texts) { +// if (text.startsWith("^")) { +// String pureText = text.substring(1); +// if (message.getBody().contains(pureText)) { +// fail("Message body does contain '" + pureText + "' even if it shouldn't: " + message.getBody()); +// } +// } else if (text.startsWith("|")) { +// String[] strings = StringUtils.split(text, "|"); +// for (String string : strings) { +// if (message.getBody().contains(string)) { +// continue condition; +// } +// } +// fail("Message body does not contain any of " + Arrays.asList(strings) + ": " + message.getBody()); +// } else { +// if (!message.getBody().contains(text)) { +// fail("Message body doesn't contain '" + text + "': " + message.getBody()); +// } +// } +// } +// } +// +// private ArrayListValuedHashMap sortByRecipients(Collection messages) { +// ArrayListValuedHashMap rv = new ArrayListValuedHashMap<>(); +// messages.forEach(m -> +// m.getTo().forEach( +// to -> rv.put(to, m))); +// return rv; +// } +// +// private Map sortByRecipientsSingle(Collection messages) { +// Map rv = new HashMap<>(); +// messages.forEach(m -> +// m.getTo().forEach( +// to -> rv.put(to, m))); +// return rv; +// } +// +// private Task getParentTask(PrismObject task, OperationResult result) +// throws SchemaException, ObjectNotFoundException { +// return taskManager.getTaskByIdentifier(task.asObjectable().getParent(), result); +// } +// +// private void assertTriggers(PrismObject wfTask, int count) { +// assertEquals("Wrong # of triggers", count, wfTask.asObjectable().getTrigger().size()); +// } +// +// private void assertAssignee(WorkItemType workItem, String originalAssignee, String... currentAssignee) { +// assertRefEquals("Wrong original assignee", ObjectTypeUtil.createObjectRef(originalAssignee, ObjectTypes.USER), workItem.getOriginalAssigneeRef()); +// assertReferenceValues(ref(workItem.getAssigneeRef()), currentAssignee); +// } +// +// +// private void assertStage(PrismObject wfTask, Integer stageNumber, Integer stageCount, String stageName, String stageDisplayName) { +// WfContextType wfc = wfTask.asObjectable().getWorkflowContext(); +// assertEquals("Wrong stage number", stageNumber, wfc.getStageNumber()); +// assertEquals("Wrong stage count", stageCount, WfContextUtil.getStageCount(wfc)); +// assertEquals("Wrong stage name", stageName, WfContextUtil.getStageName(wfc)); +// assertEquals("Wrong stage name", stageDisplayName, WfContextUtil.getStageDisplayName(wfc)); +// } +// +// private void assertApprovalLevel(ApprovalSchemaType schema, int number, String name, String duration, int timedActions) { +// ApprovalStageDefinitionType level = schema.getStage().get(number-1); +// assertEquals("Wrong level number", number, (int) level.getNumber()); +// assertEquals("Wrong level name", name, level.getName()); +// assertEquals("Wrong level duration", XmlTypeConverter.createDuration(duration), level.getDuration()); +// assertEquals("Wrong # of timed actions", timedActions, level.getTimedActions().size()); +// } +// +// private List assertEvents(PrismObject wfTask, int expectedCount) { +// WfContextType wfc = wfTask.asObjectable().getWorkflowContext(); +// assertEquals("Wrong # of wf events", expectedCount, wfc.getEvent().size()); +// return wfc.getEvent(); +// } +// +// private void assertEscalationEvent(CaseEventType wfProcessEventType, String initiator, String originalAssignee, +// int stageNumber, String stageName, List assigneesBefore, List delegatedTo, +// WorkItemDelegationMethodType methodType, int newEscalationLevelNumber, String newEscalationLevelName) { +// if (!(wfProcessEventType instanceof WorkItemEscalationEventType)) { +// fail("Wrong event class: expected: " + WorkItemEscalationEventType.class + ", real: " + wfProcessEventType.getClass()); +// } +// WorkItemEscalationEventType event = (WorkItemEscalationEventType) wfProcessEventType; +// assertEvent(event, initiator, originalAssignee, stageNumber, stageName); +// PrismAsserts.assertReferenceValues(ref(event.getAssigneeBefore()), assigneesBefore.toArray(new String[0])); +// PrismAsserts.assertReferenceValues(ref(event.getDelegatedTo()), delegatedTo.toArray(new String[0])); +// assertEquals("Wrong delegation method", methodType, event.getDelegationMethod()); +// assertEquals("Wrong escalation level #", (Integer) newEscalationLevelNumber, event.getNewEscalationLevel().getNumber()); +// assertEquals("Wrong escalation level name", newEscalationLevelName, event.getNewEscalationLevel().getName()); +// } +// +// private void assertCompletionEvent(CaseEventType wfProcessEventType, String initiator, String originalAssignee, +// int stageNumber, String stageName, WorkItemOutcomeType outcome, String comment) { +// if (!(wfProcessEventType instanceof WorkItemCompletionEventType)) { +// fail("Wrong event class: expected: " + WorkItemCompletionEventType.class + ", real: " + wfProcessEventType.getClass()); +// } +// WorkItemCompletionEventType event = (WorkItemCompletionEventType) wfProcessEventType; +// assertEvent(event, initiator, originalAssignee, stageNumber, stageName); +// assertEquals("Wrong outcome", outcome, ApprovalUtils.fromUri(event.getOutput().getOutcome())); +// assertEquals("Wrong comment", comment, event.getOutput().getComment()); +// } +// +// private void assertEvent(CaseEventType processEvent, String initiator, String originalAssignee, Integer stageNumber, +// String stageName) { +// if (!(processEvent instanceof WorkItemEventType)) { +// fail("Wrong event class: expected: " + WorkItemEventType.class + ", real: " + processEvent.getClass()); +// } +// WorkItemEventType event = (WorkItemEventType) processEvent; +// PrismAsserts.assertReferenceValue(ref(event.getInitiatorRef()), initiator); +// assertEquals("Wrong stage #", stageNumber, event.getStageNumber()); +// //assertEquals("Wrong stage name", stageName, event.getStageName()); +// if (originalAssignee != null) { +// assertNotNull("Null original assignee", event.getOriginalAssigneeRef()); +// PrismAsserts.assertReferenceValue(ref(event.getOriginalAssigneeRef()), originalAssignee); +// } +// } + +} diff --git a/testing/story/src/test/resources/delivery/orgs/0-org-monkey-island-modified.xml b/testing/story/src/test/resources/delivery/orgs/0-org-monkey-island-modified.xml new file mode 100644 index 00000000000..b84cdd6d0b9 --- /dev/null +++ b/testing/story/src/test/resources/delivery/orgs/0-org-monkey-island-modified.xml @@ -0,0 +1,134 @@ + + + + + + + + + F0001 + The office of the most respectful Governor. + Governor Office + 0001 + functional + CC0 + The Governor's Mansion + + + + F0002 + Defending the scum since the ancient times. + + + + + Ministry of Defense + 0002 + functional + The towers, bastions and bars + + + + + F0003 + Offending. Anyone. Anywhere. + + + + + Ministry of Offense + 0003 + functional + CC666 + + + + F0004 + Why is the rum always gone? + + + + + Ministry of Rum + 0004 + functional + CCRUM + + + + F0005 + Swashing, buckling and insulting. Hard. + + + + + Swashbuckler Section + 0005 + functional + + + + F0006 + Hosting the worst scumm of the Caribbean. + + + + + + + + + Scumm Bar + 0006 + functional + Mêlée Island + + + + + + PRoot + Project organizational structure root + Projects + project + + + + P0001 + + + + + Save Elaine + 0001 + project + + + + P0002 + + + + + Kidnap and marry Elaine + 0002 + project + + + \ No newline at end of file diff --git a/testing/story/src/test/resources/delivery/roles/role-end-user.xml b/testing/story/src/test/resources/delivery/roles/role-end-user.xml new file mode 100644 index 00000000000..3bd4114588a --- /dev/null +++ b/testing/story/src/test/resources/delivery/roles/role-end-user.xml @@ -0,0 +1,69 @@ + + + + End User + + + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#read + + SystemConfigurationType + + + TaskType + + + LookupTableType + + + RoleType + + + OrgType + + + UserType + + + + + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#assign + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#unassign + request + + self + + + RoleType + + + + + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#modify + execution + + UserType + + + + false + false + system + diff --git a/testing/story/src/test/resources/delivery/roles/role-it-1.xml b/testing/story/src/test/resources/delivery/roles/role-it-1.xml new file mode 100644 index 00000000000..2f2b1894335 --- /dev/null +++ b/testing/story/src/test/resources/delivery/roles/role-it-1.xml @@ -0,0 +1,27 @@ + + + + IT-1 + + + + + diff --git a/testing/story/src/test/resources/delivery/roles/role-it-2.xml b/testing/story/src/test/resources/delivery/roles/role-it-2.xml new file mode 100644 index 00000000000..b58da36918c --- /dev/null +++ b/testing/story/src/test/resources/delivery/roles/role-it-2.xml @@ -0,0 +1,27 @@ + + + + IT-2 + + + + + diff --git a/testing/story/src/test/resources/delivery/rules/k10-tpu-10-rem-elaine.xml b/testing/story/src/test/resources/delivery/rules/k10-tpu-10-rem-elaine.xml new file mode 100644 index 00000000000..d4602ac4f7d --- /dev/null +++ b/testing/story/src/test/resources/delivery/rules/k10-tpu-10-rem-elaine.xml @@ -0,0 +1,30 @@ + + + + + k10-tpu-10-rem-elaine + + employeeType + 10 + + + + + + \ No newline at end of file diff --git a/testing/story/src/test/resources/delivery/rules/k10-tpu-10.xml b/testing/story/src/test/resources/delivery/rules/k10-tpu-10.xml new file mode 100644 index 00000000000..4a80ad1a5e4 --- /dev/null +++ b/testing/story/src/test/resources/delivery/rules/k10-tpu-10.xml @@ -0,0 +1,29 @@ + + + + + k10-tpu-10 + + employeeType + 10 + + + + + \ No newline at end of file diff --git a/testing/story/src/test/resources/delivery/rules/k10.xml b/testing/story/src/test/resources/delivery/rules/k10.xml new file mode 100644 index 00000000000..cca3f118b3b --- /dev/null +++ b/testing/story/src/test/resources/delivery/rules/k10.xml @@ -0,0 +1,54 @@ + + + + + k10 + + + + + + + UserType + + + + + + + + + + + employeeType + 10 + + + + + + + + + + + 2 + + \ No newline at end of file diff --git a/testing/story/src/test/resources/delivery/schema/rules.xsd b/testing/story/src/test/resources/delivery/schema/rules.xsd new file mode 100644 index 00000000000..8dc0a12c799 --- /dev/null +++ b/testing/story/src/test/resources/delivery/schema/rules.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + diff --git a/testing/story/src/test/resources/delivery/users/barkeeper.xml b/testing/story/src/test/resources/delivery/users/barkeeper.xml new file mode 100644 index 00000000000..8728b9ff5a5 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/barkeeper.xml @@ -0,0 +1,50 @@ + + + + + + barkeeper + + + + + + + + + + + + + + + + + + Horridly Scarred Barkeep + Barkeeper + Scumm + barkeeper@evolveum.com + + + a123456 + + + diff --git a/testing/story/src/test/resources/delivery/users/bob.xml b/testing/story/src/test/resources/delivery/users/bob.xml new file mode 100644 index 00000000000..97ef9f0bcc3 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/bob.xml @@ -0,0 +1,37 @@ + + + + + bob + + + + + + + + Ghost Pirate Bob + Bob + bob@evolveum.com + + + a123456 + + + diff --git a/testing/story/src/test/resources/delivery/users/carla.xml b/testing/story/src/test/resources/delivery/users/carla.xml new file mode 100644 index 00000000000..27ccc8f1e15 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/carla.xml @@ -0,0 +1,42 @@ + + + + + carla + + + + + + + + + + + + + Carla the Swordmaster + Carla + carla@evolveum.com + + + a123456 + + + diff --git a/testing/story/src/test/resources/delivery/users/cheese.xml b/testing/story/src/test/resources/delivery/users/cheese.xml new file mode 100644 index 00000000000..c1ab29a2789 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/cheese.xml @@ -0,0 +1,94 @@ + + + + + + cheese + The owner of Scumm Bar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ignatius Cheese + Ignatius + Cheese + cheese@evolveum.com + + + a123456 + + + diff --git a/testing/story/src/test/resources/delivery/users/chef.xml b/testing/story/src/test/resources/delivery/users/chef.xml new file mode 100644 index 00000000000..e1ed27ae0c0 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/chef.xml @@ -0,0 +1,66 @@ + + + + + chef + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Scumm Bar Chef + Chef + Scumm + chef@evolveum.com + + + a123456 + + + + diff --git a/testing/story/src/test/resources/delivery/users/elaine.xml b/testing/story/src/test/resources/delivery/users/elaine.xml new file mode 100644 index 00000000000..343fff6cb37 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/elaine.xml @@ -0,0 +1,80 @@ + + + + elaine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Elaine Marley + Elaine + Marley + Governor + elaine@evolveum.com + + + a123456 + + + \ No newline at end of file diff --git a/testing/story/src/test/resources/delivery/users/guybrush.xml b/testing/story/src/test/resources/delivery/users/guybrush.xml new file mode 100644 index 00000000000..9a55b6c315f --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/guybrush.xml @@ -0,0 +1,41 @@ + + + + + guybrush + + + + + + + + + + Guybrush Threepwood + Guybrush + Threepwood + guybrush@evolveum.com + + + a123456 + + + diff --git a/testing/story/src/test/resources/delivery/users/lechuck.xml b/testing/story/src/test/resources/delivery/users/lechuck.xml new file mode 100644 index 00000000000..af6f7cb9979 --- /dev/null +++ b/testing/story/src/test/resources/delivery/users/lechuck.xml @@ -0,0 +1,36 @@ + + + + + lechuck + + + + + + Captain LeChuck + LeChuck + lechuck@evolveum.com + + + a123456 + + +