Skip to content

Commit

Permalink
Add bulk-3#xxx authorizations
Browse files Browse the repository at this point in the history
The model-3#executeBulkAction (a recent replacement of #executeScript)
was not bad, but even better is providing authorizations for individual
bulk actions: add, delete, enable, disable, ... This way, the admin
is able to fine-tune authorizations to cover exactly what a user
needs to have. Moreover, it is well-aligned to similar namespaces:
gui-3 and rest-3.

Other changes:
- Removed ScriptExecutionException. Bulk actions executor now throws
standard exceptions (SchemaException, ObjectNotFoundException, ...).
- The "search" instruction is now an action; although it cannot
be called dynamically because of a conflict between "type" property
in <action> and in <search>, it is really something that we want
to allow/deny in expression profiles and by authorizations.
- Improved the API by introducing BulkActionExecutionOptions.
- Created BulkAction enum that lists all known actions.
  • Loading branch information
mederly committed Aug 24, 2023
1 parent 96b6795 commit 3c50c95
Show file tree
Hide file tree
Showing 70 changed files with 1,178 additions and 1,027 deletions.
Expand Up @@ -15,6 +15,8 @@

import com.evolveum.midpoint.gui.impl.page.admin.resource.component.ResourceTaskCreator;

import com.evolveum.midpoint.model.api.BulkAction;

import jakarta.xml.bind.JAXBElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -128,14 +130,14 @@ public String getOperationKey() {
* of a given abstract role.
*/
void createAndSubmitTask(Task task, OperationResult result) throws CommonException {
checkBulkActionsAuthorization(task, result);
authorizeBulkActionExecution(BulkAction.UNASSIGN, task, result);
submitTask(
createUnassignMembersActivity(), task, result);
}

/** Creates the member unassignment task. */
@NotNull PrismObject<TaskType> createTask(Task task, OperationResult result) throws CommonException {
checkBulkActionsAuthorization(task, result);
authorizeBulkActionExecution(BulkAction.UNASSIGN, task, result);
return createTask(
createUnassignMembersActivity(), task, result);
}
Expand Down Expand Up @@ -196,7 +198,7 @@ public String getOperationKey() {

/** Returns task OID */
public String createAndSubmitTask(Task task, OperationResult result) throws CommonException {
checkBulkActionsAuthorization(task, result);
authorizeBulkActionExecution(BulkAction.ASSIGN, task, result);
return submitTask(
createActivity(), task, result);
}
Expand Down Expand Up @@ -233,7 +235,7 @@ public String getOperationKey() {
}

void createAndSubmitTask(Task task, OperationResult result) throws CommonException {
checkBulkActionsAuthorization(task, result);
authorizeBulkActionExecution(BulkAction.DELETE, task, result);
submitTask(
createActivity(), task, result);
}
Expand All @@ -253,7 +255,7 @@ private ActivityDefinitionType createActivity() throws SchemaException {
/** "Recompute members" operation. */
static class Recompute extends Scoped {

public Recompute(
Recompute(
@NotNull AbstractRoleType targetAbstractRole, @NotNull QName memberType, @NotNull ObjectQuery memberQuery,
@NotNull QueryScope scope, @NotNull PageBase pageBase) {
super(targetAbstractRole, memberType, memberQuery, scope, pageBase);
Expand All @@ -266,14 +268,14 @@ public String getOperationKey() {

/** Creates and submits a task that recomputes the role members. */
void createAndSubmitTask(@NotNull Task task, @NotNull OperationResult result) throws CommonException {
checkRecomputationAuthorization(task, result);
authorizeRecomputation(task, result);
submitTask(
createActivity(), task, result);
}

/** Creates a task that recomputes the role members. */
PrismObject<TaskType> createTask(@NotNull Task task, @NotNull OperationResult result) throws CommonException {
checkRecomputationAuthorization(task, result);
authorizeRecomputation(task, result);
return createTask(
createActivity(), task, result);
}
Expand Down Expand Up @@ -331,12 +333,13 @@ private ActivitySubmissionOptions createSubmissionOptions() {
getOperationName())));
}

void checkBulkActionsAuthorization(Task task, OperationResult result) throws CommonException {
pageBase.getSecurityEnforcer().authorize(
ModelAuthorizationAction.EXECUTE_BULK_ACTIONS.getUrl(), task, result);
void authorizeBulkActionExecution(@NotNull BulkAction action, Task task, OperationResult result) throws CommonException {
// Note that the task itself will require both REQUEST and EXECUTION phase authorization due to current limitations.
pageBase.getModelInteractionService().authorizeBulkActionExecution(
action, AuthorizationPhaseType.EXECUTION, task, result);
}

void checkRecomputationAuthorization(@NotNull Task task, @NotNull OperationResult result) throws CommonException {
void authorizeRecomputation(@NotNull Task task, @NotNull OperationResult result) throws CommonException {
pageBase.getSecurityEnforcer().authorize(
ModelAuthorizationAction.RECOMPUTE.getUrl(), task, result);
}
Expand Down
Expand Up @@ -9,6 +9,7 @@
import java.io.File;
import java.util.Arrays;

import com.evolveum.midpoint.model.api.BulkActionExecutionOptions;
import com.evolveum.midpoint.schema.config.ConfigurationItemOrigin;
import com.evolveum.midpoint.schema.config.ExecuteScriptConfigItem;
import com.evolveum.midpoint.schema.util.ScriptingBeansUtil;
Expand Down Expand Up @@ -161,7 +162,8 @@ private boolean executeScript(PrismProperty<Object> expression, File file, Task
// TODO or should we create some "fully trusted origin"?
ConfigurationItemOrigin.external(SchemaConstants.CHANNEL_INIT_URI)),
VariablesMap.emptyMap(),
false,
BulkActionExecutionOptions.create()
.withExecutionPhase(),
task,
result);
result.recordSuccess();
Expand Down
Expand Up @@ -9,6 +9,7 @@
import com.evolveum.midpoint.authentication.api.util.AuthConstants;
import com.evolveum.midpoint.authentication.api.authorization.Url;

import com.evolveum.midpoint.model.api.BulkActionExecutionOptions;
import com.evolveum.midpoint.schema.config.ConfigurationItemOrigin;
import com.evolveum.midpoint.schema.config.ExecuteScriptConfigItem;
import com.evolveum.midpoint.schema.util.ScriptingBeansUtil;
Expand All @@ -21,7 +22,6 @@
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;

import com.evolveum.midpoint.util.exception.ScriptExecutionException;
import com.evolveum.midpoint.model.api.BulkActionExecutionResult;
import com.evolveum.midpoint.schema.expression.VariablesMap;
import com.evolveum.midpoint.schema.result.OperationResult;
Expand Down Expand Up @@ -152,7 +152,7 @@ private void startPerformed(AjaxRequestTarget target) {
getBulkActionsService().executeBulkAction(
ExecuteScriptConfigItem.of(typed, ConfigurationItemOrigin.user()),
VariablesMap.emptyMap(),
false,
BulkActionExecutionOptions.create(),
task,
result);
result.recordStatus(
Expand All @@ -162,8 +162,7 @@ private void startPerformed(AjaxRequestTarget target) {
executionResult.getDataOutput().size()).getString());
result.addReturn("console", executionResult.getConsoleOutput());
result.addArbitraryObjectCollectionAsReturn("data", executionResult.getDataOutput());
} catch (ScriptExecutionException | SchemaException | SecurityViolationException | ExpressionEvaluationException
| ObjectNotFoundException | CommunicationException | ConfigurationException | ClassCastException e) {
} catch (Exception e) {
result.recordFatalError(createStringResource("PageBulkAction.message.startPerformed.fatalError.execute").getString(), e);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't execute bulk action", e);
}
Expand Down
Expand Up @@ -8,6 +8,7 @@

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.*;

/**
* Classes implementing this interface are used to handle arbitrary objects (not always {@link PrismObject} instances),
Expand Down
Expand Up @@ -8,6 +8,7 @@

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;

/**
Expand Down
Expand Up @@ -11,22 +11,20 @@
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.evolveum.midpoint.xml.ns._public.model.scripting_3.*;

import jakarta.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.util.MiscUtil;

import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType;

import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.beanutils.PropertyUtils;

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.model.scripting_3.ActionExpressionType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ObjectFactory;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -85,15 +83,15 @@ private static <T extends ScriptingExpressionType> JAXBElement<T> toJaxbElement(
expression);
}

public static @NotNull String getActionType(@NotNull ActionExpressionType action) {
if (action.getType() != null) {
return action.getType();
public static @NotNull String getActionType(@NotNull AbstractActionExpressionType action) {
if (action instanceof ActionExpressionType dynamic && dynamic.getType() != null) {
return dynamic.getType();
} else {
return toJaxbElement(action).getName().getLocalPart();
}
}

public static <T> T getBeanPropertyValue(ActionExpressionType action, String propertyName, Class<T> clazz)
public static <T> T getBeanPropertyValue(AbstractActionExpressionType action, String propertyName, Class<T> clazz)
throws SchemaException {
try {
try {
Expand All @@ -114,7 +112,7 @@ public static <T> T getBeanPropertyValue(ActionExpressionType action, String pro
}
}

private static Boolean getBeanBooleanPropertyValue(ActionExpressionType action, String propertyName)
private static Boolean getBeanBooleanPropertyValue(AbstractActionExpressionType action, String propertyName)
throws IllegalAccessException, InvocationTargetException, SchemaException {
try {
String methodName = "is" + StringUtils.capitalize(propertyName);
Expand Down
Expand Up @@ -202,7 +202,7 @@
<xsd:sequence>
<xsd:element ref="tns:scriptingExpression" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="list" type="xsd:boolean" /> <!-- requires there are no elements other than 'expression' -->
<xsd:attribute name="list" type="xsd:boolean" /> <!-- requires there are no elements other than 'scriptingExpression' -->
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
Expand All @@ -214,14 +214,15 @@
</xsd:annotation>
</xsd:element>

<!-- Should be perhaps called SearchActionExpressionType, but keeping this one for compatibility reasons. -->
<xsd:complexType name="SearchExpressionType">
<xsd:annotation>
<xsd:documentation>
Queries the model for objects of a given type, optionally fulfilling given condition.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:ScriptingExpressionType">
<xsd:extension base="tns:AbstractActionExpressionType">
<xsd:sequence>
<xsd:element name="type" type="xsd:QName">
<xsd:annotation>
Expand Down Expand Up @@ -417,14 +418,32 @@
</xsd:annotation>
</xsd:element>

<!-- Should be perhaps called ActionExpressionType (and that one "DynamicActionExpressionType") but keeping
this for compatibility reasons. -->
<xsd:complexType name="AbstractActionExpressionType">
<xsd:annotation>
<xsd:documentation>
Either traditional "dynamic" action (add, modify, ...), or a search, that is considered to be an action since 4.8.
The search cannot be a dynamic action because of the conflict on the "type" property.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.8</a:since> <!-- Introduced so that "search" can be also an action. -->
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:ScriptingExpressionType">
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="ActionExpressionType">
<xsd:annotation>
<xsd:documentation>
Executes a given action (add, modify, delete, enable, disable, assign, ...)
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:ScriptingExpressionType">
<xsd:extension base="tns:AbstractActionExpressionType">
<xsd:sequence>
<xsd:element name="type" type="xsd:string" minOccurs="0">
<xsd:annotation>
Expand Down
Expand Up @@ -14,10 +14,8 @@
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ActionExpressionType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExpressionPipelineType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExpressionSequenceType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.SearchExpressionType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.*;

import org.testng.annotations.Test;

import jakarta.xml.bind.JAXBElement;
Expand Down Expand Up @@ -81,21 +79,21 @@ protected void assertPrismPropertyValueLocal(PrismPropertyValue<ExpressionPipeli
JAXBElement<ExpressionSequenceType> sequenceJaxb1 = (JAXBElement<ExpressionSequenceType>) pipe.getScriptingExpression().get(0);
assertEquals("Wrong element name (1)", SchemaConstants.S_SEQUENCE, sequenceJaxb1.getName());
assertEquals("Wrong element type (1)", ExpressionSequenceType.class, sequenceJaxb1.getValue().getClass());
JAXBElement<SearchExpressionType> searchJaxb1_1 = (JAXBElement<SearchExpressionType>) sequenceJaxb1.getValue().getScriptingExpression().get(0);
var searchJaxb1_1 = (JAXBElement<SearchExpressionType>) sequenceJaxb1.getValue().getScriptingExpression().get(0);
assertEquals("Wrong first element name", SchemaConstants.S_SEARCH, searchJaxb1_1.getName());
assertEquals("Wrong element type (1.1)", SearchExpressionType.class, searchJaxb1_1.getValue().getClass());
assertEquals(new QName("RoleType"), searchJaxb1_1.getValue().getType());
assertNotNull(searchJaxb1_1.getValue().getSearchFilter());
JAXBElement<ActionExpressionType> actionJaxb1_2 = (JAXBElement<ActionExpressionType>) sequenceJaxb1.getValue().getScriptingExpression().get(1);
var actionJaxb1_2 = (JAXBElement<ActionExpressionType>) sequenceJaxb1.getValue().getScriptingExpression().get(1);
assertEquals("Wrong element type (1.2)", ActionExpressionType.class, actionJaxb1_2.getValue().getClass());
assertEquals("log", actionJaxb1_2.getValue().getType());

JAXBElement<ExpressionSequenceType> sequenceJaxb2 = (JAXBElement<ExpressionSequenceType>) pipe.getScriptingExpression().get(1);
assertEquals("Wrong second element name", SchemaConstants.S_SEQUENCE, sequenceJaxb2.getName());
assertEquals("Wrong element type (2)", ExpressionSequenceType.class, sequenceJaxb2.getValue().getClass());
JAXBElement<ActionExpressionType> actionJaxb2_1 = (JAXBElement<ActionExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(0);
JAXBElement<ActionExpressionType> actionJaxb2_2 = (JAXBElement<ActionExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(1);
JAXBElement<SearchExpressionType> searchJaxb2_3 = (JAXBElement<SearchExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(2);
var actionJaxb2_1 = (JAXBElement<ActionExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(0);
var actionJaxb2_2 = (JAXBElement<ActionExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(1);
var searchJaxb2_3 = (JAXBElement<SearchExpressionType>) sequenceJaxb2.getValue().getScriptingExpression().get(2);
assertEquals("Wrong element name (2.1)", SchemaConstants.S_ACTION, actionJaxb2_1.getName());
assertEquals("Wrong element type (2.1)", ActionExpressionType.class, actionJaxb2_1.getValue().getClass());
assertEquals("Wrong element name (2.2)", SchemaConstants.S_ACTION, actionJaxb2_2.getName());
Expand Down

0 comments on commit 3c50c95

Please sign in to comment.