Skip to content

Commit

Permalink
Fix visibility of approval buttons for attorney
Browse files Browse the repository at this point in the history
The attorney feature was fixed recently but with the exception
of Approve/Reject/Forward buttons visibility. This is implemented
in this commit, along with some tests for basic back-end attorney
approvals.

Also:
- eliminating needless execution task run when no work items were
approved
- created convenience methods runUnderPowerOfAttorney and
runUnderPowerOfAttorneyChecked in ModelInteractionService
and similar method in WebComponentUtil

This resolves MID-6225.
  • Loading branch information
mederly committed Apr 17, 2020
1 parent 09829ff commit c406971
Show file tree
Hide file tree
Showing 42 changed files with 1,534 additions and 282 deletions.
Expand Up @@ -4134,6 +4134,14 @@ public static void dropPowerOfAttorneyIfRequested(OperationResult result, PrismO
}
}

public static <T> T runUnderPowerOfAttorneyIfNeeded(CheckedProducer<T> producer, PrismObject<? extends FocusType> powerDonor,
PageBase pageBase, Task task, OperationResult result) throws CommonException {
if (powerDonor != null) {
return pageBase.getModelInteractionService().runUnderPowerOfAttorneyChecked(producer, powerDonor, task, result);
} else {
return producer.get();
}
}

@NotNull
public static List<SceneDto> computeChangesCategorizationList(ChangesByState changesByState, ObjectReferenceType objectRef,
Expand Down
Expand Up @@ -84,7 +84,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) {

}
};
workItemApproveButton.add(new VisibleBehaviour(() -> isApproveRejectButtonVisible()));
workItemApproveButton.add(new VisibleBehaviour(this::isApproveRejectButtonVisible));
workItemApproveButton.setOutputMarkupId(true);
actionButtonsContainer.add(workItemApproveButton);

Expand All @@ -100,7 +100,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) {
}
};
workItemRejectButton.setOutputMarkupId(true);
workItemRejectButton.add(new VisibleBehaviour(() -> isApproveRejectButtonVisible()));
workItemRejectButton.add(new VisibleBehaviour(this::isApproveRejectButtonVisible));
actionButtonsContainer.add(workItemRejectButton);

AjaxButton workItemForwardButton = new AjaxButton(ID_WORK_ITEM_FORWARD_BUTTON,
Expand All @@ -113,7 +113,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) {
}
};
workItemForwardButton.setOutputMarkupId(true);
workItemForwardButton.add(new VisibleBehaviour(() -> isForwardButtonVisible()));
workItemForwardButton.add(new VisibleBehaviour(this::isForwardButtonVisible));
actionButtonsContainer.add(workItemForwardButton);

AjaxButton workItemClaimButton = new AjaxButton(ID_WORK_ITEM_CLAIM_BUTTON,
Expand Down Expand Up @@ -240,30 +240,37 @@ public String getObject() {
}

private boolean isApproveRejectButtonVisible() {
boolean isAuthorized = false;
if (CaseWorkItemUtil.isCaseWorkItemClosed(getModelObject()) ||
CaseWorkItemUtil.isWorkItemClaimable(getModelObject())) {
return false; // checking separately to avoid needless authorization checking
}
try {
OperationResult result = new OperationResult(OPERATION_CHECK_SUBMIT_ACTION_AUTHORIZATION);
Task task = getPageBase().createSimpleTask(OPERATION_CHECK_SUBMIT_ACTION_AUTHORIZATION);
isAuthorized = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result);
return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() ->
getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result),
getPowerDonor(), getPageBase(), task, result);
} catch (Exception ex) {
LOGGER.error("Cannot check current user authorization to submit work item: {}", ex.getLocalizedMessage(), ex);
return false;
}
return CaseWorkItemUtil.isCaseWorkItemNotClosed(getModelObject()) &&
!CaseWorkItemUtil.isWorkItemClaimable(getModelObject()) && isAuthorized;

}

private boolean isForwardButtonVisible() {
boolean isAuthorized = false;
if (CaseWorkItemUtil.isCaseWorkItemClosed(getModelObject()) ||
CaseWorkItemUtil.isWorkItemClaimable(getModelObject())) {
return false; // checking separately to avoid needless authorization checking
}
try {
OperationResult result = new OperationResult(OPERATION_CHECK_DELEGATE_AUTHORIZATION);
Task task = getPageBase().createSimpleTask(OPERATION_CHECK_DELEGATE_AUTHORIZATION);
isAuthorized = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result);
return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() ->
getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result),
getPowerDonor(), getPageBase(), task, result);
} catch (Exception ex) {
LOGGER.error("Cannot check current user authorization to submit work item: {}", ex.getLocalizedMessage(), ex);
return false;
}
return CaseWorkItemUtil.isCaseWorkItemNotClosed(getModelObject()) &&
!CaseWorkItemUtil.isWorkItemClaimable(getModelObject()) && isAuthorized;
}

private boolean isClaimButtonVisible() {
Expand Down
Expand Up @@ -215,7 +215,12 @@ private void initLayout(){
summaryPanel.setOutputMarkupId(true);
add(summaryPanel);

WorkItemDetailsPanel workItemDetailsPanel = new WorkItemDetailsPanel(ID_WORK_ITEM_DETAILS, caseWorkItemModel);
WorkItemDetailsPanel workItemDetailsPanel = new WorkItemDetailsPanel(ID_WORK_ITEM_DETAILS, caseWorkItemModel) {
@Override
protected PrismObject<? extends FocusType> getPowerDonor() {
return PageCaseWorkItem.this.getPowerDonor();
}
};
workItemDetailsPanel.setOutputMarkupId(true);
add(workItemDetailsPanel);

Expand Down
Expand Up @@ -19,6 +19,7 @@
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ApprovalContextUtil;
import com.evolveum.midpoint.schema.util.CaseTypeUtil;
import com.evolveum.midpoint.schema.util.WorkItemTypeUtil;
Expand All @@ -34,7 +35,6 @@
import com.evolveum.midpoint.web.component.prism.show.ScenePanel;
import com.evolveum.midpoint.web.component.util.VisibleBehaviour;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.admin.cases.ManualCaseTabPanel;
import com.evolveum.midpoint.web.page.admin.cases.PageCaseWorkItem;
import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnBlurAjaxFormUpdatingBehaviour;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
Expand All @@ -55,17 +55,15 @@
/**
* Created by honchar
*/
public class WorkItemDetailsPanel extends BasePanel<CaseWorkItemType>{
public class WorkItemDetailsPanel extends BasePanel<CaseWorkItemType> {
private static final long serialVersionUID = 1L;

private static final String DOT_CLASS = WorkItemDetailsPanel.class.getName() + ".";
private static final Trace LOGGER = TraceManager.getTrace(WorkItemDetailsPanel.class);
private static final String OPERATION_PREPARE_DELTA_VISUALIZATION = DOT_CLASS + "prepareDeltaVisualization";
private static final String OPERATION_LOAD_CUSTOM_FORM = DOT_CLASS + "loadCustomForm";
private static final String OPERATION_LOAD_CASE_FOCUS_OBJECT = DOT_CLASS + "loadCaseFocusObject";
private static final String OPERATION_CHECK_SUBMIT_AUTHORIZATION = DOT_CLASS + "checkApproveRejectAuthorization";
private static final String OPERATION_CHECK_DELEGATE_AUTHORIZATION = DOT_CLASS + "checkDelegateAuthorization";
private static final String OPERATION_CHECK_CLAIM_AUTHORIZATION = DOT_CLASS + "checkClaimAuthorization";
private static final String OPERATION_CHECK_ACTIONS_AUTHORIZATION = DOT_CLASS + "checkActionsAuthorization";

private static final String ID_DISPLAY_NAME_PANEL = "displayNamePanel";
private static final String ID_REQUESTED_BY = "requestedBy";
Expand Down Expand Up @@ -290,22 +288,25 @@ public String getObject() {

}

private boolean isAuthorizedForActions(){
Task checkApproveRejectAuthTask = getPageBase().createSimpleTask(OPERATION_CHECK_SUBMIT_AUTHORIZATION);
Task checkDelegateAuthTask = getPageBase().createSimpleTask(OPERATION_CHECK_DELEGATE_AUTHORIZATION);
private boolean isAuthorizedForActions() {
Task task = getPageBase().createSimpleTask(OPERATION_CHECK_ACTIONS_AUTHORIZATION);
OperationResult result = task.getResult();
try {
boolean isAuthorizedToSubmit = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(),
checkApproveRejectAuthTask, checkApproveRejectAuthTask.getResult());
boolean isAuthorizedToDelegate = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(),
checkDelegateAuthTask, checkDelegateAuthTask.getResult());
boolean isAuthorizedToClaim = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToClaim(getModelObject());
return isAuthorizedToSubmit || isAuthorizedToClaim || isAuthorizedToDelegate;
} catch (Exception ex){
return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() ->
getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result) ||
getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result) ||
getPageBase().getWorkflowManager().isCurrentUserAuthorizedToClaim(getModelObject()),
getPowerDonor(), getPageBase(), task, result);
} catch (Exception ex) {
LOGGER.error("Unable to check user authorization for workitem actions: {}", ex.getLocalizedMessage());
}
return false;
}

protected PrismObject<? extends FocusType> getPowerDonor() {
return null;
}

// Expects that we deal with primary changes of the focus (i.e. not of projections)
// Beware: returns the full object; regardless of the security settings
public ObjectType getCaseFocusObject(CaseType caseType) {
Expand Down
Expand Up @@ -323,8 +323,8 @@ public <T extends ObjectType, O extends ObjectType, F> F computeSecurityFilter(M
}

@Override
public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal,
String attorneyAuthorizationAction, PrismObject<UserType> donor, Task task,
public <F extends FocusType> MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal,
String attorneyAuthorizationAction, PrismObject<F> donor, Task task,
OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException,
CommunicationException, ConfigurationException, SecurityViolationException {
Expand Down
Expand Up @@ -120,6 +120,13 @@ public static void assertReferenceValues(PrismReference ref, String... oids) {
}
}

public static void assertReferenceValues(List<? extends Referencable> refs, String... oids) {
assert oids.length == refs.size() : "Wrong number of values in "+refs+"; expected "+oids.length+" but was "+refs.size();
for (String oid: oids) {
assertReferenceValue(refs, oid);
}
}

public static void assertReferenceValue(PrismReference ref, String oid) {
for (PrismReferenceValue val: ref.getValues()) {
if (oid.equals(val.getOid())) {
Expand All @@ -129,6 +136,15 @@ public static void assertReferenceValue(PrismReference ref, String oid) {
fail("Oid "+oid+" not found in reference "+ref);
}

public static void assertReferenceValue(List<? extends Referencable> refs, String oid) {
for (Referencable ref: refs) {
if (oid.equals(ref.getOid())) {
return;
}
}
fail("Oid "+oid+" not found among references "+refs);
}

public static void assertItems(PrismContainer<?> object, int expectedNumberOfItems) {
List<PrismContainerValue> values = (List)object.getValues();
if (expectedNumberOfItems == 0) {
Expand Down
Expand Up @@ -64,10 +64,14 @@ public static CaseWorkItemType getWorkItem(CaseType aCase, long id) {
return null;
}

public static boolean isCaseWorkItemNotClosed(CaseWorkItemType workItem){
public static boolean isCaseWorkItemNotClosed(CaseWorkItemType workItem) {
return workItem != null && workItem.getCloseTimestamp() == null;
}

public static boolean isCaseWorkItemClosed(CaseWorkItemType workItem) {
return workItem != null && workItem.getCloseTimestamp() != null;
}

public static boolean isWorkItemClaimable(CaseWorkItemType workItem){
return workItem != null && (workItem.getOriginalAssigneeRef() == null || StringUtils.isEmpty(workItem.getOriginalAssigneeRef().getOid()))
&& !doesAssigneeExist(workItem) && CollectionUtils.isNotEmpty(workItem.getCandidateRef());
Expand Down
Expand Up @@ -6,6 +6,7 @@
*/
package com.evolveum.midpoint.test.util;

import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.AssertJUnit.*;

import java.util.ArrayList;
Expand Down Expand Up @@ -356,4 +357,30 @@ public static void assertInstanceOf(String message, Object object, Class<?> expe
private static PrismContext getPrismContext() {
return TestSpringContextHolder.getPrismContext();
}

public static void assertThatReferenceMatches(ObjectReferenceType ref, String desc, String expectedOid, QName expectedType) {
assertThat(ref).as(desc).isNotNull();
assertThat(ref.getOid()).as(desc + ".oid").isEqualTo(expectedOid);
assertThatTypeMatches(ref.getType(), desc + ".type", expectedType);
}

// here because of AssertJ dependency (consider moving)
public static void assertThatTypeMatches(QName actualType, String desc, QName expectedType) {
assertThat(actualType).as(desc)
.matches(t -> QNameUtil.match(t, expectedType), "matches " + expectedType);
}

// here because of AssertJ dependency (consider moving)
public static void assertUriMatches(String current, String desc, QName expected) {
assertThat(current).as(desc)
.isNotNull()
.matches(s -> QNameUtil.match(QNameUtil.uriToQName(s, true), expected), "is " + expected);
}

// here because of AssertJ dependency (consider moving)
public static void assertUriMatches(String current, String desc, String expected) {
assertThat(current).as(desc)
.isNotNull()
.matches(s -> QNameUtil.matchUri(s, expected), "is " + expected);
}
}
Expand Up @@ -27,7 +27,10 @@
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.enforcer.api.ItemSecurityConstraints;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.CheckedProducer;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.Producer;
import com.evolveum.midpoint.util.annotation.Experimental;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ExecuteCredentialResetRequestType;
Expand Down Expand Up @@ -151,7 +154,7 @@ <F extends ObjectType> ModelContext<F> previewChanges(
<H extends AssignmentHolderType, R extends AbstractRoleType> RoleSelectionSpecification getAssignableRoleSpecification(PrismObject<H> assignmentHolder, Class<R> targetType, int assignmentOrder, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException;

/**
* Returns filter for lookup of donors or power of attorney. The donors are the users that have granted
* Returns filter for lookup of donors of power of attorney. The donors are the users that have granted
* the power of attorney to the currently logged-in user.
*
* TODO: authorization limitations
Expand Down Expand Up @@ -352,12 +355,20 @@ List<ObjectReferenceType> getDeputyAssignees(ObjectReferenceType assignee, QName
*/
ActivationStatusType getAssignmentEffectiveStatus(String lifecycleStatus, ActivationType activationType);

MidPointPrincipal assumePowerOfAttorney(PrismObject<UserType> donor, Task task, OperationResult result)
MidPointPrincipal assumePowerOfAttorney(PrismObject<? extends FocusType> donor, Task task, OperationResult result)
throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException;

MidPointPrincipal dropPowerOfAttorney(Task task, OperationResult result)
throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException;

<T> T runUnderPowerOfAttorney(Producer<T> producer, PrismObject<? extends FocusType> donor, Task task, OperationResult result)
throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException;

default <T> T runUnderPowerOfAttorneyChecked(CheckedProducer<T> producer, PrismObject<? extends FocusType> donor, Task task, OperationResult result)
throws CommonException {
return MiscUtil.runChecked((p) -> runUnderPowerOfAttorney(p, donor, task, result), producer);
}

// Maybe a bit of hack: used to deduplicate processing of localizable message templates
@NotNull
LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template,
Expand Down

0 comments on commit c406971

Please sign in to comment.