Skip to content

Commit

Permalink
Disallow q:any in bulk assign actions
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Mar 10, 2020
1 parent dcdea81 commit a65a98b
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 381 deletions.
Expand Up @@ -6,6 +6,8 @@
*/
package com.evolveum.midpoint.prism;

import com.evolveum.midpoint.prism.path.ItemName;

import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;

import javax.xml.namespace.QName;
Expand Down Expand Up @@ -121,7 +123,7 @@ public class PrismConstants {
public static final QName Q_RELATION = new QName(NS_QUERY, "relation");
public static final QName Q_VALUE = new QName(NS_QUERY, "value");
public static final QName Q_ORDER_BY = new QName(NS_QUERY, "orderBy");
public static final QName Q_ANY = new QName(NS_QUERY, "any");
public static final ItemName Q_ANY = new ItemName(NS_QUERY, "any");

// Path constants
public static final String T_PARENT_LOCAL_PART = "parent";
Expand Down
Expand Up @@ -178,7 +178,7 @@ public ItemPath subPath(int from, int to) {
}
}

public boolean matches(ItemName other) {
public boolean matches(QName other) {
return QNameUtil.match(this, other);
}

Expand Down
Expand Up @@ -35,6 +35,7 @@
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
Expand Down Expand Up @@ -595,12 +596,16 @@ public static String getFirstNonNullString(Object... values) {
}

public static <T> T extractSingleton(Collection<T> collection) {
return extractSingleton(collection, () -> new IllegalArgumentException("Expected a collection with at most one item; got the one with " + collection.size() + " items"));
}

public static <T, E extends Throwable> T extractSingleton(Collection<T> collection, Supplier<E> exceptionSupplier) throws E {
if (collection == null || collection.isEmpty()) {
return null;
} else if (collection.size() == 1) {
return collection.iterator().next();
} else {
throw new IllegalArgumentException("Expected a collection with at most one item; got the one with " + collection.size() + " items");
throw exceptionSupplier.get();
}
}

Expand Down
Expand Up @@ -7,180 +7,72 @@

package com.evolveum.midpoint.model.impl.scripting.actions;

import com.evolveum.midpoint.model.api.ScriptExecutionException;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.RelationRegistry;
import com.evolveum.midpoint.schema.constants.RelationTypes;
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.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.management.relation.RelationType;
import javax.xml.namespace.QName;

/**
* @author mederly
*/
@Component
public class AssignExecutor extends AssignmentOperationsExecutor {

private static final Trace LOGGER = TraceManager.getTrace(AssignExecutor.class);

@Autowired
protected RelationRegistry relationRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// private static final String NAME = "assign";
// private static final String PARAM_RESOURCE = "resource";
// private static final String PARAM_ROLE = "role";
// private static final String PARAM_RELATION = "relation";
import com.evolveum.midpoint.prism.PrismConstants;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.RelationRegistry;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

// @PostConstruct
// public void init() {
// scriptingExpressionEvaluator.registerActionExecutor(NAME, this);
// }
@Component
public class AssignExecutor extends AssignmentOperationsExecutor {

// @Override
// public PipelineData execute(ActionExpressionType expression, PipelineData input, ExecutionContext context, OperationResult globalResult) throws ScriptExecutionException {
//
// ModelExecuteOptions executionOptions = getOptions(expression, input, context, globalResult);
// boolean dryRun = getParamDryRun(expression, input, context, globalResult);
//
// ActionParameterValueType resourceParameterValue = expressionHelper.getArgument(expression.getParameter(), PARAM_RESOURCE, false, false, NAME);
// ActionParameterValueType roleParameterValue = expressionHelper.getArgument(expression.getParameter(), PARAM_ROLE, false, false, NAME);
// Collection<String> relations = expressionHelper.getArgumentValues(expression.getParameter(), PARAM_RELATION, false, false, NAME, input, context, String.class, globalResult);
//
// Collection<ObjectReferenceType> resources;
// try {
// if (resourceParameterValue != null) {
// PipelineData data = expressionHelper
// .evaluateParameter(resourceParameterValue, null, input, context, globalResult);
// resources = data.getDataAsReferences(ResourceType.COMPLEX_TYPE, ResourceType.class, context, globalResult);
// } else {
// resources = null;
// }
// } catch (CommonException e) {
// throw new ScriptExecutionException("Couldn't evaluate '" + PARAM_RESOURCE + "' parameter of a scripting expression: " + e.getMessage(), e);
// }
//
// Collection<ObjectReferenceType> roles;
// try {
// if (roleParameterValue != null) {
// PipelineData data = expressionHelper.evaluateParameter(roleParameterValue, null, input, context, globalResult);
// roles = data.getDataAsReferences(RoleType.COMPLEX_TYPE, AbstractRoleType.class, context, globalResult); // if somebody wants to assign Org, he has to use full reference value (including object type)
// } else {
// roles = null;
// }
// } catch (CommonException e) {
// throw new ScriptExecutionException("Couldn't evaluate '" + PARAM_ROLE + "' parameter of a scripting expression: " + e.getMessage(), e);
// }
//
// if (resources == null && roles == null) {
// throw new ScriptExecutionException("Nothing to assign: neither resource nor role specified");
// }
//
// if (CollectionUtils.isEmpty(resources) && CollectionUtils.isEmpty(roles)) {
// LOGGER.warn("No resources and no roles to assign in a scripting expression");
// context.println("Warning: no resources and no roles to assign"); // TODO some better handling?
// return input;
// }
//
// for (PipelineItem item : input.getData()) {
// PrismValue value = item.getValue();
// OperationResult result = operationsHelper.createActionResult(item, this, context, globalResult);
// context.checkTaskStop();
// if (value instanceof PrismObjectValue && ((PrismObjectValue) value).asObjectable() instanceof FocusType) {
// @SuppressWarnings({"unchecked", "raw"})
// PrismObject<? extends ObjectType> prismObject = ((PrismObjectValue) value).asPrismObject();
// ObjectType objectType = prismObject.asObjectable();
// long started = operationsHelper.recordStart(context, objectType);
// Throwable exception = null;
// try {
// operationsHelper.applyDelta(createDelta(objectType, resources, roles, relations), executionOptions, dryRun, context, result);
// operationsHelper.recordEnd(context, objectType, started, null);
// } catch (Throwable ex) {
// operationsHelper.recordEnd(context, objectType, started, ex);
// exception = processActionException(ex, NAME, value, context);
// }
// context.println((exception != null ? "Attempted to modify " : "Modified ") + prismObject.toString() + optionsSuffix(executionOptions, dryRun) + exceptionSuffix(exception));
// } else {
// //noinspection ThrowableNotThrown
// processActionException(new ScriptExecutionException("Item is not a PrismObject of FocusType"), NAME, value, context);
// }
// operationsHelper.trimAndCloneResult(result, globalResult, context);
// }
// return input; // TODO updated objects?
// }
@Autowired protected RelationRegistry relationRegistry;

@Override
protected String getName() {
return AssignmentOperationsExecutor.ASSIGN_NAME;
}

@Override
protected ObjectDelta<? extends ObjectType> createDelta(AssignmentHolderType objectType, Collection<ObjectReferenceType> resources, Collection<ObjectReferenceType> roles, Collection<String> relations) throws ScriptExecutionException {
protected ObjectDelta<? extends ObjectType> createDelta(AssignmentHolderType object, Collection<ObjectReferenceType> resources,
Collection<ObjectReferenceType> roles, Collection<QName> relationSpecifications) throws SchemaException {

String relation;
QName relationSpecification = MiscUtil.extractSingleton(relationSpecifications,
() -> new IllegalArgumentException("Couldn't use 'relation' as multivalue parameter"));

if (relations == null || relations.isEmpty()) {
QName defaultRelation = prismContext.getDefaultRelation() != null ?
prismContext.getDefaultRelation() : RelationTypes.MEMBER.getRelation();
relation = QNameUtil.qNameToUri(defaultRelation);
} else if (relations.size() > 1) {
throw new IllegalArgumentException("Couldn't use relation as multivalue parameter");
} else {
relation = relations.iterator().next();
if (PrismConstants.Q_ANY.matches(relationSpecification)) {
throw new IllegalArgumentException("Using 'q:any' as relation specification is not allowed");
}

List<AssignmentType> assignments = new ArrayList<>();
List<AssignmentType> assignmentsToAdd = new ArrayList<>();

if (roles != null) {
List<RelationDefinitionType> relationDefinitions = relationRegistry.getRelationDefinitions();
QName matchingRelation = relationDefinitions.stream()
.filter(definition -> prismContext.relationMatches(relationSpecification, definition.getRef()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Relation matching '" + relationSpecification + "' not found"))
.getRef();
for (ObjectReferenceType roleRef : roles) {
AssignmentType assignmentType = new AssignmentType();
RelationDefinitionType foundRelation = null;
for (RelationDefinitionType relationDefinitionType : relationDefinitions) {
if (prismContext.relationMatches(QNameUtil.uriToQName(relation, true),
relationDefinitionType.getRef())) {
foundRelation = relationDefinitionType;
break;
}
}
if(foundRelation == null) {
throw new IllegalArgumentException("Relation " + relation + " not found");
}
roleRef.setRelation(foundRelation.getRef());
assignmentType.setTargetRef(roleRef);
assignments.add(assignmentType);
assignmentsToAdd.add(
new AssignmentType(prismContext)
.targetRef(roleRef.clone()
.relation(matchingRelation)));
}
}

if (resources != null) {
for (ObjectReferenceType resourceRef : resources) {
AssignmentType assignmentType = new AssignmentType();
ConstructionType constructionType = new ConstructionType();
constructionType.setResourceRef(resourceRef);
assignmentType.setConstruction(constructionType);
assignments.add(assignmentType);
assignmentsToAdd.add(
new AssignmentType(prismContext)
.beginConstruction()
.resourceRef(resourceRef) // relation is ignored here
.end());
}
}

ObjectDelta<? extends ObjectType> delta = prismContext.deltaFactory().object()
.createEmptyModifyDelta(objectType.getClass(), objectType.getOid());
try {
delta.addModificationAddContainer(FocusType.F_ASSIGNMENT, assignments.toArray(new AssignmentType[0]));
} catch (SchemaException e) {
throw new ScriptExecutionException("Couldn't prepare modification to add resource/role assignments", e);
}
return delta;
return prismContext.deltaFor(object.getClass())
.item(AssignmentHolderType.F_ASSIGNMENT)
.addRealValues(assignmentsToAdd)
.asObjectDelta(object.getOid());
}
}
Expand Up @@ -16,7 +16,6 @@
import com.evolveum.midpoint.prism.PrismObjectValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.constants.RelationTypes;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.QNameUtil;
Expand All @@ -28,15 +27,15 @@
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ActionExpressionType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ActionParameterValueType;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;

import javax.annotation.PostConstruct;
import javax.xml.namespace.QName;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
*
Expand All @@ -45,11 +44,11 @@ public abstract class AssignmentOperationsExecutor extends BaseActionExecutor {

private static final Trace LOGGER = TraceManager.getTrace(AssignmentOperationsExecutor.class);

protected static final String UNASSIGN_NAME = "unassign";
protected static final String ASSIGN_NAME = "assign";
protected static final String PARAM_RESOURCE = "resource";
protected static final String PARAM_ROLE = "role";
protected static final String PARAM_RELATION = "relation";
static final String UNASSIGN_NAME = "unassign";
static final String ASSIGN_NAME = "assign";
private static final String PARAM_RESOURCE = "resource";
private static final String PARAM_ROLE = "role";
private static final String PARAM_RELATION = "relation";

@PostConstruct
public void init() {
Expand All @@ -66,7 +65,18 @@ public PipelineData execute(ActionExpressionType expression, PipelineData input,

ActionParameterValueType resourceParameterValue = expressionHelper.getArgument(expression.getParameter(), PARAM_RESOURCE, false, false, getName());
ActionParameterValueType roleParameterValue = expressionHelper.getArgument(expression.getParameter(), PARAM_ROLE, false, false, getName());
Collection<String> relations = expressionHelper.getArgumentValues(expression.getParameter(), PARAM_RELATION, false, false, getName(), input, context, String.class, globalResult);
Collection<String> relationSpecificationUris = expressionHelper.getArgumentValues(expression.getParameter(), PARAM_RELATION, false, false, getName(), input, context, String.class, globalResult);

Collection<QName> relationSpecifications;
if (relationSpecificationUris.isEmpty()) {
QName defaultRelation = ObjectUtils.defaultIfNull(prismContext.getDefaultRelation(), RelationTypes.MEMBER.getRelation());
relationSpecifications = Collections.singleton(defaultRelation);
} else {
relationSpecifications = relationSpecificationUris.stream()
.map(uri -> QNameUtil.uriToQName(uri, true))
.collect(Collectors.toSet());
}
assert !relationSpecifications.isEmpty();

Collection<ObjectReferenceType> resources;
try {
Expand Down Expand Up @@ -114,13 +124,13 @@ public PipelineData execute(ActionExpressionType expression, PipelineData input,
long started = operationsHelper.recordStart(context, objectType);
Throwable exception = null;
try {
operationsHelper.applyDelta(createDelta(objectType, resources, roles, relations), executionOptions, dryRun, context, result);
operationsHelper.applyDelta(createDelta(objectType, resources, roles, relationSpecifications), executionOptions, dryRun, context, result);
operationsHelper.recordEnd(context, objectType, started, null);
} catch (Throwable ex) {
operationsHelper.recordEnd(context, objectType, started, ex);
exception = processActionException(ex, getName(), value, context);
}
context.println((exception != null ? "Attempted to modify " : "Modified ") + prismObject.toString() + optionsSuffix(executionOptions, dryRun) + exceptionSuffix(exception));
context.println(createConsoleMessage(prismObject, executionOptions, dryRun, exception));
} else {
//noinspection ThrowableNotThrown
processActionException(new ScriptExecutionException("Item is not a PrismObject of AssignmentHolderType"), getName(), value, context);
Expand All @@ -130,5 +140,13 @@ public PipelineData execute(ActionExpressionType expression, PipelineData input,
return input; // TODO updated objects?
}

protected abstract ObjectDelta<? extends ObjectType> createDelta(AssignmentHolderType object, Collection<ObjectReferenceType> resources, Collection<ObjectReferenceType> roles, Collection<String> relations) throws ScriptExecutionException;
@NotNull
private String createConsoleMessage(PrismObject<? extends ObjectType> object, ModelExecuteOptions executionOptions,
boolean dryRun, Throwable exception) {
return (exception != null ? "Attempted to modify " : "Modified ") + object
+ optionsSuffix(executionOptions, dryRun) + exceptionSuffix(exception);
}

protected abstract ObjectDelta<? extends ObjectType> createDelta(AssignmentHolderType object, Collection<ObjectReferenceType> resources,
Collection<ObjectReferenceType> roles, Collection<QName> relationSpecifications) throws SchemaException;
}

0 comments on commit a65a98b

Please sign in to comment.