Skip to content

Commit

Permalink
Start implementing new governance authorizations
Browse files Browse the repository at this point in the history
The authorizations like #readOwnCertificationDecisions are now
deprecated. To allow smooth transition, their up-to-date equivalents
will be provided on the fly by AuthorizationMigrator class.

Other changes:
- Implemented "assignee" clause for certification cases and work items.
- Changed the semantics of "assignee" clause to cover not only assignees
of open work items, but all assignees of all work items.
- MidPointPrincipal#getAuthorities now returns unmodifiable collection
(because of safety reasons).
  • Loading branch information
mederly committed Jun 8, 2023
1 parent 51314d3 commit c97e31d
Show file tree
Hide file tree
Showing 35 changed files with 405 additions and 355 deletions.
Expand Up @@ -8,7 +8,6 @@

import java.io.File;
import java.util.Arrays;
import java.util.Collection;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
Expand All @@ -23,16 +22,18 @@
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.security.api.Authorization;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.SecurityUtil;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.InternalsConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringNormalizerConfigurationType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

Expand Down Expand Up @@ -83,12 +84,7 @@ protected SecurityContext provideFakeSecurityContext() throws SchemaException {
prismContext.adopt(userAdministrator);
userAdministrator.setName(new PolyStringType(new PolyString("initAdmin", "initAdmin")));
MidPointPrincipal principal = new MidPointPrincipal(userAdministrator);
AuthorizationType superAutzType = new AuthorizationType();
prismContext.adopt(superAutzType, RoleType.class, RoleType.F_AUTHORIZATION);
superAutzType.getAction().add(AuthorizationConstants.AUTZ_ALL_URL);
Authorization superAutz = new Authorization(superAutzType);
Collection<Authorization> authorities = principal.getAuthorities();
authorities.add(superAutz);
principal.addAuthorization(SecurityUtil.createPrivilegedAuthorization());
Authentication authentication = new PreAuthenticatedAuthenticationToken(principal, null);
securityContext.setAuthentication(authentication);
return securityContext;
Expand Down
Expand Up @@ -19,6 +19,7 @@
import com.evolveum.midpoint.schema.selector.eval.ClauseFilteringContext;
import com.evolveum.midpoint.schema.selector.eval.ClauseMatchingContext;
import com.evolveum.midpoint.schema.selector.eval.SubjectedEvaluationContext.Delegation;
import com.evolveum.midpoint.schema.util.CertCampaignTypeUtil;
import com.evolveum.midpoint.schema.util.cases.CaseTypeUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.*;
Expand Down Expand Up @@ -50,18 +51,15 @@ public boolean matches(@NotNull PrismValue value, @NotNull ClauseMatchingContext
traceNotApplicable(ctx, "has no real value");
return false;
}
List<PrismObject<? extends ObjectType>> assignees = getAssignees(realValue, ctx);
if (assignees.isEmpty()) {
traceNotApplicable(ctx, "there are no assignees");
return false;
}
ClauseMatchingContext nextCtx = ctx.next(Delegation.ASSIGNEE, "a", "assignee");
for (PrismObject<? extends ObjectType> assignee : assignees) {
assert assignee != null;
// FIXME we need to provide enhanced "self" set here
if (selector.matches(assignee.getValue(), nextCtx)) {
traceApplicable(ctx, "assignee matches: %s", assignee);
return true;
var assignees = getAssignees(realValue, ctx);
if (!assignees.isEmpty()) {
ClauseMatchingContext nextCtx = ctx.next(Delegation.ASSIGNEE, "a", "assignee");
for (PrismObject<? extends ObjectType> assignee : assignees) {
assert assignee != null;
if (selector.matches(assignee.getValue(), nextCtx)) {
traceApplicable(ctx, "assignee matches: %s", assignee);
return true;
}
}
}
traceNotApplicable(ctx, "no assignee matches (assignees=%s)", assignees);
Expand All @@ -71,13 +69,13 @@ public boolean matches(@NotNull PrismValue value, @NotNull ClauseMatchingContext
@NotNull
private List<PrismObject<? extends ObjectType>> getAssignees(Object object, @NotNull ClauseMatchingContext ctx) {
List<ObjectReferenceType> assigneeRefs;
if (object instanceof CaseType) {
CaseType aCase = (CaseType) object;
assigneeRefs = CaseTypeUtil.getAllCurrentAssignees(aCase);
} else if (object instanceof AbstractWorkItemType) {
assigneeRefs = ((AbstractWorkItemType) object).getAssigneeRef();
if (object instanceof CaseType aCase) {
assigneeRefs = CaseTypeUtil.getAllAssignees(aCase);
} else if (object instanceof AccessCertificationCaseType aCase) {
assigneeRefs = CertCampaignTypeUtil.getAllAssignees(aCase);
} else if (object instanceof AbstractWorkItemType workItem) {
assigneeRefs = workItem.getAssigneeRef();
} else {
// TODO e.g. cert case
assigneeRefs = List.of();
}

Expand All @@ -99,8 +97,18 @@ public boolean applyFilter(@NotNull ClauseFilteringContext ctx) {
PrismContext.get().queryFor(CaseType.class)
.exists(CaseType.F_WORK_ITEM)
.block()
.item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull()
.and().item(CaseWorkItemType.F_ASSIGNEE_REF)
.item(CaseWorkItemType.F_ASSIGNEE_REF)
.ref(ctx.getSelfOidsArray(Delegation.ASSIGNEE))
.endBlock()
.buildFilter());
return true;
} else if (AccessCertificationCaseType.class.isAssignableFrom(type)) {
addConjunct(
ctx,
PrismContext.get().queryFor(AccessCertificationCaseType.class)
.exists(CaseType.F_WORK_ITEM)
.block()
.item(AccessCertificationWorkItemType.F_ASSIGNEE_REF)
.ref(ctx.getSelfOidsArray(Delegation.ASSIGNEE))
.endBlock()
.buildFilter());
Expand All @@ -113,6 +121,14 @@ public boolean applyFilter(@NotNull ClauseFilteringContext ctx) {
.ref(ctx.getSelfOidsArray(Delegation.ASSIGNEE))
.buildFilter());
return true;
} else if (AccessCertificationWorkItemType.class.isAssignableFrom(type)) {
addConjunct(
ctx,
PrismContext.get().queryFor(AccessCertificationWorkItemType.class)
.item(AccessCertificationWorkItemType.F_ASSIGNEE_REF)
.ref(ctx.getSelfOidsArray(Delegation.ASSIGNEE))
.buildFilter());
return true;
} else {
traceNotApplicable(ctx, "when searching, this clause applies only to cases and work items");
return false;
Expand Down
Expand Up @@ -349,21 +349,32 @@ private static boolean hasNoResponse(AccessCertificationWorkItemType workItem) {
}

// TODO use this also from GUI and maybe notifications
@SuppressWarnings("unused") // used by certification cases report
@SuppressWarnings("unused") // used by certification cases report
public static List<ObjectReferenceType> getCurrentlyAssignedReviewers(PrismContainerValue<AccessCertificationCaseType> pcv) {
AccessCertificationCaseType aCase = pcv.asContainerable();
return getCurrentlyAssignedReviewers(pcv.asContainerable());
}

public @NotNull static List<ObjectReferenceType> getCurrentlyAssignedReviewers(@NotNull AccessCertificationCaseType aCase) {
List<ObjectReferenceType> rv = new ArrayList<>();
for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) {
for (ObjectReferenceType assigneeRef : workItem.getAssigneeRef()) {
if (workItem.getCloseTimestamp() == null
&& java.util.Objects.equals(workItem.getStageNumber(), aCase.getStageNumber())) {
&& Objects.equals(workItem.getStageNumber(), aCase.getStageNumber())) {
rv.add(assigneeRef);
}
}
}
return rv;
}

public @NotNull static List<ObjectReferenceType> getAllAssignees(@NotNull AccessCertificationCaseType aCase) {
List<ObjectReferenceType> rv = new ArrayList<>();
for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) {
rv.addAll(workItem.getAssigneeRef());
}
return rv;
}

@SuppressWarnings("unused") // used by certification cases report
public static Date getLastReviewedOn(PrismContainerValue<AccessCertificationCaseType> pcv) {
return getReviewedTimestamp(pcv.asContainerable().getWorkItem());
Expand Down
Expand Up @@ -94,6 +94,14 @@ public static List<ObjectReferenceType> getAllCurrentAssignees(CaseType aCase) {
return rv;
}

public static List<ObjectReferenceType> getAllAssignees(@NotNull CaseType aCase) {
List<ObjectReferenceType> rv = new ArrayList<>();
for (CaseWorkItemType workItem : aCase.getWorkItem()) {
rv.addAll(workItem.getAssigneeRef());
}
return rv;
}

public static boolean approvalSchemaExists(CaseType aCase) {
return aCase != null
&& aCase.getApprovalContext() != null
Expand Down
Expand Up @@ -1205,9 +1205,9 @@ private void addFakeAuthorization(MidPointPrincipal principal) {
return;
}
if (principal.getAuthorities().isEmpty()) {
AuthorizationType authorizationBean = new AuthorizationType();
authorizationBean.getAction().add("FAKE");
principal.getAuthorities().add(new Authorization(authorizationBean));
principal.addAuthorization(
new Authorization(
new AuthorizationType().action("FAKE")));
}
}

Expand Down
Expand Up @@ -336,9 +336,15 @@ public void startRemediation(String campaignOid, Task task, OperationResult pare
}

@Override
public List<AccessCertificationWorkItemType> searchOpenWorkItems(ObjectQuery baseWorkItemsQuery, boolean notDecidedOnly,
boolean allItems, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, SecurityViolationException, ExpressionEvaluationException, CommunicationException, ConfigurationException {
public List<AccessCertificationWorkItemType> searchOpenWorkItems(
ObjectQuery baseWorkItemsQuery,
boolean notDecidedOnly,
boolean allItems,
Collection<SelectorOptions<GetOperationOptions>> options,
Task task,
OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, SecurityViolationException, ExpressionEvaluationException,
CommunicationException, ConfigurationException {

OperationResult result = parentResult.createSubresult(OPERATION_SEARCH_OPEN_WORK_ITEMS);
try {
Expand Down
Expand Up @@ -7,6 +7,7 @@

package com.evolveum.midpoint.certification.test;

import com.evolveum.midpoint.cases.api.util.QueryUtils;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
Expand All @@ -15,13 +16,16 @@
import com.evolveum.midpoint.security.api.SecurityUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Test;

import java.util.*;
import java.util.function.Consumer;

import static com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractWorkItemType.F_CLOSE_TIMESTAMP;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignStateType.CLOSED;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignStateType.IN_REMEDIATION;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationResponseType.*;
Expand Down Expand Up @@ -211,81 +215,76 @@ public void test030SearchAllCases() throws Exception {
public void test050SearchDecisionsAdministrator() throws Exception {
// GIVEN
login(userAdministrator.asPrismObject());

// Expected cases - phase 1:
//
// COO-Dummy: administrator
// COO-DummyBlack: administrator
// COO-Superuser: administrator

executeWorkItemsSearchTest(workItems -> {
display("workItems", workItems);
assertEquals("Wrong number of certification work items", 3, workItems.size());
checkWorkItemSanity(workItems, ROLE_COO_OID, RESOURCE_DUMMY_OID, roleCoo);
checkWorkItemSanity(workItems, ROLE_COO_OID, RESOURCE_DUMMY_BLACK_OID, roleCoo);
checkWorkItemSanity(workItems, ROLE_COO_OID, ROLE_SUPERUSER_OID, roleCoo);
});
}

private void executeWorkItemsSearchTest(Consumer<Collection<AccessCertificationWorkItemType>> workItemsChecker)
throws CommonException {
Task task = getTestTask();
OperationResult result = task.getResult();

// WHEN
when();
when("searching for work items via certification service");
List<AccessCertificationWorkItemType> workItems =
queryHelper.searchOpenWorkItems(null, SecurityUtil.getPrincipal(), false, null, result);

/* Expected cases - phase 1:
COO-Dummy: administrator
COO-DummyBlack: administrator
COO-Superuser: administrator
*/

// THEN
then();
result.computeStatus();
TestUtil.assertSuccess(result);

display("workItems", workItems);
assertEquals("Wrong number of certification work items", 3, workItems.size());
checkWorkItemSanity(workItems, ROLE_COO_OID, RESOURCE_DUMMY_OID, roleCoo);
checkWorkItemSanity(workItems, ROLE_COO_OID, RESOURCE_DUMMY_BLACK_OID, roleCoo);
checkWorkItemSanity(workItems, ROLE_COO_OID, ROLE_SUPERUSER_OID, roleCoo);
certificationService.searchOpenWorkItems(null, false, null, task, result);

then("result is OK");
assertSuccess(result);
workItemsChecker.accept(workItems);

when("searching for work items via model API");
List<AccessCertificationWorkItemType> workItems2 =
modelService.searchContainers(
AccessCertificationWorkItemType.class,
QueryUtils.filterForCertificationAssignees(
prismContext.queryFor(AccessCertificationWorkItemType.class),
SecurityUtil.getPrincipalRequired())
.and().item(F_CLOSE_TIMESTAMP).isNull()
.build(),
null, task, result);

then("result is OK");
result.recomputeStatus();
assertSuccess(result);
workItemsChecker.accept(workItems2);
}

@Test
public void test051SearchDecisionsElaine() throws Exception {
// GIVEN
login(userElaine.asPrismObject());
Task task = getTestTask();
OperationResult result = task.getResult();

// WHEN
when();
List<AccessCertificationWorkItemType> workItems =
queryHelper.searchOpenWorkItems(null, SecurityUtil.getPrincipal(), false, null, result);

/* Expected cases - phase 1:

CEO-Dummy: elaine
*/

// THEN
then();
result.computeStatus();
TestUtil.assertSuccess(result);
// Expected cases - phase 1:
//
// CEO-Dummy: elaine

display("caseList", workItems);
assertEquals("Wrong number of work items", 1, workItems.size());
checkWorkItemSanity(workItems, ROLE_CEO_OID, RESOURCE_DUMMY_OID, roleCeo);
executeWorkItemsSearchTest(workItems -> {
display("workItems", workItems);
assertEquals("Wrong number of work items", 1, workItems.size());
checkWorkItemSanity(workItems, ROLE_CEO_OID, RESOURCE_DUMMY_OID, roleCeo); });
}

@Test
public void test052SearchDecisionsJack() throws Exception {
// GIVEN
login(userJack.asPrismObject());
Task task = getTestTask();
OperationResult result = task.getResult();

// WHEN
when();
List<AccessCertificationWorkItemType> workItems =
queryHelper.searchOpenWorkItems(null, SecurityUtil.getPrincipal(), false, null, result);

/* Expected cases - phase 1: NONE */

// THEN
then();
result.computeStatus();
TestUtil.assertSuccess(result);
// Expected cases - phase 1: NONE

display("workItems", workItems);
assertEquals("Wrong number of certification work items", 0, workItems.size());
executeWorkItemsSearchTest(workItems -> {
display("workItems", workItems);
assertEquals("Wrong number of certification work items", 0, workItems.size());
});
}

/*
Expand Down

0 comments on commit c97e31d

Please sign in to comment.