diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/CertDecisionHelper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/CertDecisionHelper.java index 1a867e22013..7f810738c8c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/CertDecisionHelper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/CertDecisionHelper.java @@ -31,6 +31,7 @@ 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.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; @@ -49,39 +50,29 @@ */ public class CertDecisionHelper implements Serializable { - IColumn createObjectNameColumn(final PageBase page, final String headerKey) { - IColumn column; - column = new LinkColumn(page.createStringResource(headerKey), - AccessCertificationCaseType.F_OBJECT_REF.getLocalPart(), CertCaseOrDecisionDto.F_OBJECT_NAME) { - - @Override - public void onClick(AjaxRequestTarget target, IModel rowModel) { - CertCaseOrDecisionDto dto = rowModel.getObject(); - dispatchToObjectDetailsPage(dto.getCertCase().getObjectRef(), page, false); - } - }; - return column; + public enum WhichObject { + OBJECT, TARGET } - public IColumn createObjectOrTargetTypeColumn(final boolean isObject, final PageBase page) { // isObject = true for object, false for target + IColumn createTypeColumn(final WhichObject which, final PageBase page) { IColumn column; column = new IconColumn(page.createStringResource("")) { @Override protected IModel createIconModel(IModel rowModel) { - ObjectTypeGuiDescriptor guiDescriptor = getObjectTypeDescriptor(isObject, rowModel); + ObjectTypeGuiDescriptor guiDescriptor = getObjectTypeDescriptor(which, rowModel); String icon = guiDescriptor != null ? guiDescriptor.getBlackIcon() : ObjectTypeGuiDescriptor.ERROR_ICON; return new Model<>(icon); } - private ObjectTypeGuiDescriptor getObjectTypeDescriptor(boolean isObject, IModel rowModel) { - QName targetType = isObject ? rowModel.getObject().getObjectType() : rowModel.getObject().getTargetType(); + private ObjectTypeGuiDescriptor getObjectTypeDescriptor(WhichObject which, IModel rowModel) { + QName targetType = rowModel.getObject().getObjectType(which); return ObjectTypeGuiDescriptor.getDescriptor(ObjectTypes.getObjectTypeFromTypeQName(targetType)); } @Override public void populateItem(Item> item, String componentId, IModel rowModel) { super.populateItem(item, componentId, rowModel); - ObjectTypeGuiDescriptor guiDescriptor = getObjectTypeDescriptor(isObject, rowModel); + ObjectTypeGuiDescriptor guiDescriptor = getObjectTypeDescriptor(which, rowModel); if (guiDescriptor != null) { item.add(AttributeModifier.replace("title", page.createStringResource(guiDescriptor.getLocalizationKey()))); item.add(new TooltipBehavior()); @@ -91,10 +82,24 @@ public void populateItem(Item> item, Strin return column; } + IColumn createObjectNameColumn(final PageBase page, final String headerKey) { + IColumn column; + column = new LinkColumn(page.createStringResource(headerKey), + AccessCertificationCaseType.F_OBJECT_REF.getLocalPart(), CertCaseOrDecisionDto.F_OBJECT_NAME) { + + @Override + public void onClick(AjaxRequestTarget target, IModel rowModel) { + CertCaseOrDecisionDto dto = rowModel.getObject(); + dispatchToObjectDetailsPage(dto.getCertCase().getObjectRef(), page, false); + } + }; + return column; + } + IColumn createTargetNameColumn(final PageBase page, final String headerKey) { IColumn column; column = new LinkColumn(page.createStringResource(headerKey), - AccessCertificationCaseType.F_TARGET_REF.getLocalPart(), CertCaseOrDecisionDto.F_TARGET_NAME) { + AccessCertificationCaseType.F_TARGET_REF.getLocalPart(), CertCaseOrDecisionDto.F_TARGET_NAME) { @Override public void onClick(AjaxRequestTarget target, IModel rowModel) { @@ -105,6 +110,11 @@ public void onClick(AjaxRequestTarget target, IModel rowM return column; } + IColumn createConflictingNameColumn(final PageBase page, final String headerKey) { + return new PropertyColumn(page.createStringResource(headerKey), + CertCaseOrDecisionDto.F_CONFLICTING_TARGETS); + } + public IColumn createDetailedInfoColumn(final PageBase page) { IColumn column; column = new IconColumn(page.createStringResource("")) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java index b83eaffb4c4..0f9c72a059c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java @@ -16,6 +16,7 @@ package com.evolveum.midpoint.web.page.admin.certification; +import com.evolveum.midpoint.certification.api.AccessCertificationApiConstants; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; @@ -61,6 +62,8 @@ import java.util.ArrayList; import java.util.List; +import static com.evolveum.midpoint.web.page.admin.certification.CertDecisionHelper.WhichObject.OBJECT; +import static com.evolveum.midpoint.web.page.admin.certification.CertDecisionHelper.WhichObject.TARGET; import static com.evolveum.midpoint.web.page.admin.certification.PageCertCampaigns.*; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCasesStatisticsType.*; @@ -264,13 +267,13 @@ private List> initColumns() { IColumn column; - column = helper.createObjectOrTargetTypeColumn(true, this); + column = helper.createTypeColumn(OBJECT, this); columns.add(column); column = helper.createObjectNameColumn(this, "PageCertCampaign.table.objectName"); columns.add(column); - column = helper.createObjectOrTargetTypeColumn(false, this); + column = helper.createTypeColumn(TARGET, this); columns.add(column); column = helper.createTargetNameColumn(this, "PageCertCampaign.table.targetName"); @@ -279,6 +282,11 @@ private List> initColumns() { column = helper.createDetailedInfoColumn(this); columns.add(column); + if (AccessCertificationApiConstants.EXCLUSION_HANDLER_URI.equals(campaignModel.getObject().getHandlerUri())) { + column = helper.createConflictingNameColumn(this, "PageCertCampaign.table.conflictingTargetName"); + columns.add(column); + } + column = new PropertyColumn(createStringResource("PageCertCampaign.table.reviewers"), CertCaseDto.F_REVIEWERS); columns.add(column); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java index 5b015777188..f551fe431d3 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java @@ -72,6 +72,8 @@ import java.util.Date; import java.util.List; +import static com.evolveum.midpoint.web.page.admin.certification.CertDecisionHelper.WhichObject.OBJECT; +import static com.evolveum.midpoint.web.page.admin.certification.CertDecisionHelper.WhichObject.TARGET; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationResponseType.*; /** @@ -182,13 +184,13 @@ private List> initColumns() { column = new CheckBoxHeaderColumn<>(); columns.add(column); - column = helper.createObjectOrTargetTypeColumn(true, this); + column = helper.createTypeColumn(OBJECT, this); columns.add(column); column = helper.createObjectNameColumn(this, "PageCertDecisions.table.objectName"); columns.add(column); - column = helper.createObjectOrTargetTypeColumn(false, this); + column = helper.createTypeColumn(TARGET, this); columns.add(column); column = helper.createTargetNameColumn(this, "PageCertDecisions.table.targetName"); @@ -197,7 +199,10 @@ private List> initColumns() { column = helper.createDetailedInfoColumn(this); columns.add(column); - if (WebComponentUtil.isAuthorized(AuthorizationConstants.AUTZ_UI_CERTIFICATION_ALL_URL, + column = helper.createConflictingNameColumn(this, "PageCertDecisions.table.conflictingTargetName"); + columns.add(column); + + if (WebComponentUtil.isAuthorized(AuthorizationConstants.AUTZ_UI_CERTIFICATION_ALL_URL, AuthorizationConstants.AUTZ_UI_CERTIFICATION_CAMPAIGN_URL)) { column = new LinkColumn( diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/dto/CertCaseOrDecisionDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/dto/CertCaseOrDecisionDto.java index 1d16a4b6593..f22c45c8527 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/dto/CertCaseOrDecisionDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/dto/CertCaseOrDecisionDto.java @@ -20,16 +20,15 @@ import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.util.CertCampaignTypeUtil; import com.evolveum.midpoint.web.component.util.Selectable; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCaseType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationStageType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.web.page.admin.certification.CertDecisionHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DurationFormatUtils; import org.apache.commons.lang3.Validate; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; -import java.util.Date; +import java.util.*; /** * A common superclass for CertCaseDto + CertDecisionDto. @@ -46,6 +45,7 @@ public class CertCaseOrDecisionDto extends Selectable { public static final String F_CAMPAIGN_NAME = "campaignName"; public static final String F_REVIEW_REQUESTED = "reviewRequested"; public static final String F_DEADLINE_AS_STRING = "deadlineAsString"; + public static final String F_CONFLICTING_TARGETS = "conflictingTargets"; private AccessCertificationCaseType certCase; private String objectName; @@ -82,6 +82,14 @@ public QName getObjectType() { return certCase.getObjectRef().getType(); } + public QName getObjectType(CertDecisionHelper.WhichObject which) { + switch (which) { + case OBJECT: return getObjectType(); + case TARGET: return getTargetType(); + default: return null; + } + } + public String getTargetName() { return targetName; } @@ -188,4 +196,34 @@ private String computeDeadlineAsString(PageBase page) { public String getDeadlineAsString() { return deadlineAsString; } + + /** + * Preliminary implementation. Eventually we will create a list of hyperlinks pointing to the actual objects. + */ + public String getConflictingTargets() { + if (!(certCase instanceof AccessCertificationAssignmentCaseType)) { + return ""; + } + AccessCertificationAssignmentCaseType assignmentCase = (AccessCertificationAssignmentCaseType) certCase; + if (assignmentCase.getAssignment() == null) { + return ""; + } + Set exclusions = new TreeSet<>(); + for (EvaluatedPolicyRuleTriggerType trigger : assignmentCase.getAssignment().getTrigger()) { + if (!(trigger instanceof EvaluatedExclusionTriggerType)) { + continue; + } + EvaluatedExclusionTriggerType exclusionTrigger = (EvaluatedExclusionTriggerType) trigger; + ObjectReferenceType conflicting = exclusionTrigger.getConflictingObjectRef(); + if (conflicting == null) { + continue; + } + if (conflicting.getTargetName() != null) { + exclusions.add(conflicting.getTargetName().getOrig()); + } else { + exclusions.add(conflicting.getOid()); // TODO try to resolve? + } + } + return StringUtils.join(exclusions, ", "); + } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/handlers/DirectAssignmentCertGuiHandler.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/handlers/DirectAssignmentCertGuiHandler.java index fc0c9136652..4b05c5f95dd 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/handlers/DirectAssignmentCertGuiHandler.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/handlers/DirectAssignmentCertGuiHandler.java @@ -44,6 +44,7 @@ public class DirectAssignmentCertGuiHandler implements CertGuiHandler { @Override public String getCaseInfoButtonTitle(IModel rowModel, PageBase page) { + CertCaseOrDecisionDto dto = rowModel.getObject(); AccessCertificationCaseType _case = dto.getCertCase(); if (!(_case instanceof AccessCertificationAssignmentCaseType)) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java index 8b069627cfd..0cedd4c4417 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2016 Biznet, Evolveum + * Copyright (c) 2012-2017 Biznet, Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,9 @@ import org.apache.wicket.markup.html.form.RequiredTextField; import org.apache.wicket.model.Model; -import com.evolveum.midpoint.common.policy.ValuePolicyGenerator; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; @@ -51,6 +51,8 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.Producer; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; @@ -66,6 +68,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceCredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceType; 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.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; @@ -344,18 +347,19 @@ public OperationResult run() { task.setOwner(user.asPrismObject()); OperationResult result = new OperationResult("generateUserNonce"); ProtectedStringType nonceCredentials = new ProtectedStringType(); - nonceCredentials.setClearValue(generateNonce(noncePolicy, task, result)); - - NonceType nonceType = new NonceType(); - nonceType.setValue(nonceCredentials); - - ObjectDelta nonceDelta; try { + nonceCredentials.setClearValue(generateNonce(noncePolicy, task, user.asPrismObject(), result)); + + NonceType nonceType = new NonceType(); + nonceType.setValue(nonceCredentials); + + ObjectDelta nonceDelta; + nonceDelta = ObjectDelta.createModificationReplaceContainer(UserType.class, user.getOid(), SchemaConstants.PATH_NONCE, getPrismContext(), nonceType); WebModelServiceUtils.save(nonceDelta, result, task, PageForgotPassword.this); - } catch (SchemaException e) { + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) { result.recordFatalError("Failed to generate nonce for user"); LoggingUtils.logException(LOGGER, "Failed to generate nonce for user: " + e.getMessage(), e); @@ -368,7 +372,7 @@ public OperationResult run() { }); } - private String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) { + private String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, PrismObject user, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { ValuePolicyType policy = null; if (noncePolicy != null && noncePolicy.getValuePolicyRef() != null) { @@ -377,7 +381,8 @@ private String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, policy = valuePolicy.asObjectable(); } - return ValuePolicyGenerator.generate(policy != null ? policy.getStringPolicy() : null, 24, result); + return getModelInteractionService().generateValue(policy != null ? policy.getStringPolicy() : null, + 24, false, user, "nonce generation", task, result); } // Check if the user exists with the given email and username in the idm diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java index 8210ff3bc64..76e4bc32c12 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2012-2016 Biznet, Evolveum + * Copyright (c) 2012-2017 Biznet, Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,11 +41,11 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import com.evolveum.midpoint.common.policy.ValuePolicyGenerator; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.crypto.EncryptionException; @@ -484,9 +484,9 @@ private void resetPassword(UserType user, AjaxRequestTarget target) { systemConfig.asObjectable().getGlobalPasswordPolicyRef().getOid(), options, task, result); LOGGER.trace("password policy {}", valPolicy); - newPassword = ValuePolicyGenerator.generate(valPolicy.asObjectable().getStringPolicy(), - valPolicy.asObjectable().getStringPolicy().getLimitations().getMinLength(), - result); + newPassword = getModelInteractionService().generateValue(valPolicy.asObjectable().getStringPolicy(), + valPolicy.asObjectable().getStringPolicy().getLimitations().getMinLength(), false, + user.asPrismObject(), "security questions password generation", task, result); } else { // TODO What if there is no policy? What should be done to // provide a new automatic password @@ -502,7 +502,7 @@ private void resetPassword(UserType user, AjaxRequestTarget target) { throw new RestartResponseException(PageLogin.class); } - } catch (ObjectNotFoundException e1) { + } catch (ObjectNotFoundException | ExpressionEvaluationException e1) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't reset password", e1); } catch (SchemaException e1) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java index 73c068271a9..43d68be4403 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java @@ -32,12 +32,12 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.util.string.StringValue; -import com.evolveum.midpoint.common.policy.ValuePolicyGenerator; import com.evolveum.midpoint.gui.api.component.captcha.CaptchaPanel; import com.evolveum.midpoint.gui.api.component.password.PasswordPanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.delta.ObjectDelta; @@ -46,6 +46,8 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.Producer; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; @@ -63,6 +65,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceCredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceType; 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.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; @@ -540,7 +543,7 @@ private void saveUser(Task task, OperationResult result) { ObjectDelta userDelta; try { userDelta = prepareUserDelta(task, result); - } catch (SchemaException e) { + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) { result.recordFatalError("Failed to create delta for user: " + e.getMessage(), e); return; } @@ -552,7 +555,7 @@ private void saveUser(Task task, OperationResult result) { } - private ObjectDelta prepareUserDelta(Task task, OperationResult result) throws SchemaException { + private ObjectDelta prepareUserDelta(Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { if (getOidFromParams(getPageParameters()) == null) { LOGGER.trace("Preparing user ADD delta (new user registration)"); UserType userType = prepareUserToSave(task, result); @@ -584,7 +587,7 @@ private ObjectDelta prepareUserDelta(Task task, OperationResult result } } - private UserType prepareUserToSave(Task task, OperationResult result) { + private UserType prepareUserToSave(Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { SelfRegistrationDto selfRegistrationConfiguration = getSelfRegistrationConfiguration(); UserType userType = userModel.getObject(); @@ -644,7 +647,7 @@ private DynamicFormPanel getDynamicFormPanel() { } private void createCredentials(UserType user, NonceCredentialsPolicyType noncePolicy, Task task, - OperationResult result) { + OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { NonceType nonceType = createNonce(noncePolicy, task, result); // PasswordType password = createPassword(); @@ -661,9 +664,9 @@ private void createCredentials(UserType user, NonceCredentialsPolicyType noncePo } - private NonceType createNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) { + private NonceType createNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { ProtectedStringType nonceCredentials = new ProtectedStringType(); - nonceCredentials.setClearValue(generateNonce(noncePolicy, task, result)); + nonceCredentials.setClearValue(generateNonce(noncePolicy, null, task, result)); NonceType nonceType = new NonceType(); nonceType.setValue(nonceCredentials); @@ -678,7 +681,8 @@ private PasswordType createPassword() { return password; } - private String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) { + private String generateNonce(NonceCredentialsPolicyType noncePolicy, + PrismObject user, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { ValuePolicyType policy = null; if (noncePolicy != null && noncePolicy.getValuePolicyRef() != null) { @@ -687,7 +691,8 @@ private String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, policy = valuePolicy.asObjectable(); } - return ValuePolicyGenerator.generate(policy != null ? policy.getStringPolicy() : null, 24, result); + return getModelInteractionService().generateValue(policy != null ? policy.getStringPolicy() : null, + 24, false, user, "nonce generation (registration)", task, result); } private String getPassword() { diff --git a/gui/admin-gui/src/main/resources/localization/Midpoint.properties b/gui/admin-gui/src/main/resources/localization/Midpoint.properties index eab14c8b980..4894b11000a 100644 --- a/gui/admin-gui/src/main/resources/localization/Midpoint.properties +++ b/gui/admin-gui/src/main/resources/localization/Midpoint.properties @@ -1266,6 +1266,7 @@ PageCertCampaign.table.reviewedAt=Reviewed at PageCertCampaign.table.reviewedBy=Reviewed by PageCertCampaign.table.reviewedInStage=In stage PageCertCampaign.table.targetName=Target +PageCertCampaign.table.conflictingTargetName=Conflicting PageCertCampaign.noReviewers=(none) PageCertCampaign.time=Time PageCertCampaign.title=Access Certification Campaign @@ -1294,6 +1295,7 @@ PageCertDecisions.table.decision=Decision PageCertDecisions.table.objectName=Object PageCertDecisions.table.requested=Requested PageCertDecisions.table.targetName=Target +PageCertDecisions.table.conflictingTargetName=Conflicting PageCertDecisions.table.targetType=Type PageCertDecisions.title=My cases to decide PageCertDefinition.basicInformation=Basic information diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java index 002f9ac370f..8d4fd5e87cc 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java @@ -529,7 +529,6 @@ public String debugDump(int indent) { sb.append(PrettyPrinter.prettyPrint(value)); } else { PrismPropertyValue.debugDumpValue(sb, indent + 1, value.getValue(), prismContext); - //sb.append(PrettyPrinter.prettyPrint(value.getValue())); } } } @@ -551,7 +550,7 @@ public String debugDump(int indent) { if (DebugUtil.isDetailedDebugDump()) { sb.append(PrettyPrinter.prettyPrint(value)); } else { - sb.append(value.getValue()); + PrismPropertyValue.debugDumpValue(sb, indent + 1, value.getValue(), prismContext); } } if (iterator.hasNext()) { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismPropertyValue.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismPropertyValue.java index fabc5057adc..3b696cb08f9 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismPropertyValue.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismPropertyValue.java @@ -604,7 +604,7 @@ public String debugDump(int indent) { public static void debugDumpValue(StringBuilder sb, int indent, Object value, PrismContext prismContext) { String formatted; - if (DebugUtil.getPrettyPrintBeansAs() != null && value != null && prismContext != null + if (DebugUtil.getPrettyPrintBeansAs() != null && value != null && !(value instanceof Enum) && prismContext != null && value.getClass().getAnnotation(XmlType.class) != null) { try { formatted = prismContext.serializerFor(DebugUtil.getPrettyPrintBeansAs()).serializeRealValue(value, new QName("value")); 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 699285d81d1..78ce5e8a039 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 @@ -717,7 +717,14 @@ public void swallow(ItemDelta newItemDelta) throws SchemaException { // nothing to do for DELETE } - private Collection> createEmptyModifications() { + public void swallow(List> itemDeltas) throws SchemaException { + for (ItemDelta itemDelta : itemDeltas) { + swallow(itemDelta); + } + } + + + private Collection> createEmptyModifications() { // Lists are easier to debug return new ArrayList<>(); } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java index 1e093f34fc3..5d07e514ff3 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java @@ -16,6 +16,7 @@ package com.evolveum.midpoint.prism.marshaller; import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.schema.SchemaRegistry; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.prism.xnode.*; @@ -84,7 +85,16 @@ public XNode marshall(@Nullable T bean, @Nullable SerializationContext ctx) Marshaller marshaller = specialMarshallers.get(bean.getClass()); if (marshaller != null) { return marshaller.marshal(bean, ctx); - } else if (bean instanceof Containerable) { + } + + // avoiding chatty PolyString serializations (namespace declaration + orig + norm) + if (bean instanceof PolyString) { + bean = (T) ((PolyString) bean).getOrig(); + } else if (bean instanceof PolyStringType) { + bean = (T) ((PolyStringType) bean).getOrig(); + } + + if (bean instanceof Containerable) { return prismContext.xnodeSerializer().serializeRealValue(bean, new QName("dummy")).getSubnode(); } else if (bean instanceof Enum) { return marshalEnum((Enum) bean, ctx); @@ -414,7 +424,7 @@ private XNode marshallValue(T value, QName valueType, boolean isAttribute, S } private PrimitiveXNode createPrimitiveXNode(T value, QName valueType, boolean isAttribute) { - PrimitiveXNode xprim = new PrimitiveXNode(); + PrimitiveXNode xprim = new PrimitiveXNode<>(); xprim.setValue(value, valueType); xprim.setAttribute(isAttribute); return xprim; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java index 55b4df49cf9..06514fdfa00 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java @@ -99,7 +99,6 @@ private void createSpecialUnmarshallerMaps() { * 1. typeName is processable by unmarshaller - i.e. it corresponds to simple or complex type NOT of containerable character */ - @NotNull T unmarshal(@NotNull XNode xnode, @NotNull QName typeQName, @NotNull ParsingContext pc) throws SchemaException { Class classType = getSchemaRegistry().determineClassForType(typeQName); // TODO use correct method! if (classType == null) { @@ -114,7 +113,10 @@ T unmarshal(@NotNull XNode xnode, @NotNull QName typeQName, @NotNull Parsing return unmarshal(xnode, classType, pc); } - @NotNull + /** + * TODO: decide if this method should be marked @NotNull. + * Basically the problem is with primitives. When parsed, they sometimes return null. The question is if it's correct. + */ T unmarshal(@NotNull XNode xnode, @NotNull Class beanClass, @NotNull ParsingContext pc) throws SchemaException { T value = unmarshalInternal(xnode, beanClass, pc); if (PrismContextImpl.isExtraValidation() && value != null) { @@ -156,12 +158,7 @@ private T unmarshalInternal(@NotNull XNode xnode, @NotNull Class beanClas if (unmarshaller != null) { return unmarshaller.unmarshal(prim, beanClass, pc); } else if (prim.isEmpty()) { - // Special case. Just return empty object - try { - return beanClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new SystemException("Cannot instantiate "+beanClass+": "+e.getMessage(), e); - } + return instantiate(beanClass); // Special case. Just return empty object } else { throw new SchemaException("Cannot convert primitive value to bean of type " + beanClass); } @@ -189,7 +186,7 @@ private BeanMarshaller getBeanMarshaller() { } //endregion - + @NotNull private T unmarshalFromMapOrHeteroList(@NotNull XNode mapOrList, @NotNull Class beanClass, @NotNull ParsingContext pc) throws SchemaException { if (Containerable.class.isAssignableFrom(beanClass)) { @@ -204,16 +201,22 @@ private T unmarshalFromMapOrHeteroList(@NotNull XNode mapOrList, @NotNull Cl throw new SchemaException("SearchFilterType is not supported in combination of heterogeneous list."); } } else { - T bean; - try { - bean = beanClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new SystemException("Cannot instantiate bean of type " + beanClass + ": " + e.getMessage(), e); - } + T bean = instantiate(beanClass); return unmarshalFromMapOrHeteroListToBean(bean, mapOrList, null, pc); } } + private T instantiate(@NotNull Class beanClass) { + T bean; + try { + bean = beanClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new SystemException("Cannot instantiate bean of type " + beanClass + ": " + e.getMessage(), e); + } + return bean; + } + + @NotNull private T unmarshalFromMapOrHeteroListToBean(@NotNull T bean, @NotNull XNode mapOrList, @Nullable Collection keysToParse, @NotNull ParsingContext pc) throws SchemaException { @SuppressWarnings("unchecked") Class beanClass = (Class) bean.getClass(); @@ -224,6 +227,9 @@ private T unmarshalFromMapOrHeteroListToBean(@NotNull T bean, @NotNull XNode if (keysToParse != null && !keysToParse.contains(key.getLocalPart())) { continue; } + if (entry.getValue() == null) { + continue; + } unmarshalEntry(bean, beanClass, entry.getKey(), entry.getValue(), mapOrList, false, pc); } } else if (mapOrList.isHeterogeneousList()) { @@ -366,18 +372,29 @@ private void unmarshalEntry(@NotNull T bean, @NotNull Class beanClass, final Method getter = mechanism.getter; final Method setter = mechanism.setter; - Class paramType = mechanism.paramType; final boolean wrapInJaxbElement = mechanism.wrapInJaxbElement; - if (Element.class.isAssignableFrom(paramType)) { + if (Element.class.isAssignableFrom(mechanism.paramType)) { throw new IllegalArgumentException("DOM not supported in field "+actualPropertyName+" in "+beanClass); } + // The type T that is expected by the bean, i.e. either by + // - setMethod(T value), or + // - Collection getMethod() + // We use it to retrieve the correct value when parsing the node. + // We might specialize it using the information derived from the node (to deal with inclusive polymorphism, + // i.e. storing ExclusionPolicyConstraintType where AbstractPolicyConstraintType is expected). + @NotNull Class paramType; + if (!storeAsRawType && !isHeteroListProperty) { - paramType = specializeNodeType(node, paramType, pc); - if (paramType == null) { + Class t = specializeParamType(node, mechanism.paramType, pc); + if (t == null) { // indicates a problem return; + } else { + paramType = t; } + } else { + paramType = mechanism.paramType; } if (!(node instanceof ListXNode) && Object.class.equals(paramType) && !storeAsRawType) { @@ -397,7 +414,7 @@ private void unmarshalEntry(@NotNull T bean, @NotNull Class beanClass, try { Object value = unmarshalSinglePropValue(node, actualPropertyName, paramType, storeAsRawType, beanClass, pc); if (wrapInJaxbElement) { - propValue = wrapInJaxbElement(propValue, mechanism.objectFactory, + propValue = wrapInJaxbElement(value, mechanism.objectFactory, mechanism.elementFactoryMethod, propName, beanClass, pc); } else { propValue = value; @@ -507,11 +524,12 @@ private void unmarshalEntry(@NotNull T bean, @NotNull Class beanClass, } } - private Class specializeNodeType(@NotNull XNode node, @NotNull Class expectedType,@NotNull ParsingContext pc) + private Class specializeParamType(@NotNull XNode node, @NotNull Class expectedType, @NotNull ParsingContext pc) throws SchemaException { if (node.getTypeQName() != null) { Class explicitType = getSchemaRegistry().determineClassForType(node.getTypeQName()); - return explicitType != null ? explicitType : expectedType; //check for subclasses??? + return explicitType != null && expectedType.isAssignableFrom(explicitType) ? explicitType : expectedType; + // (if not assignable, we hope the adaptation will do it) } else if (node.getElementName() != null) { Collection candidateTypes = getSchemaRegistry() .findTypeDefinitionsByElementName(node.getElementName(), TypeDefinition.class); @@ -910,7 +928,7 @@ private void unmarshallToAnyValue(T bean, Class beanClass, S subBean, Cla } catch (ClassCastException e) { throw new SystemException("Getter "+getter+" on bean of type "+beanClass+" returned "+getterReturn+" instead of collection"); } - col.add(subBeanElement != null ? subBeanElement.getValue() : subBeanElement); + col.add(subBeanElement != null ? subBeanElement.getValue() : null); } private void unmarshalToAnyUsingField(T bean, Field anyField, QName elementName, XNode xsubnode, ParsingContext pc) throws SchemaException{ @@ -946,9 +964,11 @@ private Object unmarshalSinglePropValue(XNode xsubnode, String fieldName, Class } else { // paramType is what we expect e.g. based on parent definition // but actual type (given by xsi:type/@typeDef) may be different, e.g. more specific - paramType = specializeNodeType(xsubnode, paramType, pc); + // (although we already specialized paramType within the caller, we do it again here, because the subnode + // used here might be a child of node used in the caller) + paramType = specializeParamType(xsubnode, paramType, pc); if (paramType == null) { - return null; // skipping this element + return null; // skipping this element in case of error } if (xsubnode instanceof PrimitiveXNode || xsubnode instanceof MapXNode || xsubnode.isHeterogeneousList()) { propValue = unmarshal(xsubnode, paramType, pc); @@ -1027,12 +1047,7 @@ private T unmarshalSearchFilterType(MapXNode xmap, if (xmap == null) { return null; } - T filterType; - try { - filterType = beanClass.newInstance(); - } catch (InstantiationException|IllegalAccessException e) { - throw new SystemException("Cannot instantiate " + beanClass + ": " + e.getMessage(), e); - } + T filterType = instantiate(beanClass); filterType.parseFromXNode(xmap, prismContext); return filterType; } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismParserImpl.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismParserImpl.java index f3ba8f3eb44..2f27b716e57 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismParserImpl.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismParserImpl.java @@ -209,7 +209,7 @@ private IV doParseItemValue(RootXNode root, Class cla @Nullable private IV getSingleParentlessValue(Item item) { - if (item.isEmpty()) { + if (item.size() == 0) { return null; } else if (item.size() == 1) { IV value = item.getValues().get(0); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java index eee1b01fdc7..b6f870bc3b0 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/JavaTypeConverter.java @@ -59,167 +59,172 @@ public class JavaTypeConverter { "yyyy-MM-dd'T'HHmmss.SSSz", "yyyy-MM-dd" }; - + + @SuppressWarnings("unchecked") public static T convert(Class expectedType, Object rawValue) { + return (T) convert(expectedType, rawValue, true); + } + + public static Object convert(Class expectedType, Object rawValue, boolean failIfImpossible) { if (rawValue == null || expectedType.isInstance(rawValue)) { - return (T)rawValue; + return rawValue; } if (rawValue instanceof PrismPropertyValue) { rawValue = ((PrismPropertyValue)rawValue).getValue(); } // This really needs to be checked twice if (rawValue == null || expectedType.isInstance(rawValue)) { - return (T)rawValue; + return rawValue; } // Primitive types // boolean if (expectedType == boolean.class && rawValue instanceof Boolean) { - return (T) ((Boolean)rawValue); + return ((Boolean)rawValue); } if (expectedType == Boolean.class && rawValue instanceof String) { - return (T) (Boolean)Boolean.parseBoolean((((String)rawValue)).trim()); + return (Boolean)Boolean.parseBoolean((((String)rawValue)).trim()); } if (expectedType == Boolean.class && rawValue instanceof PolyString) { - return (T) (Boolean)Boolean.parseBoolean((rawValue.toString().trim())); + return (Boolean)Boolean.parseBoolean((rawValue.toString().trim())); } if (expectedType == boolean.class && rawValue instanceof String) { - return (T) (Boolean)Boolean.parseBoolean(((String)rawValue).trim()); + return (Boolean)Boolean.parseBoolean(((String)rawValue).trim()); } if (expectedType == boolean.class && rawValue instanceof PolyString) { - return (T) (Boolean)Boolean.parseBoolean((rawValue).toString().trim()); + return (Boolean)Boolean.parseBoolean((rawValue).toString().trim()); } if (expectedType == String.class && rawValue instanceof Boolean) { - return (T) rawValue.toString(); + return rawValue.toString(); } // int if (expectedType == int.class && rawValue instanceof Integer) { - return (T)((Integer)rawValue); + return ((Integer)rawValue); } if (expectedType == Integer.class && rawValue instanceof String) { - return (T) (Integer)Integer.parseInt(((String)rawValue).trim()); + return (Integer)Integer.parseInt(((String)rawValue).trim()); } if (expectedType == int.class && rawValue instanceof String) { - return (T) (Integer)Integer.parseInt(((String)rawValue).trim()); + return (Integer)Integer.parseInt(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Integer) { - return (T) rawValue.toString(); + return rawValue.toString(); } if (expectedType == int.class && rawValue instanceof Long) { - return (T)(Integer)((Long)rawValue).intValue(); + return (Integer)((Long)rawValue).intValue(); } if (expectedType == long.class && rawValue instanceof Long) { - return (T)(rawValue); + return (rawValue); } if (expectedType == Long.class && rawValue instanceof String) { - return (T) (Long)Long.parseLong(((String)rawValue).trim()); + return (Long)Long.parseLong(((String)rawValue).trim()); } if (expectedType == long.class && rawValue instanceof String) { - return (T) (Long)Long.parseLong(((String)rawValue).trim()); + return (Long)Long.parseLong(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Long) { - return (T) rawValue.toString(); + return rawValue.toString(); } if (expectedType == float.class && rawValue instanceof Float) { - return (T)((Float)rawValue); + return ((Float)rawValue); } if (expectedType == Float.class && rawValue instanceof String) { - return (T) (Float)Float.parseFloat(((String)rawValue).trim()); + return (Float)Float.parseFloat(((String)rawValue).trim()); } if (expectedType == float.class && rawValue instanceof String) { - return (T) (Float)Float.parseFloat(((String)rawValue).trim()); + return (Float)Float.parseFloat(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Float) { - return (T) rawValue.toString(); + return rawValue.toString(); } if (expectedType == double.class && rawValue instanceof Double) { - return (T)((Double)rawValue); + return ((Double)rawValue); } if (expectedType == Double.class && rawValue instanceof String) { - return (T) (Double)Double.parseDouble(((String)rawValue).trim()); + return (Double)Double.parseDouble(((String)rawValue).trim()); } if (expectedType == double.class && rawValue instanceof String) { - return (T) (Double)Double.parseDouble(((String)rawValue).trim()); + return (Double)Double.parseDouble(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof Float) { - return (T) rawValue.toString(); + return rawValue.toString(); } if (expectedType == byte.class && rawValue instanceof Byte) { - return (T)((Byte)rawValue); + return ((Byte)rawValue); } if (expectedType == Byte.class && rawValue instanceof String) { - return (T) (Byte)Byte.parseByte(((String)rawValue)); + return (Byte)Byte.parseByte(((String)rawValue)); } if (expectedType == byte.class && rawValue instanceof String) { - return (T) (Byte)Byte.parseByte(((String)rawValue)); + return (Byte)Byte.parseByte(((String)rawValue)); } if (expectedType == String.class && rawValue instanceof Byte) { - return (T) rawValue.toString(); + return rawValue.toString(); } if (expectedType == BigInteger.class && rawValue instanceof String) { - return (T) new BigInteger(((String)rawValue).trim()); + return new BigInteger(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof BigInteger) { - return (T) rawValue.toString().trim(); + return rawValue.toString().trim(); } if (expectedType == BigDecimal.class && rawValue instanceof String) { - return (T) new BigDecimal(((String)rawValue).trim()); + return new BigDecimal(((String)rawValue).trim()); } if (expectedType == String.class && rawValue instanceof BigDecimal) { - return (T) ((BigDecimal)rawValue).toString().trim(); + return ((BigDecimal)rawValue).toString().trim(); } if (expectedType == PolyString.class && rawValue instanceof String) { - return (T) new PolyString((String)rawValue); + return new PolyString((String)rawValue); } if (expectedType == PolyStringType.class && rawValue instanceof String) { PolyStringType polyStringType = new PolyStringType(); polyStringType.setOrig((String)rawValue); - return (T) polyStringType; + return polyStringType; } if (expectedType == String.class && rawValue instanceof PolyString) { - return (T)((PolyString)rawValue).getOrig(); + return ((PolyString)rawValue).getOrig(); } if (expectedType == String.class && rawValue instanceof PolyStringType) { - return (T)((PolyStringType)rawValue).getOrig(); + return ((PolyStringType)rawValue).getOrig(); } if (expectedType == PolyString.class && rawValue instanceof PolyStringType) { - return (T) ((PolyStringType)rawValue).toPolyString(); + return ((PolyStringType)rawValue).toPolyString(); } if (expectedType == PolyString.class && rawValue instanceof Integer) { - return (T) new PolyString(((Integer)rawValue).toString()); + return new PolyString(((Integer)rawValue).toString()); } if (expectedType == PolyStringType.class && rawValue instanceof PolyString) { PolyStringType polyStringType = new PolyStringType((PolyString)rawValue); - return (T) polyStringType; + return polyStringType; } if (expectedType == PolyStringType.class && rawValue instanceof Integer) { PolyStringType polyStringType = new PolyStringType(((Integer)rawValue).toString()); - return (T) polyStringType; + return polyStringType; } // Date and time if (expectedType == XMLGregorianCalendar.class && rawValue instanceof Long) { XMLGregorianCalendar xmlCalType = XmlTypeConverter.createXMLGregorianCalendar((Long)rawValue); - return (T) xmlCalType; + return xmlCalType; } if (expectedType == XMLGregorianCalendar.class && rawValue instanceof String) { XMLGregorianCalendar xmlCalType = magicDateTimeParse((String)rawValue); - return (T) xmlCalType; + return xmlCalType; } if (expectedType == String.class && rawValue instanceof XMLGregorianCalendar) { - return (T) ((XMLGregorianCalendar)rawValue).toXMLFormat(); + return ((XMLGregorianCalendar)rawValue).toXMLFormat(); } if (expectedType == Long.class && rawValue instanceof XMLGregorianCalendar) { - return (T) (Long) XmlTypeConverter.toMillis((XMLGregorianCalendar)rawValue); + return (Long) XmlTypeConverter.toMillis((XMLGregorianCalendar)rawValue); } // XML Enums (JAXB) @@ -227,26 +232,30 @@ public static T convert(Class expectedType, Object rawValue) { return XmlTypeConverter.toXmlEnum(expectedType, ((String)rawValue).trim()); } if (expectedType == String.class && rawValue.getClass().isEnum() && rawValue.getClass().getAnnotation(XmlEnum.class) != null) { - return (T) XmlTypeConverter.fromXmlEnum(rawValue); + return XmlTypeConverter.fromXmlEnum(rawValue); } // Java Enums if (expectedType.isEnum() && rawValue instanceof String) { - return (T) Enum.valueOf((Class)expectedType, ((String)rawValue).trim()); + return Enum.valueOf((Class)expectedType, ((String)rawValue).trim()); } if (expectedType == String.class && rawValue.getClass().isEnum()) { - return (T) rawValue.toString(); + return rawValue.toString(); } //QName if (expectedType == QName.class && rawValue instanceof QName){ - return (T) rawValue; + return rawValue; } if (expectedType == QName.class && rawValue instanceof String){ - return (T) QNameUtil.uriToQName(((String)rawValue).trim()); + return QNameUtil.uriToQName(((String)rawValue).trim()); + } + + if (failIfImpossible) { + throw new IllegalArgumentException("Expected " + expectedType + " type, but got " + rawValue.getClass()); + } else { + return rawValue; } - - throw new IllegalArgumentException("Expected "+expectedType+" type, but got "+rawValue.getClass()); } private static XMLGregorianCalendar magicDateTimeParse(String stringDate) { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismUtil.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismUtil.java index 61911cceced..9e15af6cc4e 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismUtil.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismUtil.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; /** * @author semancik @@ -207,6 +208,11 @@ public static String serializeQuietly(PrismContext prismContext, Object object) if (object == null) { return null; } + if (object instanceof Collection) { + return ((Collection) object).stream() + .map(o -> serializeQuietly(prismContext, o)) + .collect(Collectors.joining("; ")); + } try { PrismSerializer serializer = prismContext.xmlSerializer(); if (object instanceof Item) { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java index e43b7f4c836..5f6fa699f69 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * 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. @@ -23,12 +23,13 @@ */ public class ExpressionConstants { + // Generic variables public static final QName VAR_INPUT = new QName(SchemaConstants.NS_C, "input"); - public static final QName VAR_FOCUS = new QName(SchemaConstants.NS_C, "focus"); - public static final QName VAR_USER = new QName(SchemaConstants.NS_C, "user"); - public static final QName VAR_ACCOUNT = new QName(SchemaConstants.NS_C, "account"); + public static final QName VAR_OBJECT = new QName(SchemaConstants.NS_C, "object"); + + // Variables used in various mappings + public static final QName VAR_FOCUS = new QName(SchemaConstants.NS_C, "focus"); public static final QName VAR_PROJECTION = new QName(SchemaConstants.NS_C, "projection"); - public static final QName VAR_SHADOW = new QName(SchemaConstants.NS_C, "shadow"); public static final QName VAR_SOURCE = new QName(SchemaConstants.NS_C, "source"); public static final QName VAR_ASSIGNMENT = new QName(SchemaConstants.NS_C, "assignment"); public static final QName VAR_IMMEDIATE_ASSIGNMENT = new QName(SchemaConstants.NS_C, "immediateAssignment"); @@ -45,6 +46,12 @@ public class ExpressionConstants { public static final QName VAR_ACTOR = new QName(SchemaConstants.NS_C, "actor"); public static final QName VAR_VALUE = new QName(SchemaConstants.NS_C, "value"); + // DEPRECATED variables, just for compatibility + public static final QName VAR_USER = new QName(SchemaConstants.NS_C, "user"); + public static final QName VAR_ACCOUNT = new QName(SchemaConstants.NS_C, "account"); + public static final QName VAR_SHADOW = new QName(SchemaConstants.NS_C, "shadow"); + + // existence mapping variables public static final QName VAR_LEGAL = new QName(SchemaConstants.NS_C, "legal"); public static final QName VAR_ASSIGNED = new QName(SchemaConstants.NS_C, "assigned"); public static final QName VAR_FOCUS_EXISTS = new QName(SchemaConstants.NS_C, "focusExists"); 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 3134d95546d..b4bc9151f55 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 @@ -3299,12 +3299,26 @@ + + + + Fired policy rule triggers for this assignment. (Some fields of the triggers might be zeroed out + in order to save space. This is yet to be decided.) + This is EXPERIMENTAL functionality. It is likely to change in the near future. + + + 3.6 + true + + + + Recorded exception from a policy rule. The exceptions that are approved are recoded here to avoid re-evaluating and re-approving them all the time. - This is EXPERIMENAL functionality. It is likely to change in the near future. + This is EXPERIMENTAL functionality. It is likely to change in the near future. 3.5 @@ -9384,7 +9398,16 @@ - + + + + DEPRECATED. Use the lifetime settings in security policy. + + + true + + + @@ -9485,9 +9508,50 @@ - + + + +

+ Minimum number of unique characters in the generated string. + Defaults to minLength. +

+
+
+
+ + + +

+ Expression that is used to check the resulting value whether it is acceptable + or not. If the expression returns true, then the value is accepted. + If the expression returns false value, then the value is rejected and it + will be generated again. Until the maximum number of attempts is reached. +

+
+
+
+ + + +

+ Message thet will be displayed to user when the expression check fails. +

+
+
+
+ + + +

+ Maximum number of attempts to generate an expression. Attempts are used + with limitations that cannot be incorporated directly into the generation + algorithm (check expression, pattern, dictionary). +

+
+
+
diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 90e218e671c..c142e12f4c7 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -476,6 +476,7 @@ + @@ -530,6 +531,7 @@ + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd index a0981da4350..24403dbd48c 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd @@ -1409,16 +1409,16 @@
- - - + + + - + - Reason for automated decision. + Type of event cause. diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/parser/TestParseUser.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/parser/TestParseUser.java index bdb41ebe531..feae85c6baf 100644 --- a/infra/schema/src/test/java/com/evolveum/midpoint/schema/parser/TestParseUser.java +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/parser/TestParseUser.java @@ -25,23 +25,12 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.schema.SchemaConstantsGenerated; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.testng.annotations.Test; import org.w3c.dom.Element; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.PrismConstants; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismProperty; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.PrismReference; -import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.util.PrismAsserts; @@ -80,22 +69,24 @@ public void testParseFileAsPO() throws Exception { public void testParseRoundTripAsPCV() throws Exception{ displayTestTitle("testParseRoundTripAsPCV"); - processParsingsPCV(v -> getPrismContext().serializerFor(language).serialize(v), "s0"); - processParsingsPCV(v -> getPrismContext().serializerFor(language).root(new QName("dummy")).serialize(v), "s1"); - processParsingsPCV(v -> getPrismContext().serializerFor(language).root(SchemaConstantsGenerated.C_SYSTEM_CONFIGURATION).serialize(v), "s2"); // misleading item name - processParsingsPCV(v -> getPrismContext().serializerFor(language).serializeRealValue(v.asContainerable()), "s3"); - processParsingsPCV(v -> getPrismContext().serializerFor(language).root(new QName("dummy")).serializeAnyData(v.asContainerable()), "s4"); + SerializationOptions o = SerializationOptions.createSerializeReferenceNames(); + processParsingsPCV(v -> getPrismContext().serializerFor(language).options(o).serialize(v), "s0"); + processParsingsPCV(v -> getPrismContext().serializerFor(language).options(o).root(new QName("dummy")).serialize(v), "s1"); + processParsingsPCV(v -> getPrismContext().serializerFor(language).options(o).root(SchemaConstantsGenerated.C_SYSTEM_CONFIGURATION).serialize(v), "s2"); // misleading item name + processParsingsPCV(v -> getPrismContext().serializerFor(language).options(o).serializeRealValue(v.asContainerable()), "s3"); + processParsingsPCV(v -> getPrismContext().serializerFor(language).options(o).root(new QName("dummy")).serializeAnyData(v.asContainerable()), "s4"); } @Test public void testParseRoundTripAsPO() throws Exception{ displayTestTitle("testParseRoundTripAsPO"); - processParsingsPO(v -> getPrismContext().serializerFor(language).serialize(v), "s0", true); - processParsingsPO(v -> getPrismContext().serializerFor(language).root(new QName("dummy")).serialize(v), "s1", false); - processParsingsPO(v -> getPrismContext().serializerFor(language).root(SchemaConstantsGenerated.C_SYSTEM_CONFIGURATION).serialize(v), "s2", false); // misleading item name - processParsingsPO(v -> getPrismContext().serializerFor(language).serializeRealValue(v.asObjectable()), "s3", false); - processParsingsPO(v -> getPrismContext().serializerFor(language).root(new QName("dummy")).serializeAnyData(v.asObjectable()), "s4", false); + SerializationOptions o = SerializationOptions.createSerializeReferenceNames(); + processParsingsPO(v -> getPrismContext().serializerFor(language).options(o).serialize(v), "s0", true); + processParsingsPO(v -> getPrismContext().serializerFor(language).options(o).root(new QName("dummy")).serialize(v), "s1", false); + processParsingsPO(v -> getPrismContext().serializerFor(language).options(o).root(SchemaConstantsGenerated.C_SYSTEM_CONFIGURATION).serialize(v), "s2", false); // misleading item name + processParsingsPO(v -> getPrismContext().serializerFor(language).options(o).serializeRealValue(v.asObjectable()), "s3", false); + processParsingsPO(v -> getPrismContext().serializerFor(language).options(o).root(new QName("dummy")).serializeAnyData(v.asObjectable()), "s4", false); } private void processParsingsPCV(SerializingFunction> serializer, String serId) throws Exception { @@ -181,9 +172,17 @@ void assertUserPrism(PrismObject user, boolean isObject) { PrismAsserts.assertDefinition(firstAssignmentExtensionItem.getDefinition(), EXTENSION_INT_TYPE_ELEMENT, DOMUtil.XSD_INT, 0, -1); PrismPropertyValue firstValueOfFirstAssignmentExtensionItem = firstAssignmentExtensionItem.getValues().get(0); assertEquals("Wrong value of "+EXTENSION_INT_TYPE_ELEMENT+" in assignment extension", 42, firstValueOfFirstAssignmentExtensionItem.getValue()); - - // TODO: check accountConstruction - + + PrismContainer constructionContainer = firstAssignmentValue.findContainer(AssignmentType.F_CONSTRUCTION); + PrismAsserts.assertDefinition(constructionContainer.getDefinition(), AssignmentType.F_CONSTRUCTION, ConstructionType.COMPLEX_TYPE, 0, 1); + List> constructionItems = constructionContainer.getValue().getItems(); + assertNotNull("No construction items", constructionItems); + assertEquals("Wrong number of construction items", 1, constructionItems.size()); + PrismReference firstConstructionItem = (PrismReference) constructionItems.get(0); + PrismAsserts.assertDefinition(firstConstructionItem.getDefinition(), ConstructionType.F_RESOURCE_REF, ObjectReferenceType.COMPLEX_TYPE, 0, 1); + PrismReferenceValue firstValueOfFirstConstructionItem = firstConstructionItem.getValues().get(0); + assertEquals("Wrong resource name", "resource1", PolyString.getOrig(firstValueOfFirstConstructionItem.getTargetName())); + PrismReference accountRef = user.findReference(UserType.F_LINK_REF); assertEquals("Wrong number of accountRef values", 3, accountRef.getValues().size()); PrismAsserts.assertReferenceValue(accountRef, USER_ACCOUNT_REF_1_OID); diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/util/SchemaTestUtil.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/util/SchemaTestUtil.java index d5fc949014d..e3e3d02b64b 100644 --- a/infra/schema/src/test/java/com/evolveum/midpoint/schema/util/SchemaTestUtil.java +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/util/SchemaTestUtil.java @@ -86,7 +86,7 @@ public static void assertFocusDefinition(ComplexTypeDefinition complexTypeDefini PrismContainerDefinition assignmentContainer = complexTypeDefinition.findContainerDefinition(UserType.F_ASSIGNMENT); PrismAsserts.assertDefinition(assignmentContainer, UserType.F_ASSIGNMENT, AssignmentType.COMPLEX_TYPE, 0, -1); assertFalse("Assignment is runtime", assignmentContainer.isRuntimeSchema()); - assertEquals("Assignment size", 18, assignmentContainer.getDefinitions().size()); + assertEquals("Assignment size", 19, assignmentContainer.getDefinitions().size()); PrismContainerDefinition constructionContainer = assignmentContainer.findContainerDefinition(AssignmentType.F_CONSTRUCTION); PrismAsserts.assertDefinition(constructionContainer, AssignmentType.F_CONSTRUCTION, ConstructionType.COMPLEX_TYPE, 0, 1); diff --git a/infra/schema/src/test/resources/common/json/no-ns/user-jack.json b/infra/schema/src/test/resources/common/json/no-ns/user-jack.json index 66845ade881..344dce3f55e 100644 --- a/infra/schema/src/test/resources/common/json/no-ns/user-jack.json +++ b/infra/schema/src/test/resources/common/json/no-ns/user-jack.json @@ -62,7 +62,8 @@ }, "construction" : { "resourceRef" : { - "oid" : "2f9b9299-5555-5555-5555-000000001111" + "oid" : "2f9b9299-5555-5555-5555-000000001111", + "targetName" : "resource1" } } } ], diff --git a/infra/schema/src/test/resources/common/json/ns/user-jack.json b/infra/schema/src/test/resources/common/json/ns/user-jack.json index a84709989b4..62072d4225a 100644 --- a/infra/schema/src/test/resources/common/json/ns/user-jack.json +++ b/infra/schema/src/test/resources/common/json/ns/user-jack.json @@ -67,7 +67,8 @@ "construction" : { "resourceRef" : { "oid" : "2f9b9299-5555-5555-5555-000000001111", - "type" : "http://midpoint.evolveum.com/xml/ns/public/common/common-3#ResourceType" + "type" : "http://midpoint.evolveum.com/xml/ns/public/common/common-3#ResourceType", + "targetName" : "resource1" } } } ], diff --git a/infra/schema/src/test/resources/common/xml/no-ns/user-jack.xml b/infra/schema/src/test/resources/common/xml/no-ns/user-jack.xml index bdd44b69a4e..c19aac65dc5 100644 --- a/infra/schema/src/test/resources/common/xml/no-ns/user-jack.xml +++ b/infra/schema/src/test/resources/common/xml/no-ns/user-jack.xml @@ -66,7 +66,9 @@ 42 - + + resource1 + diff --git a/infra/schema/src/test/resources/common/xml/ns/user-jack.xml b/infra/schema/src/test/resources/common/xml/ns/user-jack.xml index 4cc59396759..f267300f489 100644 --- a/infra/schema/src/test/resources/common/xml/ns/user-jack.xml +++ b/infra/schema/src/test/resources/common/xml/ns/user-jack.xml @@ -76,12 +76,24 @@ - - 42 - - - - + + 42 + + + + resource1 + + + + + + + source123 + + + + + diff --git a/infra/schema/src/test/resources/common/yaml/no-ns/user-jack.yaml b/infra/schema/src/test/resources/common/yaml/no-ns/user-jack.yaml index eadd7aac909..d297601f652 100644 --- a/infra/schema/src/test/resources/common/yaml/no-ns/user-jack.yaml +++ b/infra/schema/src/test/resources/common/yaml/no-ns/user-jack.yaml @@ -44,6 +44,7 @@ object: !UserType construction: resourceRef: oid: 2f9b9299-5555-5555-5555-000000001111 + targetName: resource1 activation: administrativeStatus: enabled fullName: Jack Sparrow diff --git a/infra/schema/src/test/resources/common/yaml/ns/user-jack.yaml b/infra/schema/src/test/resources/common/yaml/ns/user-jack.yaml index 7a6842884bd..34d22e072a0 100644 --- a/infra/schema/src/test/resources/common/yaml/ns/user-jack.yaml +++ b/infra/schema/src/test/resources/common/yaml/ns/user-jack.yaml @@ -46,6 +46,14 @@ object: !http://midpoint.evolveum.com/xml/ns/public/common/common-3/UserType construction: resourceRef: oid: 2f9b9299-5555-5555-5555-000000001111 + targetName: resource1 + trigger: + assignmentPath: + segment: + sourceRef: + oid: 123 + targetName: source123 + constraint: {} activation: administrativeStatus: enabled fullName: Jack Sparrow diff --git a/model/certification-impl/src/test/java/com/evolveum/midpoint/certification/test/SoDCertificationTest.java b/model/certification-impl/src/test/java/com/evolveum/midpoint/certification/test/SoDCertificationTest.java index 0931513d2ad..edfdcc4a34f 100644 --- a/model/certification-impl/src/test/java/com/evolveum/midpoint/certification/test/SoDCertificationTest.java +++ b/model/certification-impl/src/test/java/com/evolveum/midpoint/certification/test/SoDCertificationTest.java @@ -16,12 +16,14 @@ package com.evolveum.midpoint.certification.test; +import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; 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.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.test.annotation.DirtiesContext; @@ -59,6 +61,8 @@ public class SoDCertificationTest extends AbstractCertificationTest { private static String roleATest2aOid; private static final File ROLE_A_TEST_2B = new File(TEST_DIR, "a-test-2b.xml"); private static String roleATest2bOid; + private static final File ROLE_A_TEST_2C = new File(TEST_DIR, "a-test-2c.xml"); + private static String roleATest2cOid; private static final File ROLE_A_TEST_3A = new File(TEST_DIR, "a-test-3a.xml"); private static String roleATest3aOid; private static final File ROLE_A_TEST_3B = new File(TEST_DIR, "a-test-3b.xml"); @@ -73,6 +77,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti super.initSystem(initTask, initResult); roleATest2aOid = addAndRecompute(ROLE_A_TEST_2A, initTask, initResult); roleATest2bOid = addAndRecompute(ROLE_A_TEST_2B, initTask, initResult); + roleATest2cOid = addAndRecompute(ROLE_A_TEST_2C, 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); @@ -82,9 +87,12 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti assignRole(USER_JACK_OID, roleATest2aOid); assignRole(USER_JACK_OID, roleATest2bOid); + assignRole(USER_JACK_OID, roleATest2cOid); assignRole(USER_JACK_OID, roleATest3aOid); assignRole(USER_JACK_OID, roleATest3bOid); display("jack", getUser(USER_JACK_OID)); + + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); } @Test @@ -193,12 +201,13 @@ public void test020OpenFirstStage() throws Exception { AccessCertificationCampaignType campaign = getCampaignWithCases(campaignOid); display("campaign in stage 1", campaign); - assertAfterCampaignStart(campaign, certificationDefinition, 4); + assertAfterCampaignStart(campaign, certificationDefinition, 5); checkAllCases(campaign.getCase(), campaignOid); List caseList = campaign.getCase(); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2aOid, ACCEPT, ACCEPT, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2bOid, ACCEPT, ACCEPT, null); + assertCaseOutcome(caseList, USER_JACK_OID, roleATest2cOid, ACCEPT, ACCEPT, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3aOid, ACCEPT, ACCEPT, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3bOid, ACCEPT, ACCEPT, null); assertPercentComplete(campaign, 0, 100, 0); // preliminary outcomes for all cases are "ACCEPT" @@ -207,10 +216,11 @@ public void test020OpenFirstStage() throws Exception { protected void checkAllCases(Collection caseList, String campaignOid) throws ConfigurationException, ObjectNotFoundException, SchemaException, CommunicationException, SecurityViolationException { - assertEquals("Wrong number of certification cases", 4, caseList.size()); + assertEquals("Wrong number of certification cases", 5, caseList.size()); UserType jack = getUser(USER_JACK_OID).asObjectable(); checkCase(caseList, USER_JACK_OID, roleATest2aOid, jack, campaignOid); checkCase(caseList, USER_JACK_OID, roleATest2bOid, jack, campaignOid); + checkCase(caseList, USER_JACK_OID, roleATest2cOid, jack, campaignOid); checkCase(caseList, USER_JACK_OID, roleATest3aOid, jack, campaignOid); checkCase(caseList, USER_JACK_OID, roleATest3bOid, jack, campaignOid); } @@ -256,11 +266,13 @@ public void test100RecordDecisions() throws Exception { AccessCertificationCaseType test2aCase = findCase(caseList, USER_JACK_OID, roleATest2aOid); AccessCertificationCaseType test2bCase = findCase(caseList, USER_JACK_OID, roleATest2bOid); + AccessCertificationCaseType test2cCase = findCase(caseList, USER_JACK_OID, roleATest2cOid); AccessCertificationCaseType test3aCase = findCase(caseList, USER_JACK_OID, roleATest3aOid); AccessCertificationCaseType test3bCase = findCase(caseList, USER_JACK_OID, roleATest3bOid); recordDecision(campaignOid, test2aCase, REVOKE, "no way", 1, USER_JACK_OID, task, result); recordDecision(campaignOid, test2bCase, ACCEPT, null, 1, USER_JACK_OID, task, result); + recordDecision(campaignOid, test2cCase, ACCEPT, null, 1, USER_JACK_OID, task, result); recordDecision(campaignOid, test3aCase, ACCEPT, "OK", 1, USER_JACK_OID, task, result); recordDecision(campaignOid, test3bCase, NOT_DECIDED, "dunno", 1, USER_JACK_OID, task, result); @@ -275,16 +287,19 @@ public void test100RecordDecisions() throws Exception { test2aCase = findCase(caseList, USER_JACK_OID, roleATest2aOid); test2bCase = findCase(caseList, USER_JACK_OID, roleATest2bOid); + test2cCase = findCase(caseList, USER_JACK_OID, roleATest2cOid); test3aCase = findCase(caseList, USER_JACK_OID, roleATest3aOid); test3bCase = findCase(caseList, USER_JACK_OID, roleATest3bOid); assertSingleDecision(test2aCase, REVOKE, "no way", 1, USER_JACK_OID, REVOKE, false); assertSingleDecision(test2bCase, ACCEPT, null, 1, USER_JACK_OID, ACCEPT, false); + assertSingleDecision(test2cCase, ACCEPT, null, 1, USER_JACK_OID, ACCEPT, false); assertSingleDecision(test3aCase, ACCEPT, "OK", 1, USER_JACK_OID, ACCEPT, false); assertSingleDecision(test3bCase, NOT_DECIDED, "dunno", 1, USER_JACK_OID, ACCEPT, false); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2aOid, REVOKE, REVOKE, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2bOid, ACCEPT, ACCEPT, null); + assertCaseOutcome(caseList, USER_JACK_OID, roleATest2cOid, ACCEPT, ACCEPT, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3aOid, ACCEPT, ACCEPT, null); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3bOid, ACCEPT, ACCEPT, null); @@ -318,16 +333,19 @@ public void test150CloseFirstStage() throws Exception { List caseList = queryHelper.searchCases(campaignOid, null, null, result); AccessCertificationCaseType test2aCase = findCase(caseList, USER_JACK_OID, roleATest2aOid); AccessCertificationCaseType test2bCase = findCase(caseList, USER_JACK_OID, roleATest2bOid); + AccessCertificationCaseType test2cCase = findCase(caseList, USER_JACK_OID, roleATest2cOid); AccessCertificationCaseType test3aCase = findCase(caseList, USER_JACK_OID, roleATest3aOid); AccessCertificationCaseType test3bCase = findCase(caseList, USER_JACK_OID, roleATest3bOid); assertSingleDecision(test2aCase, REVOKE, "no way", 1, USER_JACK_OID, REVOKE, true); assertSingleDecision(test2bCase, ACCEPT, null, 1, USER_JACK_OID, ACCEPT, true); + assertSingleDecision(test2cCase, ACCEPT, null, 1, USER_JACK_OID, ACCEPT, true); assertSingleDecision(test3aCase, ACCEPT, "OK", 1, USER_JACK_OID, ACCEPT, true); assertSingleDecision(test3bCase, NOT_DECIDED, "dunno", 1, USER_JACK_OID, ACCEPT, true); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2aOid, REVOKE, REVOKE, 1); assertCaseOutcome(caseList, USER_JACK_OID, roleATest2bOid, ACCEPT, ACCEPT, 1); + assertCaseOutcome(caseList, USER_JACK_OID, roleATest2cOid, ACCEPT, ACCEPT, 1); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3aOid, ACCEPT, ACCEPT, 1); assertCaseOutcome(caseList, USER_JACK_OID, roleATest3bOid, ACCEPT, ACCEPT, 1); @@ -373,7 +391,7 @@ public void test200StartRemediation() throws Exception { assertEquals("wrong # of stages", 1, campaign.getStage().size()); List caseList = queryHelper.searchCases(campaignOid, null, null, result); - assertEquals("wrong # of cases", 4, caseList.size()); + assertEquals("wrong # of cases", 5, caseList.size()); AccessCertificationCaseType test2aCase = findCase(caseList, USER_JACK_OID, roleATest2aOid); assertApproximateTime("test2aCase.remediedTimestamp", new Date(), test2aCase.getRemediedTimestamp()); diff --git a/model/certification-impl/src/test/resources/sod/a-test-2a.xml b/model/certification-impl/src/test/resources/sod/a-test-2a.xml index f403b347811..880bd173b17 100644 --- a/model/certification-impl/src/test/resources/sod/a-test-2a.xml +++ b/model/certification-impl/src/test/resources/sod/a-test-2a.xml @@ -19,13 +19,16 @@ xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3" xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3"> a-test-2a - Exclusive with test-2b + Exclusive with test-2b/2c + + + diff --git a/model/certification-impl/src/test/resources/sod/a-test-2b.xml b/model/certification-impl/src/test/resources/sod/a-test-2b.xml index 22359ff6881..48324f3f86d 100644 --- a/model/certification-impl/src/test/resources/sod/a-test-2b.xml +++ b/model/certification-impl/src/test/resources/sod/a-test-2b.xml @@ -19,13 +19,16 @@ xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3" xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3"> a-test-2b - Exclusive with test-2a + Exclusive with test-2a/2c + + + diff --git a/model/certification-impl/src/test/resources/sod/a-test-2c.xml b/model/certification-impl/src/test/resources/sod/a-test-2c.xml new file mode 100644 index 00000000000..48671e42b2f --- /dev/null +++ b/model/certification-impl/src/test/resources/sod/a-test-2c.xml @@ -0,0 +1,37 @@ + + + + a-test-2c + Exclusive with test-2a/2b + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java index 40622ec5611..8a6a37d4a1c 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java @@ -224,6 +224,18 @@ PrismObject mergeObjectsPreviewObject(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException ; - + /** + * TEMPORARY. Need to find out better way how to deal with generated values + * + * @param policy + * @param defaultLength + * @param generateMinimalSize + * @param object object for which we generate the value (e.g. user or shadow) + * @param inputResult + * @return + * @throws ExpressionEvaluationException + */ + String generateValue(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, + PrismObject object, String shortDesc, Task task, OperationResult inputResult) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException; } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java index a7b7fd2feab..b4d7930ae91 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java @@ -87,6 +87,4 @@ public interface EvaluatedAssignment extends DebugDumpable Collection getPolicySituations(); void triggerConstraint(@Nullable EvaluatedPolicyRule rule, EvaluatedPolicyRuleTrigger trigger) throws PolicyViolationException; - - Set getPolicySituationsSynced(); } \ No newline at end of file diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java index bfd405d341a..5fb3d64abb2 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java @@ -118,6 +118,7 @@ public EvaluatedPolicyRuleTriggerType toEvaluatedPolicyRuleTriggerType(Evaluated } protected void fillCommonContent(EvaluatedPolicyRuleTriggerType tt, EvaluatedPolicyRule owningRule) { + tt.setRuleName(owningRule.getName()); tt.setConstraintKind(constraintKind); tt.setConstraint(constraint); tt.setMessage(message); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java index cf1bf0c039b..8452d278b7a 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -803,6 +803,17 @@ public static PrismPropertyValue evaluateCondition(ExpressionVariables return (PrismPropertyValue) evaluateExpression(variables, outputDefinition, expressionType, expressionFactory, shortDesc, task, parentResult); } + + public static boolean getBooleanConditionOutput(PrismPropertyValue conditionOutput) { + if (conditionOutput == null) { + return false; + } + Boolean value = conditionOutput.getValue(); + if (value == null) { + return false; + } + return value; + } public static Map compileVariablesAndSources(ExpressionEvaluationContext params) { Map variablesAndSources = new HashMap(); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionVariables.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionVariables.java index ef75cf0eab9..dd1714a7449 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionVariables.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionVariables.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * 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. @@ -15,14 +15,18 @@ */ package com.evolveum.midpoint.model.common.expression; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.QNameUtil; +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.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + import org.apache.commons.lang.StringUtils; import org.w3c.dom.Element; @@ -145,6 +149,33 @@ public Object get(QName name) { return variables.get(name); } + @SuppressWarnings("unchecked") + public T get(QName name, Class type) throws SchemaException { + Object object = get(name); + if (object == null) { + return null; + } + if (type.isAssignableFrom(object.getClass())) { + return (T) object; + } + throw new SchemaException("Expected type "+type.getSimpleName()+" in variable "+name+", but found type "+object.getClass()); + } + + public PrismObject getObjectNew(QName name) throws SchemaException { + Object object = get(name); + if (object == null) { + return null; + } + if (object instanceof PrismObject) { + return (PrismObject) object; + } + if (object instanceof ObjectDeltaObject) { + ObjectDeltaObject odo = (ObjectDeltaObject)object; + return odo.getNewObject(); + } + throw new SchemaException("Expected object in variable "+name+", but found type "+object.getClass()); + } + public Set> entrySet() { return variables.entrySet(); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java index c070649d6f9..7281e0a6933 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * 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. @@ -19,20 +19,23 @@ import org.apache.commons.lang.StringUtils; -import com.evolveum.midpoint.common.policy.ValuePolicyGenerator; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluator; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; +import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.common.expression.StringPolicyResolver; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.util.ObjectResolver; import com.evolveum.midpoint.util.RandomString; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -41,6 +44,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorModeType; import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorType; 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.StringPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; @@ -58,14 +62,16 @@ public class GenerateExpressionEvaluator evaluate(ExpressionEvaluationContext params) GenerateExpressionEvaluatorModeType mode = generateEvaluatorType.getMode(); if (mode == null || mode == GenerateExpressionEvaluatorModeType.POLICY) { + PrismObject object = getObject(params); + // TODO: generate value based on stringPolicyType (if not null) if (stringPolicyType != null) { if (isNotEmptyMinLength(stringPolicyType)) { - stringValue = ValuePolicyGenerator.generate(stringPolicyType, DEFAULT_LENGTH, true, - params.getResult()); + stringValue = valuePolicyGenerator.generate(stringPolicyType, DEFAULT_LENGTH, true, object, + params.getContextDescription(), params.getTask(), params.getResult()); } else { - stringValue = ValuePolicyGenerator.generate(stringPolicyType, DEFAULT_LENGTH, false, - params.getResult()); + stringValue = valuePolicyGenerator.generate(stringPolicyType, DEFAULT_LENGTH, false, object, + params.getContextDescription(), params.getTask(), params.getResult()); } params.getResult().computeStatus(); if (params.getResult().isError()) { @@ -171,6 +179,21 @@ public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext params) return ItemDelta.toDeltaSetTriple(output, null); } + // determine object from the variables + @SuppressWarnings("unchecked") + private PrismObject getObject(ExpressionEvaluationContext params) throws SchemaException { + ExpressionVariables variables = params.getVariables(); + if (variables == null) { + return null; + } + PrismObject object = variables.getObjectNew(ExpressionConstants.VAR_PROJECTION); + if (object != null) { + return object; + } + object = variables.getObjectNew(ExpressionConstants.VAR_FOCUS); + return object; + } + /* * (non-Javadoc) * diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluatorFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluatorFactory.java index ecf7f5aa115..e62fe4d0fe8 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluatorFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * 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. @@ -25,6 +25,7 @@ import com.evolveum.midpoint.model.common.expression.ExpressionEvaluator; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluatorFactory; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismValue; @@ -45,12 +46,14 @@ public class GenerateExpressionEvaluatorFactory implements ExpressionEvaluatorFa private Protector protector; private PrismContext prismContext; private ObjectResolver objectResolver; + private ValuePolicyGenerator valuePolicyGenerator; - public GenerateExpressionEvaluatorFactory(Protector protector, ObjectResolver objectResolver, PrismContext prismContext) { + public GenerateExpressionEvaluatorFactory(Protector protector, ObjectResolver objectResolver, ValuePolicyGenerator valuePolicyGenerator, PrismContext prismContext) { super(); this.protector = protector; this.prismContext = prismContext; this.objectResolver = objectResolver; + this.valuePolicyGenerator = valuePolicyGenerator; } @Override @@ -83,7 +86,7 @@ public ExpressionEvaluator GenerateExpressionEvaluatorType generateEvaluatorType = (GenerateExpressionEvaluatorType)evaluatorTypeObject; - return new GenerateExpressionEvaluator(generateEvaluatorType, outputDefinition, protector, objectResolver, prismContext); + return new GenerateExpressionEvaluator(generateEvaluatorType, outputDefinition, protector, objectResolver, valuePolicyGenerator, prismContext); } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java index b2740031ed1..26ed8b50293 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java @@ -111,6 +111,30 @@ public static String uc(String orig) { return StringUtils.upperCase(orig); } + public boolean contains(Object object, Object search) { + String objectStr = stringify(object); + if (StringUtils.isEmpty(objectStr)) { + return false; + } + String searchStr = stringify(search); + if (StringUtils.isEmpty(searchStr)) { + return false; + } + return objectStr.contains(searchStr); + } + + public boolean containsIgnoreCase(Object object, Object search) { + String objectStr = stringify(object); + if (StringUtils.isEmpty(objectStr)) { + return false; + } + String searchStr = stringify(search); + if (StringUtils.isEmpty(searchStr)) { + return false; + } + return StringUtils.containsIgnoreCase(objectStr, searchStr); + } + /** * Remove whitespaces at the beginning and at the end of the string. */ diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java similarity index 95% rename from infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java rename to model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java index df907f296de..d6d07ae3eab 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.evolveum.midpoint.common.policy; +package com.evolveum.midpoint.model.common.stringpolicy; import java.util.ArrayList; import java.util.HashSet; diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java similarity index 63% rename from infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java rename to model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java index 236b85c6fed..39e1bfca03c 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * 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. @@ -14,12 +14,15 @@ * limitations under the License. */ -package com.evolveum.midpoint.common.policy; +package com.evolveum.midpoint.model.common.stringpolicy; /** + * Generator for values that match value policies (mostly passwords). + * Mishmash or old and new code. Only partially refactored. + * Still need to align with ValuePolicyGenerator and the utils. * * @author mamut - * + * @author semancik */ import java.util.ArrayList; @@ -31,34 +34,94 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.text.StrBuilder; - +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.common.expression.Expression; +import com.evolveum.midpoint.model.common.expression.ExpressionFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionUtil; +import com.evolveum.midpoint.model.common.expression.ExpressionVariables; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LimitationsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringLimitType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType; +@Component public class ValuePolicyGenerator { + private static final String OP_GENERATE = ValuePolicyGenerator.class.getName() + ".generate"; private static final transient Trace LOGGER = TraceManager.getTrace(ValuePolicyGenerator.class); - private static final Random rand = new Random(System.currentTimeMillis()); + private static final Random RAND = new Random(System.currentTimeMillis()); + + private static final int DEFAULT_MAX_ATTEMPTS = 10; + + @Autowired + private ExpressionFactory expressionFactory; + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } - public static String generate(StringPolicyType policy, int defaultLength, OperationResult inputResult) { - return generate(policy, defaultLength, false, inputResult); + public void setExpressionFactory(ExpressionFactory expressionFactory) { + this.expressionFactory = expressionFactory; } - public static String generate(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, - OperationResult inputResult) { + public String generate(StringPolicyType policy, int defaultLength, PrismObject object, String shortDesc, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { + return generate(policy, defaultLength, false, object, shortDesc, task, result); + } - if (null == inputResult) { - throw new IllegalArgumentException("Provided operation result cannot be null"); + public String generate(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, + PrismObject object, String shortDesc, Task task, OperationResult parentResult) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { + + OperationResult result = parentResult.createSubresult(OP_GENERATE); + + int maxAttempts = DEFAULT_MAX_ATTEMPTS; + if (policy.getLimitations() != null && policy.getLimitations().getMaxAttempts() != null) { + maxAttempts = policy.getLimitations().getMaxAttempts(); + } + if (maxAttempts < 1) { + ExpressionEvaluationException e = new ExpressionEvaluationException("Illegal number of maximum value genaration attemps: "+maxAttempts); + result.recordFatalError(e); + throw e; + } + String generatedValue = null; + int attempt = 1; + for (;;) { + generatedValue = generateAttempt(policy, defaultLength, generateMinimalSize, result); + if (result.isError()) { + throw new ExpressionEvaluationException(result.getMessage()); + } + if (checkAttempt(generatedValue, policy, object, shortDesc, task, result)) { + break; + } + LOGGER.trace("Generator attempt {}: check failed", attempt); + if (attempt == maxAttempts) { + ExpressionEvaluationException e = new ExpressionEvaluationException("Unable to genarate value, maximum number of attemps exceeded"); + result.recordFatalError(e); + throw e; + } + attempt++; } - // Define result from generator - OperationResult generatorResult = new OperationResult( - "Password generator running policy :" + (policy != null ? policy.getDescription() - : "No policy defined, using default config")); - inputResult.addSubresult(generatorResult); + + return generatedValue; + + } + + private String generateAttempt(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, + OperationResult result) { // if (policy.getLimitations() != null && // policy.getLimitations().getMinLength() != null){ @@ -134,12 +197,12 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // If any limitation was found to be first if (!mustBeFirst.isEmpty()) { Map> posibleFirstChars = cardinalityCounter(mustBeFirst, null, false, false, - generatorResult); + result); int intersectionCardinality = mustBeFirst.keySet().size(); List intersectionCharacters = posibleFirstChars.get(intersectionCardinality); // If no intersection was found then raise error if (null == intersectionCharacters || intersectionCharacters.size() == 0) { - generatorResult.recordFatalError( + result.recordFatalError( "No intersection for required first character sets in password policy:" + policy.getDescription()); // Log error @@ -165,7 +228,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea LOGGER.trace("Generate first character intersection items [" + tmp + "] into password."); } // Generate random char into password from intersection - password.append(intersectionCharacters.get(rand.nextInt(intersectionCharacters.size()))); + password.append(intersectionCharacters.get(RAND.nextInt(intersectionCharacters.size()))); } } @@ -186,7 +249,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea } // Find all usable characters chars = cardinalityCounter(lims, StringPolicyUtils.stringTokenizer(password.toString()), false, - uniquenessReached, generatorResult); + uniquenessReached, result); // If something goes badly then go out if (null == chars) { return null; @@ -200,7 +263,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea for (int card = 1; card < lims.keySet().size(); card++) { if (chars.containsKey(card)) { List validChars = chars.get(card); - password.append(validChars.get(rand.nextInt(validChars.size()))); + password.append(validChars.get(RAND.nextInt(validChars.size()))); break; } } @@ -208,7 +271,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // test if maximum is not exceeded if (password.length() > maxLen) { - generatorResult.recordFatalError( + result.recordFatalError( "Unable to meet minimal criteria and not exceed maximxal size of password."); return null; } @@ -236,12 +299,12 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea } // find all usable characters chars = cardinalityCounter(lims, StringPolicyUtils.stringTokenizer(password.toString()), true, - uniquenessReached, generatorResult); + uniquenessReached, result); // If something goes badly then go out if (null == chars) { // we hope this never happend. - generatorResult.recordFatalError( + result.recordFatalError( "No valid characters to generate, but no all limitation are reached"); return null; } @@ -267,14 +330,14 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea for (int card = 1; card <= lims.keySet().size(); card++) { if (chars.containsKey(card)) { List validChars = chars.get(card); - password.append(validChars.get(rand.nextInt(validChars.size()))); + password.append(validChars.get(RAND.nextInt(validChars.size()))); break; } } } if (password.length() < minLen) { - generatorResult.recordFatalError( + result.recordFatalError( "Unable to generate password and meet minimal size of password. Password lenght: " + password.length() + ", required: " + minLen); LOGGER.trace( @@ -283,7 +346,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea return null; } - generatorResult.recordSuccess(); + result.recordSuccess(); // Shuffle output to solve pattern like output StrBuilder sb = new StrBuilder(password.substring(0, 1)); @@ -294,14 +357,32 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea return sb.toString(); } - /****************************************************** - * Private helper methods - ******************************************************/ + private boolean checkAttempt(String generatedValue, StringPolicyType policy, PrismObject object, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + LimitationsType limitationsType = policy.getLimitations(); + if (limitationsType == null) { + return true; + } + ExpressionType checkExpression = limitationsType.getCheckExpression(); + if (checkExpression != null && !checkExpression(generatedValue, checkExpression, object, shortDesc, task, result)) { + LOGGER.trace("Check expression returned false for generated value in {}", shortDesc); + return false; + } + // TODO Check pattern + return true; + } + public boolean checkExpression(String generatedValue, ExpressionType checkExpression, PrismObject object, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + ExpressionVariables variables = new ExpressionVariables(); + variables.addVariableDefinition(ExpressionConstants.VAR_INPUT, generatedValue); + variables.addVariableDefinition(ExpressionConstants.VAR_OBJECT, object); + PrismPropertyValue output = ExpressionUtil.evaluateCondition(variables, checkExpression, expressionFactory, shortDesc, task, result); + return ExpressionUtil.getBooleanConditionOutput(output); + } + /** * Count cardinality */ - private static Map> cardinalityCounter(Map> lims, + private Map> cardinalityCounter(Map> lims, List password, Boolean skipMatchedLims, boolean uniquenessReached, OperationResult op) { HashMap counter = new HashMap(); @@ -372,7 +453,7 @@ private static Map> cardinalityCounter(Map a, List b) { + private int charIntersectionCounter(List a, List b) { int ret = 0; for (String s : b) { if (a.contains(s)) { diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java index f901eeafc47..af32213f130 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Evolveum + * Copyright (c) 2013-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; import com.evolveum.midpoint.model.common.expression.script.jsr223.Jsr223ScriptEvaluator; import com.evolveum.midpoint.model.common.expression.script.xpath.XPathScriptEvaluator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.crypto.AESProtector; import com.evolveum.midpoint.schema.util.ObjectResolver; @@ -50,7 +51,8 @@ public static AESProtector createInitializedProtector(PrismContext prismContext) return protector; } - public static ExpressionFactory createInitializedExpressionFactory(ObjectResolver resolver, AESProtector protector, PrismContext prismContext, SecurityEnforcer securityEnforcer) { + public static ExpressionFactory createInitializedExpressionFactory(ObjectResolver resolver, AESProtector protector, + PrismContext prismContext, SecurityEnforcer securityEnforcer) { ExpressionFactory expressionFactory = new ExpressionFactory(resolver, prismContext); // asIs @@ -67,7 +69,9 @@ public static ExpressionFactory createInitializedExpressionFactory(ObjectResolve expressionFactory.addEvaluatorFactory(pathFactory); // generate - GenerateExpressionEvaluatorFactory generateFactory = new GenerateExpressionEvaluatorFactory(protector, resolver, prismContext); + ValuePolicyGenerator valuePolicyGenerator = new ValuePolicyGenerator(); + valuePolicyGenerator.setExpressionFactory(expressionFactory); + GenerateExpressionEvaluatorFactory generateFactory = new GenerateExpressionEvaluatorFactory(protector, resolver, valuePolicyGenerator, prismContext); expressionFactory.addEvaluatorFactory(generateFactory); // script diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index be4f6d5b08d..b0f8dabf32c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -23,6 +23,7 @@ import com.evolveum.midpoint.model.api.*; import com.evolveum.midpoint.model.api.visualizer.Scene; import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.model.impl.visualizer.Visualizer; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -120,6 +121,9 @@ public class ModelInteractionServiceImpl implements ModelInteractionService { @Autowired(required = true) private SystemObjectCache systemObjectCache; + @Autowired(required = true) + private ValuePolicyGenerator valuePolicyGenerator; + @Autowired(required = true) private Protector protector; @@ -814,4 +818,10 @@ public PrismObject mergeObjectsPreviewObject(Class } } + @Override + public String generateValue(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, + PrismObject object, String shortDesc, Task task, OperationResult parentResult) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { + return valuePolicyGenerator.generate(policy, defaultLength, generateMinimalSize, object, shortDesc, task, parentResult); + } + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java index 7309d645628..0488b3462e7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java @@ -286,7 +286,11 @@ public String debugDump(int indent) { @Override public AssignmentPathSegmentType toAssignmentPathSegmentType() { AssignmentPathSegmentType rv = new AssignmentPathSegmentType(); - rv.setAssignment(getAssignment()); + AssignmentType assignment = getAssignment(); + if (assignment != null) { + rv.setAssignment(assignment); + rv.setAssignmentId(assignment.getId()); + } if (source != null) { rv.setSourceRef(ObjectTypeUtil.createObjectRef(source)); rv.setSourceDisplayName(ObjectTypeUtil.getDisplayName(source)); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java index 98ca15d0350..ed3b5bd9e61 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java @@ -77,10 +77,6 @@ public class EvaluatedAssignmentImpl implements EvaluatedAs private boolean presentInCurrentObject; private boolean presentInOldObject; private Collection policySituations = new ArrayList<>(); - // Values that were written to the respective assignment or assignment delta. - // It is easier to check the need for re-synchronization of them in this way - // than to (re)read the whole object and check on it. - private Set policySituationsSynced = null; public ItemDeltaItem,PrismContainerDefinition> getAssignmentIdi() { return assignmentIdi; @@ -475,13 +471,4 @@ public List getNonNegativeTargets() { rv.addAll(roles.getPlusSet()); return rv; } - - @Override - public Set getPolicySituationsSynced() { - return policySituationsSynced; - } - - public void setPolicySituationsSynced(Set policySituationsSynced) { - this.policySituationsSynced = policySituationsSynced; - } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java index aed41bc191e..f4431d3531d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -31,7 +31,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.evolveum.midpoint.common.policy.StringPolicyUtils; +import com.evolveum.midpoint.model.common.stringpolicy.StringPolicyUtils; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; @@ -59,6 +60,7 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -67,6 +69,7 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.CharacterClassType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LimitationsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; @@ -83,6 +86,14 @@ import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +/** + * Password policy processor and validator. Mishmash or old and new code. Only partially refactored. + * Still need to align with ValuePolicyGenerator and the utils. + * + * @author mamut + * @author semancik + * + */ @Component public class PasswordPolicyProcessor { @@ -97,12 +108,15 @@ public class PasswordPolicyProcessor { @Autowired(required = true) Protector protector; + @Autowired(required = true) + private ValuePolicyGenerator valuePolicyGenerator; + @Autowired(required = true) ModelObjectResolver resolver; void processPasswordPolicy(LensFocusContext focusContext, LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) - throws PolicyViolationException, SchemaException { + throws PolicyViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (!UserType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { LOGGER.trace("Skipping processing password policies because focus is not user"); @@ -166,7 +180,7 @@ void processPasswordPolicy(LensFocusContext focusContex passwordPolicy = focusContext.getOrgPasswordPolicy(); } - processPasswordPolicy(passwordPolicy, focusContext.getObjectOld(), passwordValueProperty, result); + processPasswordPolicy(passwordPolicy, focusContext.getObjectOld(), null, passwordValueProperty, task, result); if (passwordValueProperty != null && isPasswordChange) { processPasswordHistoryDeltas(focusContext, context, now, task, result); @@ -174,8 +188,8 @@ void processPasswordPolicy(LensFocusContext focusContex } - private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismObject focus, PrismProperty passwordProperty, OperationResult result) - throws PolicyViolationException, SchemaException { + private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismObject focus, PrismObject projection, PrismProperty passwordProperty, + Task task, OperationResult result) throws PolicyViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (passwordPolicy == null) { LOGGER.trace("Skipping processing password policies. Password policy not specified."); @@ -185,7 +199,12 @@ private void processPasswordPolicy(ValuePolicyType passwor String passwordValue = determinePasswordValue(passwordProperty); PasswordType currentPasswordType = determineCurrentPassword(focus); - boolean isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, result); + boolean isValid; + if (projection != null) { + isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, projection, "projection password policy", task, result); + } else { + isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, focus, "focus password policy", task, result); + } if (!isValid) { result.computeStatus(); @@ -349,7 +368,7 @@ private ValuePolicyType resolvePolicy(PrismObject org, Task task, Opera } void processPasswordPolicy(LensProjectionContext projectionContext, - LensContext context, Task task, OperationResult result) throws SchemaException, PolicyViolationException{ + LensContext context, Task task, OperationResult result) throws SchemaException, PolicyViolationException, ObjectNotFoundException, ExpressionEvaluationException{ ObjectDelta accountDelta = projectionContext.getDelta(); @@ -362,7 +381,7 @@ void processPasswordPolicy(LensProjectionContext projecti return; } - PrismObject accountShadow; + PrismObject accountShadow = null; PrismProperty password = null; if (ChangeType.ADD == accountDelta.getChangeType()){ accountShadow = accountDelta.getObjectToAdd(); @@ -385,15 +404,20 @@ void processPasswordPolicy(LensProjectionContext projecti password = (PrismProperty) passwordValueDelta.getItemNewMatchingPath(null); } - ValuePolicyType passwordPolicy; + ValuePolicyType passwordPolicy = null; if (isCheckOrgPolicy(context)){ passwordPolicy = determineValuePolicy(context.getFocusContext().getObjectAny(), task, result); context.getFocusContext().setOrgPasswordPolicy(passwordPolicy); - } else { + } + if (passwordPolicy == null) { passwordPolicy = projectionContext.getEffectivePasswordPolicy(); } + + if (accountShadow == null) { + accountShadow = projectionContext.getObjectNew(); + } - processPasswordPolicy(passwordPolicy, null, password, result); + processPasswordPolicy(passwordPolicy, null, accountShadow, password, task, result); } private boolean isCheckOrgPolicy(LensContext context) throws SchemaException{ @@ -419,17 +443,8 @@ private boolean isCheckOrgPolicy(LensContext context) return true; } - - /** - * Check provided password against provided policy - * - * @param newPassword - * - password to check - * @param pp - * - Password policy used - * @return - Operation result of this validation - */ - public boolean validatePassword(String newPassword, PasswordType currentPasswordType, ValuePolicyType pp, OperationResult parentResult) { + public boolean validatePassword(String newPassword, PasswordType currentPasswordType, ValuePolicyType pp, + PrismObject object, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { Validate.notNull(pp, "Password policy must not be null."); @@ -489,6 +504,8 @@ public boolean validatePassword(String newPassword, PasswordType currentPassword result.addSubresult(limitResult); } testInvalidCharacters(passwd, allValidChars, result, message); + + testCheckExpression(newPassword, lims, object, shortDesc, task, result, message); if (message.toString() == null || message.toString().isEmpty()) { result.computeStatus(); @@ -733,6 +750,26 @@ private void testMaximalLength(String password, LimitationsType limitations, } } } + + private void testCheckExpression(String newPassword, LimitationsType lims, PrismObject object, + String shortDesc, Task task, OperationResult result, StringBuilder message) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + + ExpressionType expressionType = lims.getCheckExpression(); + if (expressionType == null) { + return; + } + if (!valuePolicyGenerator.checkExpression(newPassword, expressionType, object, shortDesc, task, result)) { + String msg = lims.getCheckExpressionMessage(); + if (msg == null) { + msg = "Check expression failed"; + } + result.addSubresult(new OperationResult("Check expression", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + + } private boolean passwordEquals(String newPassword, ProtectedStringType currentPassword) { return determinePasswordValue(currentPassword).equals(newPassword); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PolicyRuleProcessor.java index 3dc76a3511e..9d80f6c0894 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PolicyRuleProcessor.java @@ -438,40 +438,47 @@ private Collection selectTriggeredRul EvaluatedAssignmentImpl evaluatedAssignment, List situations) { // We consider all rules here, i.e. also those that are triggered on targets induced by this one. // Decision whether to trigger such rules lies on "primary" policy constraints. (E.g. approvals would - // not trigger, whereas exclusions probably yes.) Overall, our responsibility is simply to collect + // not trigger, whereas exclusions probably would.) Overall, our responsibility is simply to collect // all triggered rules. return evaluatedAssignment.getTargetPolicyRules().stream() .filter(r -> !r.getTriggers().isEmpty() && situations.contains(r.getPolicySituation())) .collect(Collectors.toList()); } - private PropertyDelta getAssignmentModificationDelta( - EvaluatedAssignmentImpl evaluatedAssignment) throws SchemaException { + @NotNull + private List> getAssignmentModificationDelta( + EvaluatedAssignmentImpl evaluatedAssignment, List triggers) throws SchemaException { Long id = evaluatedAssignment.getAssignmentType().getId(); if (id == null) { throw new IllegalArgumentException("Assignment with no ID: " + evaluatedAssignment); } + List> deltas = new ArrayList<>(); Set currentSituations = new HashSet<>(evaluatedAssignment.getAssignmentType().getPolicySituation()); Set newSituations = new HashSet<>(evaluatedAssignment.getPolicySituations()); - return createSituationDelta( - new ItemPath(FocusType.F_ASSIGNMENT, id, AssignmentType.F_POLICY_SITUATION), currentSituations, newSituations); + CollectionUtils.addIgnoreNull(deltas, createSituationDelta( + new ItemPath(FocusType.F_ASSIGNMENT, id, AssignmentType.F_POLICY_SITUATION), currentSituations, newSituations)); + Set currentTriggers = new HashSet<>(evaluatedAssignment.getAssignmentType().getTrigger()); + Set newTriggers = new HashSet<>(triggers); + CollectionUtils.addIgnoreNull(deltas, createTriggerDelta( + new ItemPath(FocusType.F_ASSIGNMENT, id, AssignmentType.F_TRIGGER), currentTriggers, newTriggers)); + return deltas; } - private boolean shouldSituationBeUpdated(EvaluatedAssignment evaluatedAssignment) { + private boolean shouldSituationBeUpdated(EvaluatedAssignment evaluatedAssignment, + List triggers) { Set currentSituations = new HashSet<>(evaluatedAssignment.getAssignmentType().getPolicySituation()); - // if the situations that were last synced (if they were) are the same as the current ones => OK - if (evaluatedAssignment.getPolicySituationsSynced() != null - && evaluatedAssignment.getPolicySituationsSynced().equals(currentSituations)) { - LOGGER.trace("policy situations synced are the same as current situations: {}", DebugUtil.debugDumpLazily(evaluatedAssignment)); // TODO remove - return false; - } - // if the current situations are the same as the ones in the old assignment => OK + Set currentTriggers = new HashSet<>(evaluatedAssignment.getAssignmentType().getTrigger()); + // if the current situations different from the ones in the old assignment => update // (provided that the situations in the assignment were _not_ changed directly via a delta!!!) TODO check this - if (currentSituations.equals(new HashSet<>(evaluatedAssignment.getPolicySituations()))) { - LOGGER.trace("current policy situations are the same as computed ones: {}", DebugUtil.debugDumpLazily(evaluatedAssignment)); // TODO remove - return false; + if (!currentSituations.equals(new HashSet<>(evaluatedAssignment.getPolicySituations()))) { + LOGGER.trace("computed policy situations are different from the current ones"); + return true; } - return true; + if (!currentTriggers.equals(new HashSet<>(triggers))) { + LOGGER.trace("computed policy rules triggers are different from the current ones"); + return true; + } + return false; } public void storeFocusPolicySituation(LensContext context, Task task, OperationResult result) @@ -506,6 +513,21 @@ private PropertyDelta createSituationDelta(ItemPath path, Set cu return situationsDelta; } + private PropertyDelta createTriggerDelta(ItemPath path, Set currentTriggers, Set newTriggers) + throws SchemaException { + if (newTriggers.equals(currentTriggers)) { + return null; + } + @SuppressWarnings({ "unchecked", "raw" }) + PropertyDelta triggersDelta = (PropertyDelta) + DeltaBuilder.deltaFor(FocusType.class, prismContext) + .item(path) + .replace(newTriggers.toArray()) // TODO or add + delete? + .asItemDelta(); + triggersDelta.setEstimatedOldValues(PrismPropertyValue.wrap(currentTriggers)); + return triggersDelta; + } + public void addGlobalPoliciesToAssignments(LensContext context, DeltaSetTriple> evaluatedAssignmentTriple) throws SchemaException { @@ -551,23 +573,56 @@ public void applyAssignmentSituationO T focus = objectToAdd.asObjectable(); for (EvaluatedAssignmentImpl evaluatedAssignment : context.getEvaluatedAssignmentTriple().getNonNegativeValues()) { LOGGER.trace("Applying assignment situation on object ADD for {}", evaluatedAssignment); - if (!shouldSituationBeUpdated(evaluatedAssignment)) { + List triggers = getTriggers(evaluatedAssignment); + if (!shouldSituationBeUpdated(evaluatedAssignment, triggers)) { continue; } AssignmentType assignment = evaluatedAssignment.getAssignmentType(); if (assignment.getId() != null) { - PropertyDelta delta = getAssignmentModificationDelta(evaluatedAssignment); - if (delta != null) { - delta.applyTo(objectToAdd); - } + ItemDelta.applyTo(getAssignmentModificationDelta(evaluatedAssignment, triggers), objectToAdd); } else { int i = focus.getAssignment().indexOf(assignment); if (i < 0) { throw new IllegalStateException("Assignment to be replaced not found in an object to add: " + assignment + " / " + objectToAdd); } - copyPolicySituations(focus.getAssignment().get(i), evaluatedAssignment); + copyPolicyData(focus.getAssignment().get(i), evaluatedAssignment, triggers); + } + } + } + + private List getTriggers(EvaluatedAssignmentImpl evaluatedAssignment) { + List rv = new ArrayList<>(); + for (EvaluatedPolicyRule policyRule : evaluatedAssignment.getTargetPolicyRules()) { + for (EvaluatedPolicyRuleTrigger trigger : policyRule.getTriggers()) { + EvaluatedPolicyRuleTriggerType triggerType = trigger.toEvaluatedPolicyRuleTriggerType(policyRule).clone(); + simplifyTrigger(triggerType); + rv.add(triggerType); + } + } + return rv; + } + + private void simplifyTrigger(EvaluatedPolicyRuleTriggerType trigger) { + deleteAssignments(trigger.getAssignmentPath()); + if (trigger instanceof EvaluatedExclusionTriggerType) { + EvaluatedExclusionTriggerType exclusionTrigger = (EvaluatedExclusionTriggerType) trigger; + deleteAssignments(exclusionTrigger.getConflictingObjectPath()); + exclusionTrigger.setConflictingAssignment(null); + } else if (trigger instanceof EvaluatedSituationTriggerType) { + for (EvaluatedPolicyRuleType sourceRule : ((EvaluatedSituationTriggerType) trigger).getSourceRule()) { + for (EvaluatedPolicyRuleTriggerType sourceTrigger : sourceRule.getTrigger()) { + simplifyTrigger(sourceTrigger); + } } - evaluatedAssignment.setPolicySituationsSynced(new HashSet<>(evaluatedAssignment.getPolicySituations())); + } + } + + private void deleteAssignments(AssignmentPathType path) { + if (path == null) { + return; + } + for (AssignmentPathSegmentType segment : path.getSegment()) { + segment.setAssignment(null); } } @@ -578,12 +633,13 @@ public ObjectDelta applyAssignmen } for (EvaluatedAssignmentImpl evaluatedAssignment : context.getEvaluatedAssignmentTriple().getNonNegativeValues()) { LOGGER.trace("Applying assignment situation on object MODIFY for {}", evaluatedAssignment); - if (!shouldSituationBeUpdated(evaluatedAssignment)) { + List triggers = getTriggers(evaluatedAssignment); + if (!shouldSituationBeUpdated(evaluatedAssignment, triggers)) { continue; } AssignmentType assignment = evaluatedAssignment.getAssignmentType(); if (assignment.getId() != null) { - objectDelta = swallow(context, objectDelta, getAssignmentModificationDelta(evaluatedAssignment)); + objectDelta = swallow(context, objectDelta, getAssignmentModificationDelta(evaluatedAssignment, triggers)); } else { if (objectDelta == null) { throw new IllegalStateException("No object delta!"); @@ -598,16 +654,15 @@ public ObjectDelta applyAssignmen throw new IllegalStateException("Unnumbered assignment (" + assignment + ") couldn't be found in object delta (no corresponding assignment value): " + objectDelta); } - copyPolicySituations(assignmentInDelta.asContainerable(), evaluatedAssignment); + copyPolicyData(assignmentInDelta.asContainerable(), evaluatedAssignment, triggers); } - evaluatedAssignment.setPolicySituationsSynced(new HashSet<>(evaluatedAssignment.getPolicySituations())); } return objectDelta; } private ObjectDelta swallow(LensContext context, ObjectDelta objectDelta, - PropertyDelta delta1) throws SchemaException { - if (delta1 == null || delta1.isEmpty()) { + List> deltas) throws SchemaException { + if (deltas.isEmpty()) { return objectDelta; } if (objectDelta == null) { @@ -620,13 +675,16 @@ private ObjectDelta swallow(LensC objectDelta = (ObjectDelta) new ObjectDelta(context.getFocusClass(), ChangeType.MODIFY, prismContext); objectDelta.setOid(context.getFocusContext().getOid()); } - objectDelta.swallow(delta1); + objectDelta.swallow(deltas); return objectDelta; } - private void copyPolicySituations(AssignmentType targetAssignment, EvaluatedAssignmentImpl evaluatedAssignment) { + private void copyPolicyData(AssignmentType targetAssignment, EvaluatedAssignmentImpl evaluatedAssignment, + List triggers) { targetAssignment.getPolicySituation().clear(); targetAssignment.getPolicySituation().addAll(evaluatedAssignment.getPolicySituations()); + targetAssignment.getTrigger().clear(); + targetAssignment.getTrigger().addAll(triggers); } public ObjectDelta applyAssignmentSituation(LensContext context, ObjectDelta focusDelta) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/trigger/TriggerScannerTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/trigger/TriggerScannerTaskHandler.java index 78d45c821dd..100c4ecf711 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/trigger/TriggerScannerTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/trigger/TriggerScannerTaskHandler.java @@ -188,8 +188,7 @@ private void fireTriggers(AbstractScannerResultHandler handler, Pris LOGGER.warn("Trigger without a timestamp in {}", object); } else { if (isHot(handler, timestamp)) { - fireTrigger(trigger, object, workerTask, coordinatorTask, result); - removeTrigger(object, trigger.asPrismContainerValue(), workerTask, triggerContainer.getDefinition()); + fireTrigger(trigger, object, triggerContainer.getDefinition(), workerTask, coordinatorTask, result); } else { LOGGER.trace("Trigger {} is not hot (timestamp={}, thisScanTimestamp={}, lastScanTimestamp={})", trigger, timestamp, handler.getThisScanTimestamp(), handler.getLastScanTimestamp()); @@ -217,18 +216,22 @@ private boolean isHot(AbstractScannerResultHandler handler, XMLGrego return handler.getLastScanTimestamp() == null || handler.getLastScanTimestamp().compare(timestamp) == DatatypeConstants.LESSER; } - private void fireTrigger(TriggerType trigger, PrismObject object, Task workerTask, Task coordinatorTask, OperationResult result) { + private void fireTrigger(TriggerType trigger, PrismObject object, + PrismContainerDefinition triggerContainerDefinition, + Task workerTask, Task coordinatorTask, OperationResult result) { String handlerUri = trigger.getHandlerUri(); if (handlerUri == null) { LOGGER.warn("Trigger without handler URI in {}", object); return; } LOGGER.debug("Firing trigger {} in {}: id={}", handlerUri, object, trigger.getId()); + if (triggerAlreadySeen(coordinatorTask, handlerUri, object.getOid()+":"+trigger.getId())) { + LOGGER.debug("Handler {} already executed for {}:{}", handlerUri, ObjectTypeUtil.toShortString(object), trigger.getId()); + return; + } TriggerHandler handler = triggerHandlerRegistry.getHandler(handlerUri); if (handler == null) { - LOGGER.warn("No registered trigger handler for URI {}", trigger); - } else if (triggerAlreadySeen(coordinatorTask, handlerUri, object.getOid()+":"+trigger.getId())) { - LOGGER.trace("Handler {} already executed for {}:{}", trigger, ObjectTypeUtil.toShortString(object), trigger.getId()); + LOGGER.warn("No registered trigger handler for URI {} in {}", handlerUri, trigger); } else { try { handler.handle(object, trigger, workerTask, result); @@ -239,6 +242,7 @@ private void fireTrigger(TriggerType trigger, PrismObject object, Ta result.recordPartialError(e); } } + removeTrigger(object, trigger.asPrismContainerValue(), workerTask, triggerContainerDefinition); } private void removeTrigger(PrismObject object, PrismContainerValue triggerCVal, Task task, diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index 2c5b3da3090..431728b39ef 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -143,6 +143,7 @@ scope="singleton"> + diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java index 7ab5ac59330..68a19df7706 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -16,14 +16,17 @@ package com.evolveum.midpoint.model.impl.lens; +import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; +import static com.evolveum.midpoint.test.IntegrationTestTools.display; import java.io.File; import java.io.IOException; import javax.xml.bind.JAXBException; +import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; @@ -31,37 +34,51 @@ import org.testng.AssertJUnit; import org.testng.annotations.Test; -import com.evolveum.midpoint.common.policy.StringPolicyUtils; -import com.evolveum.midpoint.common.policy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.StringPolicyUtils; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; import com.evolveum.midpoint.model.impl.AbstractInternalModelIntegrationTest; import com.evolveum.midpoint.model.impl.lens.projector.PasswordPolicyProcessor; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringLimitType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; @ContextConfiguration(locations = {"classpath:ctx-model-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class TestPasswordPolicy extends AbstractInternalModelIntegrationTest { - public static final String BASE_PATH = "src/test/resources/lens/ppolicy/"; + public static final File TEST_DIR = new File("src/test/resources/lens/ppolicy/"); private static final transient Trace LOGGER = TraceManager.getTrace(TestPasswordPolicy.class); + private static final String USER_AB_USERNAME = "ab"; + private static final String USER_AB_FULL_NAME = "Ad Fel"; + private static final String USER_AB_GIVEN_NAME = "Ad"; + private static final String USER_AB_FAMILY_NAME = "Fel"; + private static final String USER_AB_ADDITIONAL_NAME = "x"; + private static final int USERNAME_ATTEMPTS = 200; + private static final int USER_PROPS_ATTEMPTS = 5000; + @Autowired(required = true) private PasswordPolicyProcessor passwordPolicyProcessor; + + @Autowired(required = true) + private ValuePolicyGenerator valuePolicyGenerator; @Test public void stringPolicyUtilsMinimalTest() throws JAXBException, SchemaException, IOException { - String filename = "password-policy-minimal.xml"; - String pathname = BASE_PATH + filename; - File file = new File(pathname); + File file = new File(TEST_DIR, "password-policy-minimal.xml"); ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); StringPolicyType sp = pp.getStringPolicy(); StringPolicyUtils.normalize(sp); @@ -75,9 +92,10 @@ public void stringPolicyUtilsMinimalTest() throws JAXBException, SchemaException @Test public void stringPolicyUtilsComplexTest() { - String filename = "password-policy-complex.xml"; - String pathname = BASE_PATH + filename; - File file = new File(pathname); + final String TEST_NAME = "stringPolicyUtilsComplexTest"; + TestUtil.displayTestTile(TEST_NAME); + + File file = new File(TEST_DIR, "password-policy-complex.xml"); ValuePolicyType pp = null; try { pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); @@ -91,30 +109,42 @@ public void stringPolicyUtilsComplexTest() { @Test public void testPasswordGeneratorComplexNegative() throws Exception { - String filename = "password-policy-complex.xml"; - String pathname = BASE_PATH + filename; - File file = new File(pathname); - ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); - OperationResult op = new OperationResult("passwordGeneratorComplexTest"); + final String TEST_NAME = "testPasswordGeneratorComplexNegative"; + TestUtil.displayTestTile(TEST_NAME); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); - op = new OperationResult("passwordGeneratorComplexTest"); + File file = new File(TEST_DIR, "password-policy-complex.xml"); + ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); + // Make switch some cosistency pp.getStringPolicy().getLimitations().setMinLength(2); pp.getStringPolicy().getLimitations().setMinUniqueChars(5); - String psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, op); - op.computeStatus(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, null, TEST_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + display("Generated password", psswd); + result.computeStatus(); + AssertJUnit.assertTrue(result.isAcceptable()); assertNotNull(psswd); - AssertJUnit.assertTrue(op.isAcceptable()); // Switch to all must be first :-) to test if there is error for (StringLimitType l : pp.getStringPolicy().getLimitations().getLimit()) { l.setMustBeFirst(true); } LOGGER.info("Negative testing: passwordGeneratorComplexTest"); - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, op); - assertNull(psswd); - op.computeStatus(); - AssertJUnit.assertTrue(op.getStatus() == OperationResultStatus.FATAL_ERROR); + try { + valuePolicyGenerator.generate(pp.getStringPolicy(), 10, null, TEST_NAME, task, result); + assertNotReached(); + } catch (ExpressionEvaluationException e) { + result.computeStatus(); + TestUtil.assertFailure(result); + } } @Test @@ -139,90 +169,96 @@ public void testValueGeneratorMustBeFirst() throws Exception { @Test public void testValueGenerateRandomPin() throws Exception { - LOGGER.info("===[ {} ]===", "testValueGenerateRandomPin"); - String pathname = BASE_PATH + "value-policy-random-pin.xml"; - File file = new File(pathname); - LOGGER.info("Positive testing {}: {}", "testValueGenerateRandomPin", "value-policy-random-pin.xml"); - ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); - OperationResult op = new OperationResult("testValueGenerateRandomPin"); + final String TEST_NAME = "testValueGenerateRandomPin"; + TestUtil.displayTestTile(TEST_NAME); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); - String psswd; - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, op); - LOGGER.info("Generated password:" + psswd); - System.out.println("Generated password: " + psswd); - op.computeStatus(); - if (!op.isSuccess()) { - LOGGER.info("Result:" + op.debugDump()); - AssertJUnit.fail("Password generator failed:\n"+op.debugDump()); - } - assertNotNull(psswd); - assertPassword(psswd, pp); + ValuePolicyType pp = parsePasswordPolicy("value-policy-random-pin.xml"); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, null, TEST_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + display("Generated password", psswd); + result.computeStatus(); + TestUtil.assertSuccess(result); + assertNotNull(psswd); + assertPassword(psswd, pp); } @Test public void testValueGenerate() throws Exception { - LOGGER.info("===[ {} ]===", "testValueGenerate"); - String pathname = BASE_PATH + "value-policy-generate.xml"; - File file = new File(pathname); - LOGGER.info("Positive testing {}: {}", "testValueGenerate", "value-policy-generate.xml"); - ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); - OperationResult op = new OperationResult("testValueGenerate"); + final String TEST_NAME = "testValueGenerate"; + TestUtil.displayTestTile(TEST_NAME); - String psswd; - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, op); - LOGGER.info("Generated password:" + psswd); - System.out.println("Generated password: " + psswd); - op.computeStatus(); - if (!op.isSuccess()) { - LOGGER.info("Result:" + op.debugDump()); - AssertJUnit.fail("Password generator failed:\n"+op.debugDump()); - } - assertNotNull(psswd); - assertPassword(psswd, pp); + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + ValuePolicyType pp = parsePasswordPolicy("value-policy-generate.xml"); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, null, TEST_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + display("Generated password", psswd); + result.computeStatus(); + TestUtil.assertSuccess(result); + assertNotNull(psswd); + assertPassword(psswd, pp); } @Test public void testValueGenerateEmpty() throws Exception { - LOGGER.info("===[ {} ]===", "testValueGenerateEmpty"); - String pathname = BASE_PATH + "value-policy-generate-empty.xml"; - File file = new File(pathname); + final String TEST_NAME = "testValueGenerateEmpty"; + TestUtil.displayTestTile(TEST_NAME); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + File file = new File(TEST_DIR, "value-policy-generate-empty.xml"); LOGGER.info("Positive testing {}: {}", "testValueGenerate", "value-policy-generate-empty.xml"); ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); - OperationResult op = new OperationResult("testValueGenerateEmpty"); - String psswd; - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, op); - LOGGER.info("Generated password:" + psswd); - System.out.println("Generated password: " + psswd); - op.computeStatus(); - if (!op.isSuccess()) { - LOGGER.info("Result:" + op.debugDump()); - AssertJUnit.fail("Password generator failed:\n"+op.debugDump()); - } - assertNotNull(psswd); - assertPassword(psswd, pp); - + // WHEN + TestUtil.displayWhen(TEST_NAME); + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, null, TEST_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + display("Generated password", psswd); + result.computeStatus(); + TestUtil.assertSuccess(result); + assertNotNull(psswd); + assertPassword(psswd, pp); } - public void passwordGeneratorTest(final String TEST_NAME, String policyFilename) throws JAXBException, SchemaException, IOException { - LOGGER.info("===[ {} ]===", TEST_NAME); - String pathname = BASE_PATH + policyFilename; - File file = new File(pathname); + public void passwordGeneratorTest(final String TEST_NAME, String policyFilename) throws JAXBException, SchemaException, IOException, ExpressionEvaluationException, ObjectNotFoundException { + TestUtil.displayTestTile(TEST_NAME); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + File file = new File(TEST_DIR, policyFilename); LOGGER.info("Positive testing {}: {}", TEST_NAME, policyFilename); ValuePolicyType pp = (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); - OperationResult op = new OperationResult(TEST_NAME); String psswd; // generate minimal size passwd for (int i = 0; i < 100; i++) { - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, op); + psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, null, TEST_NAME, task, result); LOGGER.info("Generated password:" + psswd); - op.computeStatus(); - if (!op.isSuccess()) { - LOGGER.info("Result:" + op.debugDump()); - AssertJUnit.fail("Password generator failed:\n"+op.debugDump()); + result.computeStatus(); + if (!result.isSuccess()) { + LOGGER.info("Result:" + result.debugDump()); + AssertJUnit.fail("Password generator failed:\n"+result.debugDump()); } assertNotNull(psswd); assertPassword(psswd, pp); @@ -231,21 +267,26 @@ public void passwordGeneratorTest(final String TEST_NAME, String policyFilename) LOGGER.info("-------------------------"); // Generate up to possible for (int i = 0; i < 100; i++) { - psswd = ValuePolicyGenerator.generate(pp.getStringPolicy(), 10, false, op); + psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, false, null, TEST_NAME, task, result); LOGGER.info("Generated password:" + psswd); - op.computeStatus(); - if (!op.isSuccess()) { - LOGGER.info("Result:" + op.debugDump()); + result.computeStatus(); + if (!result.isSuccess()) { + LOGGER.info("Result:" + result.debugDump()); } - AssertJUnit.assertTrue(op.isSuccess()); + AssertJUnit.assertTrue(result.isSuccess()); assertNotNull(psswd); } } - private void assertPassword(String passwd, ValuePolicyType pp) { - OperationResult result = new OperationResult("assertPassword"); - boolean isValid = passwordPolicyProcessor.validatePassword(passwd, null, pp, result); + private void assertPassword(String passwd, ValuePolicyType pp) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + assertPassword(passwd, pp, null); + } + + private void assertPassword(String passwd, ValuePolicyType pp, PrismObject object) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + Task task = createTask("assertPassword"); + OperationResult result = task.getResult(); + boolean isValid = passwordPolicyProcessor.validatePassword(passwd, null, pp, object, "assertPassword", task, result); result.computeStatus(); if (!result.isSuccess()) { AssertJUnit.fail(result.debugDump()); @@ -282,20 +323,100 @@ public void passwordValidationTestTri() throws Exception { AssertJUnit.assertFalse(pwdValidHelper("Pa1", pp)); // too short AssertJUnit.assertFalse(pwdValidHelper("PPPPPPPPPPPPPPPPPPPPPPPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa11111111111111111111111111111111111111111111111111111111111111111111", pp)); // too long } + + /** + * MID-1657 + */ + @Test + public void testUsername() throws Exception { + final String TEST_NAME = "testUsername"; + TestUtil.displayTestTile(TEST_NAME); + + PrismObject user = createUserAb(); + ValuePolicyType pp = parsePasswordPolicy("password-policy-username.xml"); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + + for (int i = 0; i < USERNAME_ATTEMPTS; i++) { + Task task = createTask(TEST_NAME+":"+i); + OperationResult result = task.getResult(); + + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, user, TEST_NAME, task, result); + display("Generated password ("+i+")", psswd); + + result.computeStatus(); + TestUtil.assertSuccess(result); + assertNotNull(psswd); + assertPassword(psswd, pp, user); + + assertFalse("Generated password that matches the username: "+psswd, psswd.equals(USER_AB_USERNAME)); + } + + // THEN + TestUtil.displayThen(TEST_NAME); + } + + /** + * MID-1657 + */ + @Test + public void testUserProps() throws Exception { + final String TEST_NAME = "testUserProps"; + TestUtil.displayTestTile(TEST_NAME); + + PrismObject user = createUserAb(); + display("User", user); + ValuePolicyType pp = parsePasswordPolicy("password-policy-props.xml"); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + + for (int i = 0; i < USER_PROPS_ATTEMPTS; i++) { + Task task = createTask(TEST_NAME+":"+i); + OperationResult result = task.getResult(); + + String psswd = valuePolicyGenerator.generate(pp.getStringPolicy(), 10, true, user, TEST_NAME, task, result); + display("Generated password ("+i+")", psswd); + + result.computeStatus(); + TestUtil.assertSuccess(result); + assertNotNull(psswd); + assertPassword(psswd, pp, user); + + assertNotContains(psswd, USER_AB_USERNAME); + assertNotContains(psswd, USER_AB_GIVEN_NAME); + assertNotContains(psswd, USER_AB_ADDITIONAL_NAME); + } + + // THEN + TestUtil.displayThen(TEST_NAME); + } + + private PrismObject createUserAb() throws SchemaException { + PrismObject user = createUser(USER_AB_USERNAME, USER_AB_GIVEN_NAME, USER_AB_FAMILY_NAME, true); + user.asObjectable().setAdditionalName(createPolyStringType(USER_AB_ADDITIONAL_NAME)); + return user; + } + + private void assertNotContains(String psswd, String val) { + assertFalse("Generated password "+psswd+" contains value "+val, StringUtils.containsIgnoreCase(psswd, val)); + } private ValuePolicyType parsePasswordPolicy(String filename) throws SchemaException, IOException { - File file = new File(BASE_PATH, filename); + File file = new File(TEST_DIR, filename); return (ValuePolicyType) PrismTestUtil.parseObject(file).asObjectable(); } - private boolean pwdValidHelper(String password, ValuePolicyType pp) { - OperationResult op = new OperationResult("Password Validator test with password:" + password); - passwordPolicyProcessor.validatePassword(password, null, pp, op); - op.computeStatus(); - String msg = "-> Policy "+pp.getName()+", password '"+password+"': "+op.getStatus(); + private boolean pwdValidHelper(String password, ValuePolicyType pp) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + Task task = createTask("pwdValidHelper"); + OperationResult result = task.getResult(); + passwordPolicyProcessor.validatePassword(password, null, pp, null, "pwdValidHelper", task, result); + result.computeStatus(); + String msg = "-> Policy "+pp.getName()+", password '"+password+"': "+result.getStatus(); System.out.println(msg); LOGGER.info(msg); - LOGGER.trace(op.debugDump()); - return (op.isSuccess()); + LOGGER.trace(result.debugDump()); + return (result.isSuccess()); } } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicySituations.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyMetadata.java similarity index 90% rename from model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicySituations.java rename to model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyMetadata.java index c635aa36776..cfebc521251 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicySituations.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyMetadata.java @@ -15,11 +15,13 @@ */ package com.evolveum.midpoint.model.impl.lens; +import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalMonitor; 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.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.UserType; @@ -41,7 +43,7 @@ */ @ContextConfiguration(locations = {"classpath:ctx-model-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class TestPolicySituations extends AbstractLensTest { +public class TestPolicyMetadata extends AbstractLensTest { private static final String ROLE_JUDGE_POLICY_RULE_EXCLUSION_NAME = "criminal exclusion"; @@ -61,6 +63,8 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); InternalMonitor.reset(); + + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); } @Test @@ -69,7 +73,7 @@ public void test100JackAssignRoleJudge() throws Exception { TestUtil.displayTestTile(this, TEST_NAME); // GIVEN - Task task = taskManager.createTaskInstance(TestPolicySituations.class.getName() + "." + TEST_NAME); + Task task = taskManager.createTaskInstance(TestPolicyMetadata.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); // WHEN @@ -96,7 +100,7 @@ public void test110JackAssignRolePirate() throws Exception { TestUtil.displayTestTile(this, TEST_NAME); // GIVEN - Task task = taskManager.createTaskInstance(TestPolicySituations.class.getName() + "." + TEST_NAME); + Task task = taskManager.createTaskInstance(TestPolicyMetadata.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); // WHEN @@ -126,7 +130,7 @@ public void test120RecomputeJack() throws Exception { TestUtil.displayTestTile(this, TEST_NAME); // GIVEN - Task task = taskManager.createTaskInstance(TestPolicySituations.class.getName() + "." + TEST_NAME); + Task task = taskManager.createTaskInstance(TestPolicyMetadata.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); // WHEN diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java index 2a17a70e67c..00c79b7d044 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java @@ -73,6 +73,8 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti InternalMonitor.reset(); // InternalMonitor.setTraceShadowFetchOperation(true); + +// DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); } @Test diff --git a/model/model-impl/src/test/resources/lens/ppolicy/password-policy-props.xml b/model/model-impl/src/test/resources/lens/ppolicy/password-policy-props.xml new file mode 100644 index 00000000000..e48b413d5f0 --- /dev/null +++ b/model/model-impl/src/test/resources/lens/ppolicy/password-policy-props.xml @@ -0,0 +1,46 @@ + + + + + + + Testing props policy + + + 10 + 10 + 0 + + + + 30 + + 10 + + abcdefghijklmnx + + + + + \ No newline at end of file diff --git a/model/model-impl/src/test/resources/lens/ppolicy/password-policy-username.xml b/model/model-impl/src/test/resources/lens/ppolicy/password-policy-username.xml new file mode 100644 index 00000000000..59dda93f6a6 --- /dev/null +++ b/model/model-impl/src/test/resources/lens/ppolicy/password-policy-username.xml @@ -0,0 +1,45 @@ + + + + + + + Testing username policy + + + 2 + 2 + 0 + + + + + 2 + + abc + + + + + \ No newline at end of file diff --git a/model/model-impl/testng-unit.xml b/model/model-impl/testng-unit.xml index acc42ed2109..a8c43aeb2e8 100644 --- a/model/model-impl/testng-unit.xml +++ b/model/model-impl/testng-unit.xml @@ -70,6 +70,7 @@ + diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java index f6b5aeab1a2..1ff27272316 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -252,6 +252,7 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String USER_JACK_FAMILY_NAME = "Sparrow"; protected static final String USER_JACK_ADDITIONAL_NAME = "Jackie"; protected static final String USER_JACK_EMPLOYEE_TYPE = "CAPTAIN"; + protected static final String USER_JACK_EMPLOYEE_NUMBER = "emp1234"; protected static final String USER_JACK_LOCALITY = "Caribbean"; protected static final String USER_JACK_PASSWORD = "deadmentellnotales"; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestPassword.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestPassword.java index 52456ea058f..f23c5f918af 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestPassword.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestPassword.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * 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. @@ -63,6 +63,7 @@ public class TestPassword extends AbstractInitializedModelIntegrationTest { private static final String USER_PASSWORD_4_CLEAR = "sh1v3rM3T1mb3rs"; private static final String USER_PASSWORD_5_CLEAR = "s3tSa1al"; private static final String USER_PASSWORD_A_CLEAR = "A"; // too short + private static final String USER_PASSWORD_JACK_CLEAR = "12jAcK34"; // contains username private static final String USER_PASSWORD_VALID_1 = "abcd123"; private static final String USER_PASSWORD_VALID_2 = "abcd223"; private static final String USER_PASSWORD_VALID_3 = "abcd323"; @@ -70,10 +71,16 @@ public class TestPassword extends AbstractInitializedModelIntegrationTest { private static final File TEST_DIR = new File(MidPointTestConstants.TEST_RESOURCES_DIR, "password"); - protected static final String RESOURCE_DUMMY_UGLY_FILENAME = TEST_DIR + "/resource-dummy-ugly.xml"; + protected static final File RESOURCE_DUMMY_UGLY_FILE = new File(TEST_DIR, "resource-dummy-ugly.xml"); protected static final String RESOURCE_DUMMY_UGLY_OID = "10000000-0000-0000-0000-000000344104"; protected static final String RESOURCE_DUMMY_UGLY_NAME = "ugly"; + protected static final File PASSWORD_POLICY_UGLY_FILE = new File(TEST_DIR, "password-policy-ugly.xml"); + protected static final String PASSWORD_POLICY_UGLY_OID = "cfb3fa9e-027a-11e7-8e2c-dbebaacaf4ee"; + + private static final String USER_JACK_EMPLOYEE_NUMBER_NEW_BAD = "No1"; + private static final String USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD = "pir321"; + protected DummyResource dummyResourceUgly; protected DummyResourceContoller dummyResourceCtlUgly; protected ResourceType resourceDummyUglyType; @@ -89,11 +96,13 @@ public class TestPassword extends AbstractInitializedModelIntegrationTest { @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); + + importObjectFromFile(PASSWORD_POLICY_UGLY_FILE); dummyResourceCtlUgly = DummyResourceContoller.create(RESOURCE_DUMMY_UGLY_NAME, resourceDummyUgly); dummyResourceCtlUgly.extendSchemaPirate(); dummyResourceUgly = dummyResourceCtlUgly.getDummyResource(); - resourceDummyUgly = importAndGetObjectFromFile(ResourceType.class, RESOURCE_DUMMY_UGLY_FILENAME, RESOURCE_DUMMY_UGLY_OID, initTask, initResult); + resourceDummyUgly = importAndGetObjectFromFile(ResourceType.class, RESOURCE_DUMMY_UGLY_FILE, RESOURCE_DUMMY_UGLY_OID, initTask, initResult); resourceDummyUglyType = resourceDummyUgly.asObjectable(); dummyResourceCtlUgly.setResource(resourceDummyUgly); @@ -392,7 +401,7 @@ public void test120ModifyUserJackAssignAccountDummyRedAndUgly() throws Exception assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_4_CLEAR); assertDummyAccount(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, null, true); - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER); // User and default dummy account should have unchanged passwords assertEncryptedUserPassword(userJack, USER_PASSWORD_4_CLEAR); @@ -449,7 +458,85 @@ public void test121ModifyJackPasswordUserAndAccountRed() throws Exception { // so even the weak mapping took place. assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER); + + assertLinks(userJack, 3); + + assertPasswordMetadata(userJack, false, lastPasswordChangeStart, lastPasswordChangeEnd); + } + + /** + * Jack employee number is mapped to ugly resource password. + * Change employee number to something that does NOT comply with ugly resource password policy. + * MID-3769 + */ + @Test + public void test125ModifyJackEmployeeNumberBad() throws Exception { + final String TEST_NAME = "test125ModifyJackEmployeeNumberBad"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + try { + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_EMPLOYEE_NUMBER, task, result, + USER_JACK_EMPLOYEE_NUMBER_NEW_BAD); + assertNotReached(); + } catch (PolicyViolationException e) { + // this is expected + display("Expected exception", e); + } + + // THEN + result.computeStatus(); + TestUtil.assertFailure(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User after", userJack); + + assertEncryptedUserPassword(userJack, USER_PASSWORD_1_CLEAR); + assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); + assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); + // ugly password should be changed + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER); + + assertLinks(userJack, 3); + + assertPasswordMetadata(userJack, false, lastPasswordChangeStart, lastPasswordChangeEnd); + } + + /** + * Jack employee number is mapped to ugly resource password. + * Change employee number to something that does comply with ugly resource password policy. + * MID-3769 + */ + @Test + public void test128ModifyJackEmployeeNumberGood() throws Exception { + final String TEST_NAME = "test128ModifyJackEmployeeNumberGood"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_EMPLOYEE_NUMBER, task, result, + USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User after", userJack); + + assertEncryptedUserPassword(userJack, USER_PASSWORD_1_CLEAR); + assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); + assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); + // ugly password should be changed + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); assertLinks(userJack, 3); @@ -478,7 +565,6 @@ public void test130ModifyUserJackAssignAccountDummyYellow() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack); assertLinks(userJack, 4); accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); @@ -495,7 +581,7 @@ public void test130ModifyUserJackAssignAccountDummyYellow() throws Exception { assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_1_CLEAR); // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); assertPasswordMetadata(userJack, false, lastPasswordChangeStart, lastPasswordChangeEnd); @@ -529,7 +615,6 @@ public void test132ModifyUserJackPasswordA() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack); assertLinks(userJack, 4); accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); @@ -546,7 +631,7 @@ public void test132ModifyUserJackPasswordA() throws Exception { assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_A_CLEAR); // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); assertPasswordHistoryEntries(userJack); } @@ -595,7 +680,6 @@ public void test202ReconcileUserJack() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack); assertLinks(userJack, 4); accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); @@ -612,7 +696,7 @@ public void test202ReconcileUserJack() throws Exception { assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_A_CLEAR); // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); assertPasswordHistoryEntries(userJack); } @@ -676,8 +760,8 @@ public void test214RecomputeUserJack() throws Exception { * Change to password that violates the password policy (but is still OK for yellow resource). */ @Test - public void test220ModifyUserJackPasswordBad() throws Exception { - doTestModifyUserJackPasswordFailureWithHistory("test220ModifyUserJackPasswordBad", + public void test220ModifyUserJackPasswordBadA() throws Exception { + doTestModifyUserJackPasswordFailureWithHistory("test220ModifyUserJackPasswordBadA", USER_PASSWORD_1_CLEAR, USER_PASSWORD_VALID_1, USER_PASSWORD_A_CLEAR); } @@ -724,6 +808,16 @@ public void test222ModifyUserJackPasswordBadContainer() throws Exception { assertJackPasswordsWithHistory(USER_PASSWORD_VALID_1, USER_PASSWORD_A_CLEAR); } + /** + * Change to password that violates the password policy (contains username) + * MID-1657 + */ + @Test + public void test224ModifyUserJackPasswordBadJack() throws Exception { + doTestModifyUserJackPasswordFailureWithHistory("test224ModifyUserJackPasswordBadJack", + USER_PASSWORD_JACK_CLEAR, USER_PASSWORD_VALID_1, USER_PASSWORD_A_CLEAR); + } + /** * Change to password that complies with password policy. Again. See that * the change is applied correctly and that it is included in the history. @@ -846,7 +940,6 @@ private void doTestModifyUserJackPasswordFailureWithHistory(final String TEST_NA private void assertJackPasswordsWithHistory(String expectedCurrentPassword, String... expectedPasswordHistory) throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack); assertLinks(userJack, 4); accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); @@ -862,7 +955,7 @@ private void assertJackPasswordsWithHistory(String expectedCurrentPassword, Stri assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, expectedCurrentPassword); // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); assertPasswordHistoryEntries(userJack, expectedPasswordHistory); } @@ -915,7 +1008,6 @@ public void test300TwoParentOrgRefs() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack); assertLinks(userJack, 4); accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); @@ -932,7 +1024,7 @@ public void test300TwoParentOrgRefs() throws Exception { assertDummyAccount(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_VALID_1); - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); } /* @@ -964,7 +1056,6 @@ public void test310RemovePassword() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Jack Sparrow"); assertNull("User password is not null", userJack.asObjectable().getCredentials().getPassword().getValue()); assertDummyPassword(ACCOUNT_JACK_DUMMY_USERNAME, USER_PASSWORD_VALID_1); // password mapping is weak here - so no change is expected @@ -975,7 +1066,7 @@ public void test310RemovePassword() throws Exception { assertDummyPassword(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, null); // password mapping is strong here // this one is not changed - assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "emp1234"); + assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_EMPLOYEE_NUMBER_NEW_GOOD); } @Test diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java index e4645d5bceb..99ca39155c7 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * 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. @@ -1566,7 +1566,8 @@ public void test330ReconcileDummyAzureAddAccountRapp() throws Exception { PrismObject passwordPolicy = getObjectViaRepo(ValuePolicyType.class, PASSWORD_POLICY_LOWER_CASE_ALPHA_AZURE_OID); - boolean isPasswordValid = passwordPolicyProcessor.validatePassword(stringPassword, null, passwordPolicy.asObjectable(), result); + boolean isPasswordValid = passwordPolicyProcessor.validatePassword(stringPassword, null, passwordPolicy.asObjectable(), + userRapp, TEST_NAME, task, result); assertTrue("Password doesn't satisfy password policy, generated password: " + stringPassword, isPasswordValid); // These are protected accounts, they should not be imported diff --git a/model/model-intest/src/test/resources/common/password-policy-global.xml b/model/model-intest/src/test/resources/common/password-policy-global.xml index 348d5b05cf2..c704a2a2612 100644 --- a/model/model-intest/src/test/resources/common/password-policy-global.xml +++ b/model/model-intest/src/test/resources/common/password-policy-global.xml @@ -1,6 +1,6 @@ + + + must not contain username, family name and given name and additional names Alphas 1 diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index a3724a26a1b..0494e15dcc5 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -78,10 +78,11 @@ - + + diff --git a/model/model-intest/src/test/resources/password/password-policy-ugly.xml b/model/model-intest/src/test/resources/password/password-policy-ugly.xml new file mode 100644 index 00000000000..2c12b1376e5 --- /dev/null +++ b/model/model-intest/src/test/resources/password/password-policy-ugly.xml @@ -0,0 +1,47 @@ + + + + + Ugly Password Policy + + + 5 + 8 + 0 + + Alphas + 2 + 5 + false + + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + + + + Numbers + 2 + 5 + false + + 1234567890 + + + + + 0 + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/password/resource-dummy-ugly.xml b/model/model-intest/src/test/resources/password/resource-dummy-ugly.xml index dce1d763355..3f2fd56b58d 100644 --- a/model/model-intest/src/test/resources/password/resource-dummy-ugly.xml +++ b/model/model-intest/src/test/resources/password/resource-dummy-ugly.xml @@ -75,6 +75,7 @@ + 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 c82da0bd85c..4c28774c501 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 @@ -46,17 +46,14 @@ import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.path.IdItemPathSegment; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.query.AndFilter; -import com.evolveum.midpoint.prism.query.EqualFilter; -import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.OrgFilter; -import com.evolveum.midpoint.prism.query.RefFilter; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.prism.util.PrismTestUtil; @@ -3941,4 +3938,18 @@ protected List filter(List records, AuditEve .filter(r -> r.getEventType() == type && r.getEventStage() == stage) .collect(Collectors.toList()); } + + protected void resetTriggerTask(String taskOid, File taskFile, OperationResult result) + throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, FileNotFoundException { + taskManager.suspendAndDeleteTasks(Collections.singletonList(taskOid), 60000L, true, result); + importObjectFromFile(taskFile, result); + taskManager.suspendTasks(Collections.singletonList(taskOid), 60000L, result); + modifySystemObjectInRepo(TaskType.class, taskOid, + DeltaBuilder.deltaFor(TaskType.class, prismContext) + .item(TaskType.F_SCHEDULE).replace() + .asItemDeltas(), + result); + taskManager.resumeTasks(Collections.singleton(taskOid), result); + } + } diff --git a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemAllocationEvent.java b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemAllocationEvent.java index e0bbcdf43ac..cdb95be14f0 100644 --- a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemAllocationEvent.java +++ b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemAllocationEvent.java @@ -32,14 +32,14 @@ */ public class WorkItemAllocationEvent extends WorkItemEvent { - private WorkItemNotificationActionType notificationAction; - public WorkItemAllocationEvent(LightweightIdentifierGenerator lightweightIdentifierGenerator, ChangeType changeType, @NotNull WorkItemType workItem, @Nullable SimpleObjectRef assignee, WorkItemOperationKindType operationKind, - SimpleObjectRef initiator, WfContextType workflowContext, + SimpleObjectRef initiator, WorkItemResultType workItemResult, AbstractWorkItemActionType source, + WorkItemEventCauseInformationType cause, + WfContextType workflowContext, Duration timeBefore) { - super(lightweightIdentifierGenerator, changeType, workItem, assignee, initiator, operationKind, workflowContext, - null, timeBefore); + super(lightweightIdentifierGenerator, changeType, workItem, assignee, initiator, operationKind, + workItemResult, source, cause, workflowContext,null, timeBefore); } @Override diff --git a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemCustomEvent.java b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemCustomEvent.java index fcc3a60f1df..c4844cd14f8 100644 --- a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemCustomEvent.java +++ b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemCustomEvent.java @@ -19,10 +19,7 @@ import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.LightweightIdentifierGenerator; -import com.evolveum.midpoint.xml.ns._public.common.common_3.EventCategoryType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemNotificationActionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,14 +31,11 @@ */ public class WorkItemCustomEvent extends WorkItemEvent { - private WorkItemNotificationActionType notificationAction; - public WorkItemCustomEvent(LightweightIdentifierGenerator lightweightIdentifierGenerator, ChangeType changeType, - @NotNull WorkItemType workItem, @Nullable SimpleObjectRef assignee, WfContextType workflowContext, - @NotNull WorkItemNotificationActionType notificationAction) { - super(lightweightIdentifierGenerator, changeType, workItem, assignee, null, null, workflowContext, - notificationAction.getHandler(), null); - this.notificationAction = notificationAction; + @NotNull WorkItemType workItem, @Nullable SimpleObjectRef assignee, @NotNull WorkItemNotificationActionType source, + WorkItemEventCauseInformationType cause, WfContextType workflowContext) { + super(lightweightIdentifierGenerator, changeType, workItem, assignee, null, null, null, source, + cause, workflowContext, source.getHandler(), null); } @Override @@ -56,6 +50,10 @@ public void createExpressionVariables(Map variables, OperationRes super.createExpressionVariables(variables, result); } + public WorkItemNotificationActionType getNotificationAction() { + return (WorkItemNotificationActionType) getSource(); + } + @Override public String toString() { return "WorkItemCustomEvent:" + super.toString(); diff --git a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemEvent.java b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemEvent.java index 2bbe9a57449..d8584801e55 100644 --- a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemEvent.java +++ b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemEvent.java @@ -45,19 +45,27 @@ public class WorkItemEvent extends WorkflowEvent { */ protected final SimpleObjectRef initiator; protected final WorkItemOperationKindType operationKind; + protected final AbstractWorkItemActionType source; + protected final WorkItemEventCauseInformationType cause; protected final Duration timeBefore; - - public WorkItemEvent(LightweightIdentifierGenerator lightweightIdentifierGenerator, ChangeType changeType, - @NotNull WorkItemType workItem, - @Nullable SimpleObjectRef assignee, SimpleObjectRef initiator, WorkItemOperationKindType operationKind, - WfContextType workflowContext, - EventHandlerType handler, Duration timeBefore) { + protected final WorkItemResultType workItemResult; + + WorkItemEvent(LightweightIdentifierGenerator lightweightIdentifierGenerator, ChangeType changeType, + @NotNull WorkItemType workItem, + @Nullable SimpleObjectRef assignee, SimpleObjectRef initiator, WorkItemOperationKindType operationKind, + WorkItemResultType workItemResult, @Nullable AbstractWorkItemActionType source, + @Nullable WorkItemEventCauseInformationType cause, + WfContextType workflowContext, + EventHandlerType handler, Duration timeBefore) { super(lightweightIdentifierGenerator, changeType, workflowContext, handler); Validate.notNull(workItem); this.workItem = workItem; this.assignee = assignee; this.initiator = initiator; this.operationKind = operationKind; + this.workItemResult = workItemResult; + this.source = source; + this.cause = cause; this.timeBefore = timeBefore; } @@ -87,6 +95,14 @@ public WorkItemOperationKindType getOperationKind() { return operationKind; } + public AbstractWorkItemActionType getSource() { + return source; + } + + public WorkItemEventCauseInformationType getCause() { + return cause; + } + public Duration getTimeBefore() { return timeBefore; } @@ -98,9 +114,13 @@ public void createExpressionVariables(Map variables, OperationRes variables.put(SchemaConstants.C_WORK_ITEM, workItem); } + public WorkItemResultType getWorkItemResult() { + return workItemResult != null ? workItemResult : workItem.getResult(); + } + @Override - protected String getAnswer() { - WorkItemResultType result = workItem.getResult(); + public String getAnswer() { + WorkItemResultType result = getWorkItemResult(); return result != null ? result.getOutcomeAsString() : null; } diff --git a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemLifecycleEvent.java b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemLifecycleEvent.java index 9c3bac3e280..d2cfc20f63e 100644 --- a/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemLifecycleEvent.java +++ b/model/notifications-api/src/main/java/com/evolveum/midpoint/notifications/api/events/WorkItemLifecycleEvent.java @@ -35,8 +35,10 @@ public WorkItemLifecycleEvent(LightweightIdentifierGenerator lightweightIdentifi @NotNull WorkItemType workItem, @Nullable SimpleObjectRef assignee, @Nullable SimpleObjectRef initiator, WorkItemOperationKindType operationKind, + WorkItemResultType workItemResult, AbstractWorkItemActionType source, WorkItemEventCauseInformationType cause, WfContextType workflowContext) { - super(lightweightIdentifierGenerator, changeType, workItem, assignee, initiator, operationKind, workflowContext, null, null); + super(lightweightIdentifierGenerator, changeType, workItem, assignee, initiator, operationKind, + workItemResult, source, cause, workflowContext, null, null); } @Override diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java index 6e4c4af53ba..8569491abf9 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java @@ -20,7 +20,6 @@ import com.evolveum.midpoint.notifications.api.events.*; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.LightweightIdentifierGenerator; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.logging.LoggingUtils; @@ -93,7 +92,7 @@ public void onProcessInstanceEnd(Task wfTask, OperationResult result) { public void onWorkItemCreation(WorkItemType workItem, ObjectReferenceType originalAssigneeRef, Task wfTask, OperationResult result) { WorkItemEvent event = new WorkItemLifecycleEvent(identifierGenerator, ChangeType.ADD, workItem, - SimpleObjectRefImpl.create(functions, originalAssigneeRef), null, null, wfTask.getWorkflowContext()); + SimpleObjectRefImpl.create(functions, originalAssigneeRef), null, null, null, null, null, wfTask.getWorkflowContext()); initializeWorkflowEvent(event, wfTask); processEvent(event, result); } @@ -101,22 +100,22 @@ public void onWorkItemCreation(WorkItemType workItem, ObjectReferenceType origin @Override public void onWorkItemDeletion(WorkItemType workItem, ObjectReferenceType assignee, ObjectReferenceType initiator, WorkItemOperationKindType operationKind, + WorkItemResultType workItemResult, AbstractWorkItemActionType source, WorkItemEventCauseInformationType cause, Task wfTask, OperationResult result) { WorkItemEvent event = new WorkItemLifecycleEvent(identifierGenerator, ChangeType.DELETE, workItem, SimpleObjectRefImpl.create(functions, assignee), SimpleObjectRefImpl.create(functions, initiator), - operationKind, wfTask.getWorkflowContext()); + operationKind, workItemResult, source, cause, wfTask.getWorkflowContext()); initializeWorkflowEvent(event, wfTask); processEvent(event, result); } @Override public void onWorkItemCustomEvent(WorkItemType workItem, ObjectReferenceType assignee, - @NotNull WorkItemNotificationActionType notificationAction, Task wfTask, + @NotNull WorkItemNotificationActionType notificationAction, WorkItemEventCauseInformationType cause, Task wfTask, OperationResult result) { WorkItemEvent event = new WorkItemCustomEvent(identifierGenerator, ChangeType.ADD, workItem, - SimpleObjectRefImpl.create(functions, assignee), wfTask.getWorkflowContext(), - notificationAction); + SimpleObjectRefImpl.create(functions, assignee), notificationAction, cause, wfTask.getWorkflowContext()); initializeWorkflowEvent(event, wfTask); processEvent(event, result); } @@ -124,11 +123,11 @@ public void onWorkItemCustomEvent(WorkItemType workItem, ObjectReferenceType ass @Override public void onWorkItemAllocationChangeCurrentActors(WorkItemType workItem, List currentActors, Duration timeBefore, WorkItemOperationKindType operationKind, ObjectReferenceType initiator, - AbstractWorkItemActionType source, + WorkItemResultType workItemResult, AbstractWorkItemActionType source, WorkItemEventCauseInformationType cause, Task task, OperationResult result) { checkOids(currentActors); for (ObjectReferenceType currentActor : currentActors) { - onWorkItemAllocationModifyDelete(currentActor, workItem, timeBefore, operationKind, initiator, source, cause, task, result); + onWorkItemAllocationModifyDelete(currentActor, workItem, timeBefore, operationKind, initiator, workItemResult, source, cause, task, result); } } @@ -157,18 +156,21 @@ private void onWorkItemAllocationAdd(ObjectReferenceType newActor, WorkItemType WorkItemAllocationEvent event = new WorkItemAllocationEvent(identifierGenerator, ChangeType.ADD, workItem, SimpleObjectRefImpl.create(functions, newActor), operationKind, SimpleObjectRefImpl.create(functions, initiator), + null, source, cause, task.getWorkflowContext(), null); initializeWorkflowEvent(event, task); processEvent(event, result); } private void onWorkItemAllocationModifyDelete(ObjectReferenceType currentActor, WorkItemType workItem, Duration timeBefore, - WorkItemOperationKindType operationKind, ObjectReferenceType initiator, AbstractWorkItemActionType source, + WorkItemOperationKindType operationKind, ObjectReferenceType initiator, WorkItemResultType workItemResult, + AbstractWorkItemActionType source, WorkItemEventCauseInformationType cause, Task task, OperationResult result) { WorkItemAllocationEvent event = new WorkItemAllocationEvent(identifierGenerator, timeBefore != null ? ChangeType.MODIFY : ChangeType.DELETE, workItem, SimpleObjectRefImpl.create(functions, currentActor), operationKind, SimpleObjectRefImpl.create(functions, initiator), + workItemResult, source, cause, task.getWorkflowContext(), timeBefore); initializeWorkflowEvent(event, task); processEvent(event, result); diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java index 92abb11016c..29e3d24cfdd 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/SimpleWorkflowNotifier.java @@ -200,8 +200,20 @@ private void appendWorkItemInformation(StringBuilder sb, WorkItemEvent event, Op SimpleObjectRef initiator = event.getInitiator(); if (initiator != null) { UserType initiatorFull = (UserType) functions.getObjectType(initiator, true, result); - sb.append("Carried out by: ").append(formatUserName(initiatorFull, initiator.getOid())).append("\n"); + sb.append("Carried out by: ").append(formatUserName(initiatorFull, initiator.getOid())); + WorkItemEventCauseInformationType cause = event.getCause(); + if (cause != null && cause.getType() == WorkItemEventCauseTypeType.TIMED_ACTION) { + sb.append(" (timed action"); + if (cause.getDisplayName() != null) { + sb.append(" '").append(cause.getDisplayName()).append("'"); + } else if (cause.getName() != null) { + sb.append(" '").append(cause.getName()).append("'"); + } + sb.append(")"); + } + sb.append("\n"); } + appendResultInformation(sb, event); if (!isDone(event) && workItem.getDeadline() != null) { XMLGregorianCalendar deadline = workItem.getDeadline(); diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java index 10429e4d210..f43bedab9c9 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java @@ -52,12 +52,17 @@ public interface WorkItemListener { * @param assignee * @param initiator * @param operationKind + * @param workItemResult + * @param source + * @param cause */ void onWorkItemDeletion(WorkItemType workItem, ObjectReferenceType assignee, ObjectReferenceType initiator, - WorkItemOperationKindType operationKind, Task wfTask, OperationResult result); + WorkItemOperationKindType operationKind, WorkItemResultType workItemResult, AbstractWorkItemActionType source, + WorkItemEventCauseInformationType cause, + Task wfTask, OperationResult result); void onWorkItemCustomEvent(WorkItemType workItem, ObjectReferenceType assignee, - WorkItemNotificationActionType notificationAction, Task wfTask, + WorkItemNotificationActionType notificationAction, WorkItemEventCauseInformationType cause, Task wfTask, OperationResult result); /** @@ -65,7 +70,7 @@ void onWorkItemCustomEvent(WorkItemType workItem, ObjectReferenceType assignee, */ void onWorkItemAllocationChangeCurrentActors(WorkItemType workItem, List currentActors, Duration timeBefore, WorkItemOperationKindType operationKind, ObjectReferenceType initiator, - AbstractWorkItemActionType source, + WorkItemResultType workItemResult, AbstractWorkItemActionType source, WorkItemEventCauseInformationType reason, Task task, OperationResult result); diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowConstants.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowConstants.java index bfe92931363..a0b321b86c3 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowConstants.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowConstants.java @@ -40,4 +40,7 @@ public class WorkflowConstants { public static final String AUDIT_WORK_ITEM_ID = "wf.workItemId"; public static final String AUDIT_PROCESS_INSTANCE_ID = "wf.processInstanceId"; public static final String AUDIT_REQUESTER_COMMENT = "wf.requesterComment"; + public static final String AUDIT_CAUSE_TYPE = "wf.causeType"; + public static final String AUDIT_CAUSE_NAME = "wf.causeName"; + public static final String AUDIT_CAUSE_DISPLAY_NAME = "wf.causeDisplayName"; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/activiti/dao/WorkItemManager.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/activiti/dao/WorkItemManager.java index 08971a51c53..18de496ef4f 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/activiti/dao/WorkItemManager.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/activiti/dao/WorkItemManager.java @@ -46,7 +46,6 @@ import com.evolveum.midpoint.wf.impl.util.SingleItemSerializationSafeContainerImpl; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; -import org.activiti.engine.FormService; import org.activiti.engine.TaskService; import org.activiti.engine.form.FormProperty; import org.activiti.engine.form.TaskFormData; @@ -112,7 +111,6 @@ public void completeWorkItem(String workItemId, String decision, String comment, taskService.setVariableLocal(workItemId, CommonProcessVariableNames.VARIABLE_CAUSE, new SingleItemSerializationSafeContainerImpl<>(causeInformation, prismContext)); - FormService formService = activitiEngine.getFormService(); TaskFormData data = activitiEngine.getFormService().getTaskFormData(workItemId); WorkItemType workItem = workItemProvider.getWorkItem(workItemId, result); @@ -242,7 +240,7 @@ public void delegateWorkItem(String workItemId, List delega result.addContext("user", toShortString(principal.getUser())); ObjectReferenceType initiator = - principal.getUser() != null && (causeInformation == null || causeInformation.getCause() == WorkItemEventCauseType.USER_ACTION) ? + principal.getUser() != null && (causeInformation == null || causeInformation.getType() == WorkItemEventCauseTypeType.USER_ACTION) ? ObjectTypeUtil.createObjectRef(principal.getUser()) : null; LOGGER.trace("Delegating work item {} to {}: escalation={}:{}/{}; cause={}", workItemId, delegates, escalate, @@ -259,7 +257,7 @@ public void delegateWorkItem(String workItemId, List delega com.evolveum.midpoint.task.api.Task wfTask = taskManager.getTask(workItem.getTaskRef().getOid(), result); wfTaskController.notifyWorkItemAllocationChangeCurrentActors(workItem, assigneesBefore, null, operationKind, - initiator, null, causeInformation, wfTask, result); + initiator, null, null, causeInformation, wfTask, result); List newAssignees; if (method == null) { diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/messages/TaskEvent.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/messages/TaskEvent.java index 85079ced7b7..399e72a3954 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/messages/TaskEvent.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/messages/TaskEvent.java @@ -177,6 +177,21 @@ public void setCandidateGroups(List candidateGroups) { // return (String) delegateTask.getVariable(CommonProcessVariableNames.VARIABLE_PROCESS_INSTANCE_NAME); // } - - + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "taskId='" + taskId + '\'' + + ", taskName='" + taskName + '\'' + + ", assigneeOid='" + assigneeOid + '\'' + + ", processInstanceName='" + processInstanceName + '\'' + + ", processInstanceId='" + processInstanceId + '\'' + + ", createTime=" + createTime + + ", dueDate=" + dueDate + + ", candidateUsers=" + candidateUsers + + ", candidateGroups=" + candidateGroups + + ", owner='" + owner + '\'' + + ", executionId='" + executionId + '\'' + + ", variables=" + variables + + '}'; + } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java index 9f8247f6c17..c2b14fb4f1e 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java @@ -129,13 +129,13 @@ private void executeNotifications(Duration timeBeforeAction, AbstractWorkItemAct operationKind = null; } WorkItemEventCauseInformationType cause = new WorkItemEventCauseInformationType(); - cause.setCause(WorkItemEventCauseType.TIMED_ACTION); + cause.setType(WorkItemEventCauseTypeType.TIMED_ACTION); if (action != null) { - cause.setCauseName(action.getName()); - cause.setCauseDisplayName(action.getDisplayName()); + cause.setName(action.getName()); + cause.setDisplayName(action.getDisplayName()); } wfTaskController.notifyWorkItemAllocationChangeCurrentActors(workItem, workItem.getAssigneeRef(), timeBeforeAction, - operationKind, null, action, cause, wfTask, result); + operationKind, null, null, action, cause, wfTask, result); } private void executeActions(WorkItemActionsType actions, WorkItemType workItem, Task wfTask, Task triggerScannerTask, @@ -202,21 +202,22 @@ private List computeDelegateTo(DelegateWorkItemActionType d private void executeNotificationAction(WorkItemType workItem, @NotNull WorkItemNotificationActionType notificationAction, Task wfTask, OperationResult result) throws SchemaException { + WorkItemEventCauseInformationType cause = createCauseInformation(notificationAction); if (BooleanUtils.isNotFalse(notificationAction.isPerAssignee())) { for (ObjectReferenceType assignee : workItem.getAssigneeRef()) { - wfTaskController.notifyWorkItemCustom(workItem, assignee, wfTask, notificationAction, result); + wfTaskController.notifyWorkItemCustom(workItem, assignee, cause, wfTask, notificationAction, result); } } else { - wfTaskController.notifyWorkItemCustom(workItem, null, wfTask, notificationAction, result); + wfTaskController.notifyWorkItemCustom(workItem, null, cause, wfTask, notificationAction, result); } } private WorkItemEventCauseInformationType createCauseInformation(AbstractWorkItemActionType action) { WorkItemEventCauseInformationType cause = new WorkItemEventCauseInformationType(); - cause.setCause(WorkItemEventCauseType.TIMED_ACTION); - cause.setCauseName(action.getName()); - cause.setCauseDisplayName(action.getDisplayName()); + cause.setType(WorkItemEventCauseTypeType.TIMED_ACTION); + cause.setName(action.getName()); + cause.setDisplayName(action.getDisplayName()); return cause; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/InitializeLoopThroughApproversInLevel.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/InitializeLoopThroughApproversInLevel.java index 7ab1f17a276..a7a126f9cda 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/InitializeLoopThroughApproversInLevel.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/InitializeLoopThroughApproversInLevel.java @@ -52,7 +52,7 @@ public class InitializeLoopThroughApproversInLevel implements JavaDelegate { public void execute(DelegateExecution execution) { PrismContext prismContext = getPrismContext(); - WfExpressionEvaluationHelper evaluator = SpringApplicationContextHolder.getExpressionEvaluationHelper(); + WfExpressionEvaluationHelper evaluationHelper = SpringApplicationContextHolder.getExpressionEvaluationHelper(); LOGGER.trace("Executing the delegate; execution = {}", execution); @@ -68,8 +68,8 @@ public void execute(DelegateExecution execution) { if (level.getAutomaticallyApproved() != null) { try { opTask.setChannel(wfTask.getChannel()); - expressionVariables = evaluator.getDefaultVariables(execution, wfTask, opResult); - boolean preApproved = evaluator.evaluateBooleanExpression(level.getAutomaticallyApproved(), expressionVariables, + expressionVariables = evaluationHelper.getDefaultVariables(execution, wfTask, opResult); + boolean preApproved = evaluationHelper.evaluateBooleanExpression(level.getAutomaticallyApproved(), expressionVariables, "automatic approval expression", opTask, opResult); LOGGER.trace("Pre-approved = {} for level {}", preApproved, level); if (preApproved) { @@ -89,9 +89,9 @@ public void execute(DelegateExecution execution) { if (!level.getApproverExpression().isEmpty()) { try { if (expressionVariables == null) { - expressionVariables = evaluator.getDefaultVariables(execution, wfTask, opResult); + expressionVariables = evaluationHelper.getDefaultVariables(execution, wfTask, opResult); } - approverRefs.addAll(evaluator.evaluateRefExpressions(level.getApproverExpression(), expressionVariables, + approverRefs.addAll(evaluationHelper.evaluateRefExpressions(level.getApproverExpression(), expressionVariables, "resolving approver expression", opTask, opResult)); } catch (ExpressionEvaluationException|ObjectNotFoundException|SchemaException|RuntimeException e) { throw new SystemException("Couldn't evaluate approvers expressions", e); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/MidpointUtil.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/MidpointUtil.java index f8226cc4403..5bb5e119da6 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/MidpointUtil.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/MidpointUtil.java @@ -151,6 +151,8 @@ static void setTaskDeadline(DelegateTask delegateTask, Duration duration) { public static void createTriggersForTimedActions(String workItemId, int escalationLevel, Date workItemCreateTime, Date workItemDeadline, Task wfTask, List timedActionsList, OperationResult result) { + LOGGER.trace("Creating triggers for timed actions for work item {}, escalation level {}, create time {}, deadline {}, {} timed action(s)", + workItemId, escalationLevel, workItemCreateTime, workItemDeadline, timedActionsList.size()); try { PrismContext prismContext = getPrismContext(); List triggers = new ArrayList<>(); @@ -325,6 +327,8 @@ public static void removeTriggersForWorkItem(Task wfTask, String workItemId, Ope removeSelectedTriggers(wfTask, toDelete, result); } + // not necessary any more, as work item triggers are deleted when the work item (task) is deleted + // (and there are currently no triggers other than work-item-related) public static void removeAllStageTriggersForWorkItem(Task wfTask, OperationResult result) { List> toDelete = new ArrayList<>(); for (TriggerType triggerType : wfTask.getTaskPrismObject().asObjectable().getTrigger()) { diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/SummarizeDecisionsInLevel.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/SummarizeDecisionsInLevel.java index a70f7b00c0a..ef1cb854cf4 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/SummarizeDecisionsInLevel.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/SummarizeDecisionsInLevel.java @@ -30,6 +30,8 @@ import org.activiti.engine.delegate.JavaDelegate; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static com.evolveum.midpoint.wf.impl.processes.common.SpringApplicationContextHolder.getPrismContext; @@ -70,13 +72,18 @@ public void execute(DelegateExecution execution) { allApproved &= ApprovalUtils.isApproved(event.getResult()); } approved = allApproved; - if (level.getEvaluationStrategy() == LevelEvaluationStrategyType.FIRST_DECIDES && itemEvents.size() != 1) { - throw new IllegalStateException("Not exactly one response with firstDecides strategy in " - + WfContextUtil.getBriefDiagInfo(wfc) + ": " + itemEvents.size() + " response(s)"); + if (level.getEvaluationStrategy() == LevelEvaluationStrategyType.FIRST_DECIDES) { + Set outcomes = itemEvents.stream() + .map(e -> e.getResult().getOutcome()) + .collect(Collectors.toSet()); + if (outcomes.size() > 1) { + LOGGER.warn("Ambiguous outcome with firstDecides strategy in {}: {} response(s), providing outcomes of {}", + WfContextUtil.getBriefDiagInfo(wfc), itemEvents.size(), outcomes); + } } } - MidpointUtil.removeAllStageTriggersForWorkItem(wfTask, result); + //MidpointUtil.removeAllStageTriggersForWorkItem(wfTask, result); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Approval process instance {} (id {}), level {}: result of this level: {}", diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskCompleteListener.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskCompleteListener.java index 295fe33ea4c..61c6334ce6b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskCompleteListener.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskCompleteListener.java @@ -76,14 +76,9 @@ public void notify(DelegateTask delegateTask) { } LOGGER.trace("======================================== Recording individual decision of {}", user); - WorkItemCompletionEventType event = new WorkItemCompletionEventType(); - ActivitiUtil.fillInWorkItemEvent(event, user, delegateTask.getId(), delegateTask.getVariables(), prismContext); - event.setCause(ActivitiUtil.getVariable(delegateTask.getVariables(), CommonProcessVariableNames.VARIABLE_CAUSE, - WorkItemEventCauseInformationType.class, prismContext)); - @NotNull WorkItemResultType result = getItemApprovalProcessInterface().extractWorkItemResult(delegateTask.getVariables()); - event.setResult(result); - boolean isApproved = ApprovalUtils.isApproved(result); + @NotNull WorkItemResultType result1 = getItemApprovalProcessInterface().extractWorkItemResult(delegateTask.getVariables()); + boolean isApproved = ApprovalUtils.isApproved(result1); LevelEvaluationStrategyType levelEvaluationStrategyType = level.getEvaluationStrategy(); Boolean setLoopApprovesInLevelStop = null; @@ -107,13 +102,9 @@ public void notify(DelegateTask delegateTask) { LOGGER.debug("Approval process instance {} (id {}), level {}: recording decision {}; level stops now: {}", execution.getVariable(CommonProcessVariableNames.VARIABLE_PROCESS_INSTANCE_NAME), execution.getProcessInstanceId(), - WfContextUtil.getLevelDiagName(level), result.getOutcomeAsString(), setLoopApprovesInLevelStop); + WfContextUtil.getLevelDiagName(level), result1.getOutcomeAsString(), setLoopApprovesInLevelStop); } - ObjectDeltaType additionalDelta = result.getAdditionalDeltas() != null ? - result.getAdditionalDeltas().getFocusPrimaryDelta() : null; - MidpointUtil.recordEventInTask(event, additionalDelta, wfTask.getOid(), opResult); - getActivitiInterface().notifyMidpointAboutTaskEvent(delegateTask); getActivitiInterface().notifyMidpointAboutProcessEvent(execution); } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskDeleteListener.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskDeleteListener.java index 12af85b75d3..c0576438565 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskDeleteListener.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/itemApproval/TaskDeleteListener.java @@ -41,13 +41,12 @@ public class TaskDeleteListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { - DelegateExecution execution = delegateTask.getExecution(); - PrismContext prismContext = getPrismContext(); - OperationResult opResult = new OperationResult(TaskCompleteListener.class.getName() + ".notify"); - Task wfTask = ActivitiUtil.getTask(execution, opResult); + //DelegateExecution execution = delegateTask.getExecution(); + //PrismContext prismContext = getPrismContext(); + //OperationResult opResult = new OperationResult(TaskCompleteListener.class.getName() + ".notify"); + //Task wfTask = ActivitiUtil.getTask(execution, opResult); //ApprovalLevelType level = ActivitiUtil.getAndVerifyCurrentStage(execution, wfTask, true, prismContext); - MidpointUtil.removeTriggersForWorkItem(wfTask, delegateTask.getId(), opResult); getActivitiInterface().notifyMidpointAboutTaskEvent(delegateTask); } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseAuditHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseAuditHelper.java index c43596a99b0..443137b9d65 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseAuditHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/BaseAuditHelper.java @@ -36,6 +36,7 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.api.WorkflowConstants; import com.evolveum.midpoint.wf.api.WorkflowException; +import com.evolveum.midpoint.wf.impl.messages.TaskEvent; import com.evolveum.midpoint.wf.impl.tasks.WfTask; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -134,10 +135,6 @@ public AuditEventRecord prepareWorkItemAuditReportCommon(WorkItemType workItem, ObjectReferenceType objectRef = resolveIfNeeded(workItem.getObjectRef(), result); record.setTarget(objectRef.asReferenceValue()); -// @SuppressWarnings("unchecked") -// PrismObject targetOwner = (PrismObject) ObjectTypeUtil.getPrismObjectFromReference(workItem.getOriginalAssigneeRef()); -// record.setTargetOwner(targetOwner); - record.setOutcome(OperationResultStatus.SUCCESS); record.setParameter(wfTask.getCompleteStageInfo()); @@ -168,26 +165,38 @@ public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, } // workItem contains taskRef, assignee, candidates resolved (if possible) - public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, - OperationResult result) throws WorkflowException { + public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, + WorkItemEventCauseInformationType cause, WorkItemResultType workItemResult, WfTask wfTask, + OperationResult result) throws WorkflowException { AuditEventRecord record = prepareWorkItemAuditReportCommon(workItem, wfTask, AuditEventStage.EXECUTION, result); setCurrentUserAsInitiator(record); + if (cause != null) { + if (cause.getType() != null) { + record.addPropertyValue(WorkflowConstants.AUDIT_CAUSE_TYPE, cause.getType().value()); + } + if (cause.getName() != null) { + record.addPropertyValue(WorkflowConstants.AUDIT_CAUSE_NAME, cause.getName()); + } + if (cause.getDisplayName() != null) { + record.addPropertyValue(WorkflowConstants.AUDIT_CAUSE_DISPLAY_NAME, cause.getDisplayName()); + } + } + // message + result StringBuilder message = new StringBuilder(); String stageInfo = wfTask.getCompleteStageInfo(); if (stageInfo != null) { message.append(stageInfo).append(" : "); } - WorkItemResultType itemResult = workItem.getResult(); - if (itemResult != null) { - String answer = ApprovalUtils.makeNice(itemResult.getOutcomeAsString()); + if (workItemResult != null) { + String answer = ApprovalUtils.makeNice(workItemResult.getOutcomeAsString()); record.setResult(answer); message.append(answer); - if (itemResult.getComment() != null) { - message.append(" : ").append(itemResult.getComment()); - record.addPropertyValue(WorkflowConstants.AUDIT_COMMENT, itemResult.getComment()); + if (workItemResult.getComment() != null) { + message.append(" : ").append(workItemResult.getComment()); + record.addPropertyValue(WorkflowConstants.AUDIT_COMMENT, workItemResult.getComment()); } } else { message.append("(no decision)"); // TODO diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java index ff16b56c2c2..f9c522a804b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java @@ -32,6 +32,8 @@ import com.evolveum.midpoint.wf.impl.tasks.WfTask; import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; import com.evolveum.midpoint.xml.ns._public.common.common_3.WfConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemResultType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; import org.jetbrains.annotations.NotNull; @@ -106,9 +108,12 @@ public interface ChangeProcessor { * Prepares a work item-related audit record. */ // workItem contains taskRef, assignee, candidates resolved (if possible) - AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, OperationResult result) throws WorkflowException; + AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, WfTask wfTask, + OperationResult result) throws WorkflowException; - AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, OperationResult result) throws WorkflowException; + AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WorkItemEventCauseInformationType cause, + WorkItemResultType workItemResult, TaskEvent taskEvent, WfTask wfTask, + OperationResult result) throws WorkflowException; /** * Auxiliary method to access autowired Spring beans from within non-spring java objects. diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java index e892c75b2d4..782909bf84e 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java @@ -199,15 +199,17 @@ public AuditEventRecord prepareProcessInstanceAuditRecord(WfTask wfTask, AuditEv } @Override - public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, + public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { return getScenarioBean(taskEvent.getVariables()).prepareWorkItemCreatedAuditRecord(workItem, wfTask, taskEvent, result); } @Override - public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, + public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WorkItemEventCauseInformationType cause, + WorkItemResultType workItemResult, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { - return getScenarioBean(taskEvent.getVariables()).prepareWorkItemDeletedAuditRecord(workItem, wfTask, taskEvent, result); + return getScenarioBean(taskEvent.getVariables()) + .prepareWorkItemDeletedAuditRecord(workItem, taskEvent, cause, workItemResult, wfTask, result); } //endregion } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java index c068dee17f3..0209409f823 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java @@ -33,6 +33,8 @@ import com.evolveum.midpoint.wf.impl.processors.general.GcpExternalizationHelper; import com.evolveum.midpoint.wf.impl.processors.general.GeneralChangeProcessorSpecificContent; import com.evolveum.midpoint.xml.ns._public.common.common_3.GeneralChangeProcessorScenarioType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemResultType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -98,9 +100,10 @@ public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, } @Override - public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, - OperationResult result) throws WorkflowException { - return baseAuditHelper.prepareWorkItemDeletedAuditRecord(workItem, wfTask, result); + public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, + WorkItemEventCauseInformationType cause, WorkItemResultType workItemResult, WfTask wfTask, + OperationResult result) throws WorkflowException { + return baseAuditHelper.prepareWorkItemDeletedAuditRecord(workItem, taskEvent, cause, workItemResult, wfTask, result); // TODO fill-in missing delta somehow } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/GcpScenarioBean.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/GcpScenarioBean.java index 01450ff4ea5..901b0274eab 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/GcpScenarioBean.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/GcpScenarioBean.java @@ -28,6 +28,8 @@ import com.evolveum.midpoint.wf.impl.tasks.WfTaskCreationInstruction; import com.evolveum.midpoint.wf.impl.messages.TaskEvent; import com.evolveum.midpoint.xml.ns._public.common.common_3.GeneralChangeProcessorScenarioType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemResultType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; import java.util.Map; @@ -53,7 +55,9 @@ public interface GcpScenarioBean { AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, OperationResult result) throws WorkflowException; - AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, OperationResult result) throws WorkflowException; + AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, + WorkItemEventCauseInformationType cause, WorkItemResultType workItemResult, WfTask wfTask, + OperationResult result) throws WorkflowException; WfTaskCreationInstruction prepareJobCreationInstruction(GeneralChangeProcessorScenarioType scenarioType, LensContext context, WfTask rootWfTask, Task taskFromModel, OperationResult result) throws SchemaException; } 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 87ed978a4fd..2712d7b101d 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 @@ -57,7 +57,6 @@ import java.util.*; import java.util.stream.Collectors; -import static com.evolveum.midpoint.audit.api.AuditEventStage.EXECUTION; import static com.evolveum.midpoint.audit.api.AuditEventStage.REQUEST; import static com.evolveum.midpoint.model.api.context.ModelState.PRIMARY; import static com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor.ExecutionMode.*; @@ -363,7 +362,7 @@ public AuditEventRecord prepareProcessInstanceAuditRecord(WfTask wfTask, AuditEv } @Override - public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, + public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { AuditEventRecord auditEventRecord = baseAuditHelper.prepareWorkItemCreatedAuditRecord(workItem, wfTask, result); try { @@ -376,15 +375,17 @@ public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, } @Override - public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WfTask wfTask, TaskEvent taskEvent, + public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WorkItemEventCauseInformationType cause, + WorkItemResultType workItemResult, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { - AuditEventRecord auditEventRecord = baseAuditHelper.prepareWorkItemDeletedAuditRecord(workItem, wfTask, result); + AuditEventRecord auditEventRecord = baseAuditHelper.prepareWorkItemDeletedAuditRecord(workItem, taskEvent, + cause, workItemResult, wfTask, result); try { // TODO - or merge with original deltas? - if (workItem.getResult() != null && workItem.getResult().getOutcome() == WorkItemOutcomeType.APPROVE - && workItem.getResult().getAdditionalDeltas() != null) { + if (workItemResult != null && workItemResult.getOutcome() == WorkItemOutcomeType.APPROVE + && workItemResult.getAdditionalDeltas() != null) { addDeltasToEventRecord(auditEventRecord, - ObjectTreeDeltas.fromObjectTreeDeltasType(workItem.getResult().getAdditionalDeltas(), getPrismContext())); + ObjectTreeDeltas.fromObjectTreeDeltasType(workItemResult.getAdditionalDeltas(), getPrismContext())); } } catch (SchemaException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't retrieve deltas to be put into audit record", e); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskController.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskController.java index 7d006721a30..f3fa0f053b7 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskController.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/WfTaskController.java @@ -19,15 +19,17 @@ import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.audit.api.AuditService; +import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.prism.PrismContext; +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.ObjectTypeUtil; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -41,11 +43,15 @@ import com.evolveum.midpoint.wf.impl.processes.ProcessMidPointInterface; import com.evolveum.midpoint.wf.impl.processes.common.ActivitiUtil; import com.evolveum.midpoint.wf.impl.processes.common.CommonProcessVariableNames; +import com.evolveum.midpoint.wf.impl.processes.common.WfTimedActionTriggerHandler; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.MidpointUtil; import com.evolveum.midpoint.wf.impl.processors.ChangeProcessor; import com.evolveum.midpoint.wf.impl.processors.primary.PcpWfTask; import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; +import com.evolveum.midpoint.wf.util.ApprovalUtils; 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.BooleanUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -57,6 +63,7 @@ import java.util.*; import static com.evolveum.midpoint.task.api.TaskExecutionStatus.WAITING; +import static com.evolveum.midpoint.wf.impl.processes.common.SpringApplicationContextHolder.getItemApprovalProcessInterface; /** * Manages everything related to a activiti process instance, including the task that monitors that process instance. @@ -71,36 +78,22 @@ public class WfTaskController { private static final Trace LOGGER = TraceManager.getTrace(WfTaskController.class); public static final long TASK_START_DELAY = 5000L; + public static final long COMPLETION_TRIGGER_EQUALITY_THRESHOLD = 10000L; + private static final Object DOT_CLASS = WfTaskController.class.getName() + "."; private Set processListeners = new HashSet<>(); private Set workItemListeners = new HashSet<>(); - //region Spring beans - @Autowired - private WfTaskUtil wfTaskUtil; - - @Autowired - private TaskManager taskManager; - - @Autowired - private ActivitiInterface activitiInterface; - - @Autowired - private AuditService auditService; - - @Autowired - private MiscDataUtil miscDataUtil; - - @Autowired - private ProcessInterfaceFinder processInterfaceFinder; - - @Autowired - private WfConfiguration wfConfiguration; - - @Autowired - private PrismContext prismContext; - //endregion + @Autowired private WfTaskUtil wfTaskUtil; + @Autowired private TaskManager taskManager; + @Autowired private ActivitiInterface activitiInterface; + @Autowired private AuditService auditService; + @Autowired private MiscDataUtil miscDataUtil; + @Autowired private ProcessInterfaceFinder processInterfaceFinder; + @Autowired private WfConfiguration wfConfiguration; + @Autowired private PrismContext prismContext; + @Autowired private Clock clock; //region Job creation & re-creation /** @@ -327,9 +320,10 @@ public void onTaskEvent(WorkItemType workItem, TaskEvent taskEvent, OperationRes final Task shadowTask = taskManager.createTaskInstance(shadowTaskType.asPrismObject(), result); final WfTask wfTask = recreateWfTask(shadowTask); - // auditing & notifications + // auditing & notifications & event if (taskEvent instanceof TaskCreatedEvent) { - AuditEventRecord auditEventRecord = getChangeProcessor(taskEvent).prepareWorkItemCreatedAuditRecord(workItem, wfTask, taskEvent, result); + AuditEventRecord auditEventRecord = getChangeProcessor(taskEvent).prepareWorkItemCreatedAuditRecord(workItem, + taskEvent, wfTask, result); auditService.audit(auditEventRecord, wfTask.getTask()); try { notifyWorkItemCreated(workItem, workItem.getOriginalAssigneeRef(), wfTask, result); @@ -341,28 +335,107 @@ public void onTaskEvent(WorkItemType workItem, TaskEvent taskEvent, OperationRes LoggingUtils.logUnexpectedException(LOGGER, "Couldn't send notification about work item create event", e); } } else if (taskEvent instanceof TaskDeletedEvent) { - AuditEventRecord auditEventRecord = getChangeProcessor(taskEvent).prepareWorkItemDeletedAuditRecord(workItem, wfTask, taskEvent, result); + // this might be cancellation because of: + // (1) user completion of this task + // (2) timed completion of this task + // (3) user completion of another task + // (4) timed completion of another task + // (5) process stop/deletion + // + // Actually, when the source is (4) timed completion of another task, it is quite probable that this task + // would be closed for the same reason. For a user it would be misleading if we would simply view this task + // as 'cancelled', while, in fact, it is e.g. approved/rejected because of a timed action. + + LOGGER.error("###### processing {}", taskEvent, new IllegalStateException()); + WorkItemOperationKindType operationKind = BooleanUtils.isTrue(ActivitiUtil.getVariable(taskEvent.getVariables(), + CommonProcessVariableNames.VARIABLE_WORK_ITEM_WAS_COMPLETED, Boolean.class, prismContext)) ? + WorkItemOperationKindType.COMPLETE : null; + WorkItemEventCauseInformationType cause = ActivitiUtil.getVariable(taskEvent.getVariables(), + CommonProcessVariableNames.VARIABLE_CAUSE, WorkItemEventCauseInformationType.class, prismContext); + WorkItemResultType workItemResult = getItemApprovalProcessInterface().extractWorkItemResult(taskEvent.getVariables()); + boolean genuinelyCompleted = operationKind != null; + + MidPointPrincipal user; + try { + user = SecurityUtil.getPrincipal(); + } catch (SecurityViolationException e) { + throw new SystemException("Couldn't determine current user: " + e.getMessage(), e); + } + + ObjectReferenceType userRef = user != null ? user.toObjectReference() : workItem.getCompletedByRef(); // partial fallback + + if (!genuinelyCompleted) { + TaskType task = wfTask.getTask().getTaskPrismObject().asObjectable(); + int foundTimedActions = 0; + for (TriggerType trigger : task.getTrigger()) { + if (!WfTimedActionTriggerHandler.HANDLER_URI.equals(trigger.getHandlerUri())) { + continue; + } + String workItemId = ObjectTypeUtil.getExtensionItemRealValue(trigger.getExtension(), SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); + if (!taskEvent.getTaskId().equals(workItemId)) { + continue; + } + Duration timeBeforeAction = ObjectTypeUtil.getExtensionItemRealValue(trigger.getExtension(), SchemaConstants.MODEL_EXTENSION_TIME_BEFORE_ACTION); + if (timeBeforeAction != null) { + continue; + } + WorkItemActionsType actions = ObjectTypeUtil.getExtensionItemRealValue(trigger.getExtension(), SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTIONS); + if (actions == null || actions.getComplete() == null) { + continue; + } + long diff = XmlTypeConverter.toMillis(trigger.getTimestamp()) - clock.currentTimeMillis(); + if (diff >= COMPLETION_TRIGGER_EQUALITY_THRESHOLD) { + continue; + } + CompleteWorkItemActionType completeAction = actions.getComplete(); + operationKind = WorkItemOperationKindType.COMPLETE; + cause = new WorkItemEventCauseInformationType(); + cause.setType(WorkItemEventCauseTypeType.TIMED_ACTION); + cause.setName(completeAction.getName()); + cause.setDisplayName(completeAction.getDisplayName()); + foundTimedActions++; + workItemResult = new WorkItemResultType(); + workItemResult.setOutcome(completeAction.getOutcome() != null ? completeAction.getOutcome() : WorkItemOutcomeType.REJECT); + workItemResult.setOutcomeAsString(ApprovalUtils.approvalStringValue(completeAction.getOutcome())); + } + if (foundTimedActions > 1) { + LOGGER.warn("Multiple 'work item complete' timed actions ({}) for {}: {}", foundTimedActions, + ObjectTypeUtil.toShortString(task), task.getTrigger()); + } + } + + AuditEventRecord auditEventRecord = getChangeProcessor(taskEvent).prepareWorkItemDeletedAuditRecord(workItem, + cause, workItemResult, taskEvent, wfTask, result); auditService.audit(auditEventRecord, wfTask.getTask()); try { - WorkItemOperationKindType operationKind = BooleanUtils.isTrue(ActivitiUtil.getVariable(taskEvent.getVariables(), - CommonProcessVariableNames.VARIABLE_WORK_ITEM_WAS_COMPLETED, Boolean.class, prismContext)) ? - WorkItemOperationKindType.COMPLETE : null; if (workItem.getAssigneeRef().isEmpty()) { - notifyWorkItemDeleted(workItem, null, workItem.getCompletedByRef(), operationKind, wfTask, result); + notifyWorkItemDeleted(workItem, null, userRef, operationKind, workItemResult, + null, cause, wfTask, result); } else { for (ObjectReferenceType assignee : workItem.getAssigneeRef()) { - notifyWorkItemDeleted(workItem, assignee, workItem.getCompletedByRef(), operationKind, wfTask, result); + notifyWorkItemDeleted(workItem, assignee, userRef, operationKind, workItemResult, + null, cause, wfTask, result); } } - WorkItemEventCauseInformationType cause = ActivitiUtil.getVariable(taskEvent.getVariables(), - CommonProcessVariableNames.VARIABLE_CAUSE, WorkItemEventCauseInformationType.class, prismContext); notifyWorkItemAllocationChangeCurrentActors(workItem, workItem.getAssigneeRef(), - null, operationKind, workItem.getCompletedByRef(), null, cause, + null, operationKind, userRef, workItemResult,null, cause, wfTask.getTask(), result); } catch (SchemaException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't audit work item complete event", e); } - } + + if (genuinelyCompleted || workItemResult != null) { + WorkItemCompletionEventType event = new WorkItemCompletionEventType(); + ActivitiUtil.fillInWorkItemEvent(event, user, taskEvent.getTaskId(), taskEvent.getVariables(), prismContext); + event.setCause(cause); + event.setResult(workItemResult); + ObjectDeltaType additionalDelta = workItemResult.getAdditionalDeltas() != null ? + workItemResult.getAdditionalDeltas().getFocusPrimaryDelta() : null; + MidpointUtil.recordEventInTask(event, additionalDelta, wfTask.getTask().getOid(), result); + } + + MidpointUtil.removeTriggersForWorkItem(wfTask.getTask(), taskEvent.getTaskId(), result); + } } //endregion @@ -400,21 +473,24 @@ private void notifyWorkItemCreated(WorkItemType workItem, ObjectReferenceType or } private void notifyWorkItemDeleted(WorkItemType workItem, ObjectReferenceType assignee, - ObjectReferenceType initiator, WorkItemOperationKindType operationKind, WfTask wfTask, OperationResult result) throws SchemaException { + ObjectReferenceType initiator, WorkItemOperationKindType operationKind, + WorkItemResultType workItemResult, @Nullable AbstractWorkItemActionType source, + @Nullable WorkItemEventCauseInformationType cause, + WfTask wfTask, OperationResult result) throws SchemaException { for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemDeletion(workItem, assignee, initiator, operationKind, wfTask.getTask(), result); + workItemListener.onWorkItemDeletion(workItem, assignee, initiator, operationKind, workItemResult, source, cause, wfTask.getTask(), result); } } public void notifyWorkItemAllocationChangeCurrentActors(WorkItemType workItem, @NotNull List originalActors, Duration timeBefore, WorkItemOperationKindType operationKind, ObjectReferenceType initiator, - @Nullable AbstractWorkItemActionType source, + WorkItemResultType workItemResult, @Nullable AbstractWorkItemActionType source, @Nullable WorkItemEventCauseInformationType cause, Task wfTask, OperationResult result) throws SchemaException { for (WorkItemListener workItemListener : workItemListeners) { workItemListener.onWorkItemAllocationChangeCurrentActors(workItem, originalActors, timeBefore, operationKind, - initiator, source, cause, wfTask, result); + initiator, workItemResult, source, cause, wfTask, result); } } @@ -429,11 +505,12 @@ public void notifyWorkItemAllocationChangeNewActors(WorkItemType workItem, @NotN } } - public void notifyWorkItemCustom(WorkItemType workItem, @Nullable ObjectReferenceType assignee, Task wfTask, + public void notifyWorkItemCustom(WorkItemType workItem, @Nullable ObjectReferenceType assignee, + WorkItemEventCauseInformationType cause, Task wfTask, @NotNull WorkItemNotificationActionType notificationAction, OperationResult result) throws SchemaException { for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemCustomEvent(workItem, assignee, notificationAction, wfTask, result); + workItemListener.onWorkItemCustomEvent(workItem, assignee, notificationAction, cause, wfTask, result); } } 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 39126836d38..ee53b4e75ac 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 @@ -457,6 +457,14 @@ protected WorkItemType getWorkItem(Task task, OperationResult result) return itemsAll.get(0); } + protected SearchResultList getWorkItems(Task task, OperationResult result) throws Exception { + return modelService.searchContainers(WorkItemType.class, null, null, task, result); + } + + protected void displayWorkItems(String title, List workItems) { + workItems.forEach(wi -> display(title, wi)); + } + protected ObjectReferenceType ort(String oid) { return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER); } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestEscalation.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestEscalation.java index db31abb5cb6..57aaba8db28 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestEscalation.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestEscalation.java @@ -16,28 +16,36 @@ package com.evolveum.midpoint.wf.impl.policy.other; +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.model.api.WorkflowService; +import com.evolveum.midpoint.notifications.api.transports.Message; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.schema.SearchResultList; 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.wf.api.WorkflowConstants; import com.evolveum.midpoint.wf.impl.activiti.ActivitiEngine; import com.evolveum.midpoint.wf.impl.policy.AbstractWfTestPolicy; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static com.evolveum.midpoint.test.IntegrationTestTools.display; -import static org.testng.AssertJUnit.assertEquals; +import static com.evolveum.midpoint.test.IntegrationTestTools.displayCollection; +import static org.testng.AssertJUnit.*; /** * @author mederly @@ -63,9 +71,11 @@ protected PrismObject getDefaultActor() { protected static final File TEST_ESCALATION_RESOURCE_DIR = new File("src/test/resources/policy/escalation"); protected static final File METAROLE_ESCALATED_FILE = new File(TEST_ESCALATION_RESOURCE_DIR, "metarole-escalated.xml"); protected static final File ROLE_E1_FILE = new File(TEST_ESCALATION_RESOURCE_DIR, "role-e1.xml"); + protected static final File ROLE_E2_FILE = new File(TEST_ESCALATION_RESOURCE_DIR, "role-e2.xml"); protected String metaroleEscalatedOid; protected String roleE1Oid; + protected String roleE2Oid; private PrismObject userLead1, userLead2; private String workItemId; private String approvalTaskOid; @@ -78,11 +88,14 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti metaroleEscalatedOid = repoAddObjectFromFile(METAROLE_ESCALATED_FILE, initResult).getOid(); roleE1Oid = repoAddObjectFromFile(ROLE_E1_FILE, initResult).getOid(); + roleE2Oid = repoAddObjectFromFile(ROLE_E2_FILE, initResult).getOid(); importObjectFromFile(TASK_TRIGGER_SCANNER_FILE); userLead1 = getUser(userLead1Oid); userLead2 = getUser(userLead2Oid); + + importLead1Deputies(initTask, initResult); } @Test @@ -189,4 +202,133 @@ public void test130Complete() throws Exception { assertAssignedRole(userJackOid, roleE1Oid, task, result); } + @Test + public void test200CreateTaskE2() throws Exception { + final String TEST_NAME = "test200CreateTaskE2"; + TestUtil.displayTestTile(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + clock.resetOverride(); + resetTriggerTask(TASK_TRIGGER_SCANNER_OID, TASK_TRIGGER_SCANNER_FILE, result); + + // WHEN + + assignRole(userJackOid, roleE2Oid, task, result); // should start approval process + + // THEN + assertNotAssignedRole(userJackOid, roleE2Oid, task, result); + + List workItems = getWorkItems(task, result); + displayWorkItems("Work items", workItems); + + approvalTaskOid = workItems.get(0).getTaskRef().getOid(); + PrismObject wfTask = getTask(approvalTaskOid); + + display("workflow task", wfTask); + + // D-0 days: escalate (twice) + assertEquals("Wrong # of triggers", 2, wfTask.asObjectable().getTrigger().size()); + } + + @Test + public void test210Escalate() throws Exception { + final String TEST_NAME = "test210Escalate"; + TestUtil.displayTestTile(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + dummyAuditService.clear(); + dummyTransport.clearMessages(); + + // WHEN + + clock.overrideDuration("P3DT10M"); // at 3D there's a deadline with escalation + waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); + + // THEN + + SearchResultList workItems = getWorkItems(task, result); + displayWorkItems("Work items after deadline", workItems); + + PrismObject wfTask = getTask(approvalTaskOid); + display("workflow task", wfTask); + + // D-0 days: reject (twice) + assertEquals("Wrong # of triggers", 2, wfTask.asObjectable().getTrigger().size()); + + displayCollection("audit records", dummyAuditService.getRecords()); + display("dummy transport", dummyTransport); + } + + @Test + public void test220Reject() throws Exception { + final String TEST_NAME = "test220Reject"; + TestUtil.displayTestTile(this, TEST_NAME); + login(userAdministrator); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + dummyAuditService.clear(); + dummyTransport.clearMessages(); + + // WHEN + + clock.resetOverride(); + clock.overrideDuration("P5DT20M"); // at 5D there's a deadline with auto-rejection + waitForTaskNextRun(TASK_TRIGGER_SCANNER_OID, true, 20000, true); + + // THEN + + SearchResultList workItems = getWorkItems(task, result); + displayWorkItems("Work items after deadline", workItems); + assertEquals("Wrong # of work items", 0, workItems.size()); + + PrismObject wfTask = getTask(approvalTaskOid); + display("workflow task", wfTask); + assertEquals("Wrong # of triggers", 0, wfTask.asObjectable().getTrigger().size()); + Map eventMap = new HashMap<>(); + for (WfProcessEventType event : wfTask.asObjectable().getWorkflowContext().getEvent()) { + if (event instanceof WorkItemCompletionEventType) { + WorkItemCompletionEventType c = (WorkItemCompletionEventType) event; + eventMap.put(c.getWorkItemId(), c); + assertNotNull("No result in "+c, c.getResult()); + assertEquals("Wrong outcome in "+c, WorkItemOutcomeType.REJECT, c.getResult().getOutcome()); + assertNotNull("No cause in "+c, c.getCause()); + assertEquals("Wrong cause type in "+c, WorkItemEventCauseTypeType.TIMED_ACTION, c.getCause().getType()); + assertEquals("Wrong cause name in "+c, "auto-reject", c.getCause().getName()); + assertEquals("Wrong cause display name in "+c, "Automatic rejection at deadline", c.getCause().getDisplayName()); + } + } + assertEquals("Wrong # of completion events", 2, eventMap.size()); + + displayCollection("audit records", dummyAuditService.getRecords()); + List workItemAuditRecords = dummyAuditService.getRecordsOfType(AuditEventType.WORK_ITEM); + assertEquals("Wrong # of work item audit records", 2, workItemAuditRecords.size()); + for (AuditEventRecord r : workItemAuditRecords) { + assertEquals("Wrong causeType in "+r, Collections.singleton("timedAction"), r.getPropertyValues(WorkflowConstants.AUDIT_CAUSE_TYPE)); + assertEquals("Wrong causeName in "+r, Collections.singleton("auto-reject"), r.getPropertyValues(WorkflowConstants.AUDIT_CAUSE_NAME)); + assertEquals("Wrong causeDisplayName in "+r, Collections.singleton("Automatic rejection at deadline"), r.getPropertyValues(WorkflowConstants.AUDIT_CAUSE_DISPLAY_NAME)); + assertEquals("Wrong result in "+r, "Rejected", r.getResult()); + } + displayCollection("notifications - process", dummyTransport.getMessages("dummy:simpleWorkflowNotifier-Processes")); + List notifications = dummyTransport.getMessages("dummy:simpleWorkflowNotifier-WorkItems"); + displayCollection("notifications - work items", notifications); + for (Message notification : notifications) { + assertContains(notification, "Carried out by: midPoint Administrator (administrator) (timed action 'Automatic rejection at deadline')"); + assertContains(notification, "Result: REJECTED"); + } + } + + private void assertContains(Message notification, String text) { + if (!notification.getBody().contains(text)) { + fail("No '"+text+"' in "+notification); + } + } + } diff --git a/model/workflow-impl/src/test/resources/logback-test.xml b/model/workflow-impl/src/test/resources/logback-test.xml index 00742c29cff..b31de8741db 100644 --- a/model/workflow-impl/src/test/resources/logback-test.xml +++ b/model/workflow-impl/src/test/resources/logback-test.xml @@ -34,7 +34,7 @@ - + diff --git a/model/workflow-impl/src/test/resources/policy/escalation/role-e2.xml b/model/workflow-impl/src/test/resources/policy/escalation/role-e2.xml new file mode 100644 index 00000000000..35755579305 --- /dev/null +++ b/model/workflow-impl/src/test/resources/policy/escalation/role-e2.xml @@ -0,0 +1,74 @@ + + + + role-e2 + + + + + + + + + + + + firstDecides + P3D + + escalate + + + auto-escalate + Automatic escalation at deadline + + + + P5D + addAssignees + deputies + + + + + reject + + + auto-reject + Automatic rejection at deadline + reject + + + 1 + 1 + + + + + + + + \ No newline at end of file diff --git a/model/workflow-impl/src/test/resources/policy/system-configuration.xml b/model/workflow-impl/src/test/resources/policy/system-configuration.xml index 54cb928bafb..1220dd0eaec 100644 --- a/model/workflow-impl/src/test/resources/policy/system-configuration.xml +++ b/model/workflow-impl/src/test/resources/policy/system-configuration.xml @@ -32,7 +32,27 @@ true - + + + + workflowProcessEvent + + recipient@evolveum.com + + dummy:simpleWorkflowNotifier-Processes + + + + + workItemEvent + + recipient@evolveum.com + + dummy:simpleWorkflowNotifier-WorkItems + + + + true diff --git a/repo/repo-test-util/pom.xml b/repo/repo-test-util/pom.xml index eec0811e0b4..bd8239ab4e3 100644 --- a/repo/repo-test-util/pom.xml +++ b/repo/repo-test-util/pom.xml @@ -81,6 +81,10 @@ commons-lang commons-lang + + org.apache.commons + commons-collections4 + commons-configuration commons-configuration diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java index 10c970afcaa..fd8582bd7e6 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java @@ -62,6 +62,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.opends.server.types.Entry; @@ -441,7 +442,7 @@ public static void displayPrismValuesCollection(String message, Collection collection) { System.out.println(OBJECT_TITLE_OUT_PREFIX + message); LOGGER.debug(OBJECT_TITLE_LOG_PREFIX + message); - for (Containerable c : collection) { + for (Containerable c : CollectionUtils.emptyIfNull(collection)) { String s = DebugUtil.debugDump(c.asPrismContainerValue()); System.out.println(s); LOGGER.debug("{}", s); @@ -450,10 +451,22 @@ public static void displayContainerablesCollection(String message, Collection collection) { + System.out.println(OBJECT_TITLE_OUT_PREFIX + message); + LOGGER.debug(OBJECT_TITLE_LOG_PREFIX + message); + for (DebugDumpable c : CollectionUtils.emptyIfNull(collection)) { + String s = DebugUtil.debugDump(c); + System.out.println(s); + LOGGER.debug("{}", s); + System.out.println(OBJECT_LIST_SEPARATOR); + LOGGER.debug(OBJECT_LIST_SEPARATOR); + } + } + public static void displayObjectTypeCollection(String message, Collection collection) { System.out.println(OBJECT_TITLE_OUT_PREFIX + message); LOGGER.debug(OBJECT_TITLE_LOG_PREFIX + message); - for (ObjectType o : collection) { + for (ObjectType o : CollectionUtils.emptyIfNull(collection)) { System.out.println(ObjectTypeUtil.dump(o)); LOGGER.debug(ObjectTypeUtil.dump(o)); System.out.println(OBJECT_LIST_SEPARATOR); diff --git a/samples/certification/sod/a-test-2a.xml b/samples/certification/sod/a-test-2a.xml index 9c4e88745a7..880bd173b17 100644 --- a/samples/certification/sod/a-test-2a.xml +++ b/samples/certification/sod/a-test-2a.xml @@ -19,16 +19,20 @@ xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3" xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3"> a-test-2a - Exclusive with test-2b + Exclusive with test-2b/2c + + + + \ No newline at end of file diff --git a/samples/certification/sod/a-test-2b.xml b/samples/certification/sod/a-test-2b.xml index 22359ff6881..48324f3f86d 100644 --- a/samples/certification/sod/a-test-2b.xml +++ b/samples/certification/sod/a-test-2b.xml @@ -19,13 +19,16 @@ xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3" xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3"> a-test-2b - Exclusive with test-2a + Exclusive with test-2a/2c + + + diff --git a/samples/certification/sod/a-test-2c.xml b/samples/certification/sod/a-test-2c.xml new file mode 100644 index 00000000000..48671e42b2f --- /dev/null +++ b/samples/certification/sod/a-test-2c.xml @@ -0,0 +1,37 @@ + + + + a-test-2c + Exclusive with test-2a/2b + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/certification/sod/sod-certification.xml b/samples/certification/sod/sod-certification.xml index 189a1e97704..1b1444ee65a 100644 --- a/samples/certification/sod/sod-certification.xml +++ b/samples/certification/sod/sod-certification.xml @@ -32,7 +32,7 @@ 1 P14D - + acceptedIfNotDenied accept diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestStrings.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestStrings.java index d1ef3686c3b..8d86262eb67 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestStrings.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestStrings.java @@ -42,7 +42,6 @@ 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.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.wf.api.WorkflowConstants; @@ -57,7 +56,6 @@ import javax.xml.namespace.QName; import java.io.File; -import java.io.FileNotFoundException; import java.util.*; import java.util.stream.Collectors; @@ -162,7 +160,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti .asItemDeltas(), initResult); // we prefer running trigger scanner by hand - resetTriggerTask(initResult); + resetTriggerTask(TASK_TRIGGER_SCANNER_OID, TASK_TRIGGER_SCANNER_FILE, initResult); // and we don't need validity scanner taskManager.suspendAndDeleteTasks(Collections.singletonList(TASK_VALIDITY_SCANNER_OID), 60000L, true, initResult); @@ -202,19 +200,6 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); } - private void resetTriggerTask(OperationResult result) - throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, FileNotFoundException { - taskManager.suspendAndDeleteTasks(Collections.singletonList(TASK_TRIGGER_SCANNER_OID), 60000L, true, result); - importObjectFromFile(TASK_TRIGGER_SCANNER_FILE, result); - taskManager.suspendTasks(Collections.singletonList(TASK_TRIGGER_SCANNER_OID), 60000L, result); - modifySystemObjectInRepo(TaskType.class, TASK_TRIGGER_SCANNER_OID, - DeltaBuilder.deltaFor(TaskType.class, prismContext) - .item(TaskType.F_SCHEDULE).replace() - .asItemDeltas(), - result); - taskManager.resumeTasks(Collections.singleton(TASK_TRIGGER_SCANNER_OID), result); - } - @Override protected PrismObject getDefaultActor() { return userAdministrator; @@ -872,7 +857,7 @@ public void test208SixDaysLater() throws Exception { // GIVEN clock.resetOverride(); - resetTriggerTask(result); + resetTriggerTask(TASK_TRIGGER_SCANNER_OID, TASK_TRIGGER_SCANNER_FILE, result); clock.overrideDuration("P6D"); // WHEN @@ -931,8 +916,8 @@ public void test209EightDaysLater() throws Exception { 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()); - checkOneCompletedOneCancelled(lifecycleMessages); - checkOneCompletedOneCancelled(allocationMessages); + 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"); @@ -962,6 +947,23 @@ private void checkOneCompletedOneCancelled(List lifecycleMessages) { "^Result:"); } + 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: midPoint Administrator (administrator)", // TODO remove later + "Result: REJECTED"); + assertMessage(lifecycleMessages.get(1), null, "Work item has been completed", + "Result: REJECTED"); + } + @Test public void test220FormRoleAssignmentStart() throws Exception { final String TEST_NAME = "test220FormRoleAssignmentStart";