Skip to content

Commit

Permalink
Add PoC of "run as task template owner" feature
Browse files Browse the repository at this point in the history
When a task template is instantiated, the identify of the currently
logged-in user is used as the task owner for newly created task. This is
clearly the most safe approach.

However, there can be situations where this restriction is too strong.
One of those is described in MID-6913. Unfortunately, lifting this
restriction in a secure way is not a simple thing, as it requires
deep consideration of the effects on midPoint security model.

This commit provides a proof of concept for the idea of running
tasks created from templates under the identity of the task template
owner.

To enable this feature the following must be done:

1) The feature must be enabled in system configuration by setting
internals/enableRunAsTaskTemplateOwnerAuthorization to true.

2) Any user that need to run tasks under templates owners must have
#runAsTaskTemplateOwner authorization granted.

3) Any task template that allows running under its owner must have
mext:useTaskTemplateOwner extension property set to true.

After these conditions are met, the newly instantiated task is created
with the ownerRef pointing to the template owner. The original user
identity is preserved in mext:taskTemplateExecutionInitiatorRef
extension item. It is the responsibility of the deployer to set up
e.g. custom auditing properties to properly audit this information.

Final note: All of the code is EXPERIMENTAL and, at the same time,
deprecated since its inception. Do not consider any of the code
as something more than a PoC that will disappear sooner or later
from midPoint. I assume it will be replaced by serious approach
in the future.

Unrelated change:
- The custom code execution in execute-script action as well as in
notify action with custom notifier required #all authorization. This
was changed to #executeCustomCode. Actually this is not needed for
the new feature; it is used for testing it. But it looks like a good
idea.
  • Loading branch information
mederly committed Mar 27, 2021
1 parent c869db2 commit 8ddadd1
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 15 deletions.
Expand Up @@ -16,6 +16,7 @@
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.annotation.Experimental;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

/**
Expand Down Expand Up @@ -322,6 +323,16 @@ public abstract class SchemaConstants {
public static final ItemName MODEL_EXTENSION_NOT_UPDATED_SHADOW_DURATION = new ItemName(NS_MODEL_EXTENSION, "notUpdatedShadowsDuration");
public static final ItemName MODEL_EXTENSION_REPORTING_OPTIONS = new ItemName(NS_MODEL_EXTENSION, "reporting");

@Experimental
@Deprecated
public static final ItemName MODEL_EXTENSION_TASK_TEMPLATE_EXECUTION_INITIATOR_REF =
new ItemName(NS_MODEL_EXTENSION, "taskTemplateExecutionInitiatorRef");

@Experimental
@Deprecated
public static final ItemName MODEL_EXTENSION_USE_TASK_TEMPLATE_OWNER =
new ItemName(NS_MODEL_EXTENSION, "useTaskTemplateOwner");

public static final String NS_MODEL_DISABLE_REASON = NS_MODEL + "/disableReason";
public static final String MODEL_DISABLE_REASON_EXPLICIT =
QNameUtil.qNameToUri(new QName(NS_MODEL_DISABLE_REASON, "explicit"));
Expand Down
Expand Up @@ -40,6 +40,8 @@
import java.util.Objects;
import java.util.stream.Collectors;

import static com.evolveum.midpoint.util.MiscUtil.schemaCheck;

import static org.apache.commons.collections4.CollectionUtils.emptyIfNull;

/**
Expand Down Expand Up @@ -982,6 +984,36 @@ public static void recordFetchError(PrismObject<? extends ObjectType> object, Op
object.asObjectable().setFetchResult(resultBean);
}

@Experimental
public static <T> void setExtensionItemValue(PrismObject<?> object, ItemName extensionItemName, T realValue)
throws SchemaException {
PrismContainer<?> extension = object.getOrCreateExtension();
if (realValue == null) {
//noinspection unchecked
Item<?, ?> extensionItem = extension.findItem((ItemPath) extensionItemName, Item.class);
if (extensionItem != null) {
extensionItem.clear();
}
} else {
//noinspection unchecked
Item<?, ?> extensionItem = extension.findOrCreateItem(extensionItemName, Item.class);
extensionItem.clear();
if (extensionItem instanceof PrismContainer) {
schemaCheck(realValue instanceof Containerable, "Only containerables can be put into %s", extensionItem);
//noinspection unchecked
((PrismContainer<?>) extensionItem).add(((Containerable) realValue).asPrismContainerValue());
} else if (extensionItem instanceof PrismReference) {
schemaCheck(realValue instanceof Referencable, "Only referencables can be put into %s", extensionItem);
((PrismReference) extensionItem).add(((Referencable) realValue).asReferenceValue());
} else if (extensionItem instanceof PrismProperty) {
//noinspection unchecked
((PrismProperty<T>) extensionItem).addRealValue(realValue);
} else {
throw new IllegalStateException("Unknown extension item type: " + extensionItem);
}
}
}

@FunctionalInterface
private interface ExtensionItemRemover {
// Removes item (known from the context) from the extension
Expand Down
Expand Up @@ -18833,6 +18833,21 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="enableRunAsTaskTemplateOwnerAuthorization" type="xsd:boolean" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Whether to enable task template owner authorization.
EXPERIMENTAL. DEPRECATED. This feature is temporary and will be removed soon.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.3</a:since>
<a:deprecated>true</a:deprecated>
<a:deprecatedSince>4.3</a:deprecatedSince>
<a:experimental>true</a:experimental>
<a:displayName>InternalsConfigurationType.enableRunAsTaskTemplateOwnerAuthorization</a:displayName>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="internalsConfiguration" type="tns:InternalsConfigurationType"/>
Expand Down
Expand Up @@ -684,4 +684,46 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:element>

<xsd:element name="useTaskTemplateOwner" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
If true, this tasks derived from this template use the template owner,
instead of currently logged-in user. Requires runAsTaskTemplateOwner authorization
held by the currently logged-in user as well as this feature being explicitly enabled
in the system configuration.
EXPERIMENTAL. DEPRECATED. This feature is temporary and will be removed soon.
</xsd:documentation>
<xsd:appinfo>
<a:displayName>TaskExtension.useTaskTemplateOwner</a:displayName>
<a:displayOrder>999</a:displayOrder>
<a:minOccurs>0</a:minOccurs>
<a:maxOccurs>1</a:maxOccurs>
<a:indexed>false</a:indexed>
<a:experimental>true</a:experimental>
<a:deprecated>true</a:deprecated>
<a:deprecatedSince>4.3</a:deprecatedSince>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>

<xsd:element name="taskTemplateExecutionInitiatorRef" type="c:ObjectReferenceType">
<xsd:annotation>
<xsd:documentation>
What was the original principal that initiates a task template execution
(that uses template ownerRef as the identity under which it runs)?
EXPERIMENTAL. DEPRECATED. This feature is temporary and will be removed soon.
</xsd:documentation>
<xsd:appinfo>
<a:displayName>TaskExtension.taskTemplateExecutionInitiatorRef</a:displayName>
<a:displayOrder>999</a:displayOrder>
<a:minOccurs>0</a:minOccurs>
<a:maxOccurs>1</a:maxOccurs>
<a:indexed>false</a:indexed>
<a:experimental>true</a:experimental>
<a:deprecated>true</a:deprecated>
<a:deprecatedSince>4.3</a:deprecatedSince>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:schema>
Expand Up @@ -84,6 +84,13 @@ public enum ModelAuthorizationAction implements DisplayableValue<String> {
PARTIAL_EXECUTION("partialExecution", "Partial execution", "PARTIAL_EXECUTION_HELP"),
GET_EXTENSION_SCHEMA("getExtensionSchema", "Get extension schema", "GET_EXTENSION_SCHEMA_HELP"),

@Experimental
@Deprecated // will be removed soon
RUN_AS_TASK_TEMPLATE_OWNER("runAsTaskTemplateOwner", "Run as task template owner", "RUN_AS_TASK_TEMPLATE_OWNER_HELP"),

@Experimental
EXECUTE_CUSTOM_CODE("executeCustomCode", "Execute custom code", "EXECUTE_CUSTOM_CODE_HELP"),

RUN_REPORT("runReport", "Run report", "RUN_REPORT_HELP"),
IMPORT_REPORT("importReport", "Import report", "IMPORT_REPORT_HELP"),

Expand Down
Expand Up @@ -6,6 +6,8 @@
*/
package com.evolveum.midpoint.model.impl.controller;

import static com.evolveum.midpoint.util.MiscUtil.schemaCheck;

import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;

Expand All @@ -19,6 +21,8 @@
import java.util.stream.Collectors;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.security.enforcer.api.*;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -90,10 +94,6 @@
import com.evolveum.midpoint.schema.util.*;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.enforcer.api.FilterGizmo;
import com.evolveum.midpoint.security.enforcer.api.ItemSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.ObjectSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.*;
import com.evolveum.midpoint.util.annotation.Experimental;
Expand Down Expand Up @@ -1660,10 +1660,10 @@ public TaskType submitTaskFromTemplate(String templateTaskOid, List<Item<?, ?>>
}
TaskType newTask = modelService.getObject(TaskType.class, templateTaskOid,
createCollection(createExecutionPhase()), opTask, result).asObjectable();
setOwnerRefForNewTask(newTask, principal, opTask, result);
newTask.setName(PolyStringType.fromOrig(newTask.getName().getOrig() + " " + (int) (Math.random() * 10000)));
newTask.setOid(null);
newTask.setTaskIdentifier(null);
newTask.setOwnerRef(createObjectRef(principal.getFocus(), prismContext));
newTask.setExecutionStatus(RUNNABLE);
newTask.setSchedulingState(READY);
for (Item<?, ?> extensionItem : extensionItems) {
Expand All @@ -1682,6 +1682,47 @@ public TaskType submitTaskFromTemplate(String templateTaskOid, List<Item<?, ?>>
}
}

private void setOwnerRefForNewTask(TaskType newTask, MidPointPrincipal principal, Task opTask, OperationResult result)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
SecurityViolationException, ExpressionEvaluationException {
ObjectReferenceType principalRef = createObjectRef(principal.getFocus(), prismContext);
if (useOwnerFromTemplate(newTask, principal, opTask, result)) {
schemaCheck(newTask.getOwnerRef() != null, "Object template " + newTask + " has no owner");
ObjectTypeUtil.setExtensionItemValue(newTask.asPrismObject(),
SchemaConstants.MODEL_EXTENSION_TASK_TEMPLATE_EXECUTION_INITIATOR_REF, principalRef.clone());
} else {
newTask.setOwnerRef(principalRef);
}
}

private boolean useOwnerFromTemplate(TaskType newTask, MidPointPrincipal principal, Task opTask, OperationResult result)
throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException,
ConfigurationException, ExpressionEvaluationException {
Boolean useTaskTemplateOwner = ObjectTypeUtil.getExtensionItemRealValue(newTask.asPrismObject(),
SchemaConstants.MODEL_EXTENSION_USE_TASK_TEMPLATE_OWNER);
if (!Boolean.TRUE.equals(useTaskTemplateOwner)) {
return false;
}

PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache.getSystemConfiguration(result);
if (systemConfiguration == null || systemConfiguration.asObjectable().getInternals() == null ||
!Boolean.TRUE.equals(systemConfiguration.asObjectable().getInternals().isEnableRunAsTaskTemplateOwnerAuthorization())) {
LOGGER.warn("Use of task template owner was requested but this (experimental) feature is not enabled"
+ " in the system configuration. Task: {}", newTask);
return false;
}

boolean authorized = securityEnforcer.isAuthorized(ModelAuthorizationAction.RUN_AS_TASK_TEMPLATE_OWNER.getUrl(),
null, AuthorizationParameters.EMPTY, null, opTask, result);
if (authorized) {
return true;
} else {
LOGGER.warn("Use of task template owner was requested but the currently logged-in user does not have appropriate "
+ "authorization. Task will be run under its own identity. Task: {}, User: {}", newTask, principal);
return false;
}
}

@NotNull
@Override
public TaskType submitTaskFromTemplate(String templateTaskOid, Map<QName, Object> extensionValues, Task opTask, OperationResult parentResult)
Expand Down
Expand Up @@ -9,14 +9,12 @@

import static com.evolveum.midpoint.model.impl.scripting.VariablesUtil.cloneIfNecessary;

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

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.PipelineItem;
import com.evolveum.midpoint.model.api.TaskService;
import com.evolveum.midpoint.model.api.expr.MidpointFunctions;
import com.evolveum.midpoint.model.impl.scripting.*;
import com.evolveum.midpoint.model.impl.scripting.helpers.ExpressionHelper;
Expand All @@ -35,7 +33,6 @@
import com.evolveum.midpoint.schema.expression.VariablesMap;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.statistics.IterativeTaskInformation.Operation;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
Expand Down Expand Up @@ -95,13 +92,13 @@ Throwable processActionException(Throwable e, String actionName, PrismValue valu
}
}

void checkRootAuthorization(ExecutionContext context, OperationResult globalResult, String actionName)
void checkExecuteCustomCodeAuthorization(ExecutionContext context, OperationResult globalResult, String actionName)
throws ScriptExecutionException {
if (context.isPrivileged()) {
return;
}
try {
securityEnforcer.authorize(AuthorizationConstants.AUTZ_ALL_URL, null, AuthorizationParameters.EMPTY, null, context.getTask(), globalResult);
securityEnforcer.authorize(ModelAuthorizationAction.EXECUTE_CUSTOM_CODE.getUrl(), null, AuthorizationParameters.EMPTY, null, context.getTask(), globalResult);
} catch (SecurityViolationException | SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException | ConfigurationException e) {
throw new ScriptExecutionException("You are not authorized to execute '" + actionName + "' action.");
}
Expand Down
Expand Up @@ -79,7 +79,7 @@ public PipelineData execute(ActionExpressionType action, PipelineData input, Exe
OperationResult globalResult) throws ScriptExecutionException, SchemaException, ConfigurationException,
ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException {

checkRootAuthorization(context, globalResult, NAME);
checkExecuteCustomCodeAuthorization(context, globalResult, NAME);

Parameters parameters = getParameters(action, input, context, globalResult);
PipelineData output = PipelineData.createEmpty();
Expand Down
Expand Up @@ -70,7 +70,7 @@ public PipelineData execute(ActionExpressionType action, PipelineData input, Exe
NotifyActionExpressionType.F_FOR_WHOLE_INPUT, PARAM_FOR_WHOLE_INPUT, input, context, false, PARAM_FOR_WHOLE_INPUT, globalResult);

if (handler != null) {
checkRootAuthorization(context, globalResult, NAME); // TODO explain that the reason is that handler is not null
checkExecuteCustomCodeAuthorization(context, globalResult, NAME); // TODO explain that the reason is that handler is not null
}

requireNonNull(notificationManager, "Notification manager is unavailable");
Expand Down
Expand Up @@ -23,6 +23,7 @@
import javax.xml.namespace.QName;

import com.evolveum.midpoint.model.api.ModelPublicConstants;
import com.evolveum.midpoint.test.TestResource;
import com.evolveum.midpoint.util.exception.ScriptExecutionException;

import com.evolveum.midpoint.notifications.api.transports.Message;
Expand Down Expand Up @@ -137,6 +138,9 @@ public abstract class AbstractBasicScriptingTest extends AbstractInitializedMode
private static final File TASK_TO_KEEP_SUSPENDED_FILE = new File(TEST_DIR, "task-to-keep-suspended.xml");
private static final String RESUME_SUSPENDED_TASKS = "resume-suspended-tasks";

private static final TestResource<UserType> ROLE_OPERATOR = new TestResource<>(TEST_DIR, "role-operator.xml", "8ecc780c-93ad-4f2f-a720-ae3c2c584cbf");
private static final TestResource<UserType> USER_OPERATOR = new TestResource<>(TEST_DIR, "user-operator.xml", "0f748045-450d-43a5-a720-ea6adc83e43f");

// Tests 6xx
private static final String MODIFY_JACK_PASSWORD = "modify-jack-password";
private static final String MODIFY_JACK_PASSWORD_TASK = "modify-jack-password-task";
Expand Down Expand Up @@ -1177,11 +1181,22 @@ public void test560StartTaskFromTemplate() throws Exception {
task.setOwner(getUser(USER_ADMINISTRATOR_OID));
OperationResult result = task.getResult();

repoAddObject(ROLE_OPERATOR, result);
repoAddObject(USER_OPERATOR, result);

repoAddObjectFromFile(SCRIPTING_USERS_IN_BACKGROUND_TASK_FILE, result);
ExecuteScriptType exec = parseExecuteScript(START_TASKS_FROM_TEMPLATE);

when();
ExecutionContext output = evaluator.evaluateExpression(exec, VariablesMap.emptyMap(), false, task, result);
ExecutionContext output;
try {
setEnableRunAsTaskTemplateOwnerAuthorization(true, result);
login(getUser(USER_OPERATOR.oid));
output = evaluator.evaluateExpression(exec, VariablesMap.emptyMap(), false, task, result);
} finally {
login(userAdministrator);
setEnableRunAsTaskTemplateOwnerAuthorization(false, result);
}

then();
dumpOutput(output, result);
Expand Down Expand Up @@ -1453,4 +1468,13 @@ private ExecuteScriptType parseExecuteScript(String name) throws IOException, Sc
return parseExecuteScript(getFile(name));
}

private void setEnableRunAsTaskTemplateOwnerAuthorization(boolean value, OperationResult result)
throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException {
List<ItemDelta<?, ?>> itemDeltas = deltaFor(SystemConfigurationType.class)
.item(SystemConfigurationType.F_INTERNALS, InternalsConfigurationType.F_ENABLE_RUN_AS_TASK_TEMPLATE_OWNER_AUTHORIZATION)
.replace(value)
.asItemDeltas();
repositoryService.modifyObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value(), itemDeltas,
result);
}
}

0 comments on commit 8ddadd1

Please sign in to comment.