Skip to content

Commit

Permalink
Guarantee expression profiles in mappings
Browse files Browse the repository at this point in the history
The expression profile in a mapping evaluation is now determined safely
(assuming that the origin of the mapping was determined correctly).

This is assured by:

1. not allowing "undetermined" origins anymore,
2. by treating "external" origin according to principal's authorizations
(just like we do for bulk actions).

Note that the "undetermined unsafe" origin is not used in midPoint
any longer.

Work in progress. Some tests may fail.
  • Loading branch information
mederly committed Aug 25, 2023
1 parent 97b8d7e commit 7d69779
Show file tree
Hide file tree
Showing 32 changed files with 230 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
import com.evolveum.midpoint.util.annotation.Experimental;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;

import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;

import org.jetbrains.annotations.NotNull;

import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;

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

/**
* Description of an origin of a configuration item (expression, mapping, and so on).
* Necessary e.g. for the derivation of an {@link ExpressionProfile}.
Expand All @@ -40,17 +44,6 @@ public abstract class ConfigurationItemOrigin implements Serializable {

@Serial private static final long serialVersionUID = 0L;

/**
* TEMPORARY until the implementation is complete.
*
* We may search for occurrences of this method to find all the places where we need to implement the origin determination.
*
* Use with care! Careless use of this origin may render expression profiles ineffective.
*/
public static ConfigurationItemOrigin undetermined() {
return new ConfigurationItemOrigin.Undetermined(false);
}

/** Undetermined but safe, because it is never used to determine the expression profile. */
public static ConfigurationItemOrigin undeterminedSafe() {
return new ConfigurationItemOrigin.Undetermined(true);
Expand Down Expand Up @@ -115,12 +108,23 @@ public static ConfigurationItemOrigin embedded(

public static ConfigurationItemOrigin inObject(
@NotNull ObjectType originatingObject, @NotNull ItemPath path) {
return new InObject(originatingObject, path, true);
return new InObject(originatingObject, true, path, true);
}

/**
* Very approximate origin: we know that the configuration item is used in a given (resolved, i.e., expanded) resource.
* But we don't know how it got there. It may be the resource object itself, but also any super-resource from which
* this resource inherits.
*
* See MID-9018.
*/
public static ConfigurationItemOrigin inResourceOrAncestor(@NotNull ResourceType resource) {
return new InObject(resource, false, ItemPath.EMPTY_PATH, false);
}

public static ConfigurationItemOrigin inObjectApproximate(
@NotNull ObjectType originatingObject, @NotNull ItemPath knownPath) {
return new InObject(originatingObject, knownPath, false);
return new InObject(originatingObject, true, knownPath, false);
}

public static ConfigurationItemOrigin inDelta(
Expand Down Expand Up @@ -190,6 +194,10 @@ public String toString() {
return "detached(" + channelUri + ")";
}

public @NotNull String getChannelUri() {
return channelUri;
}

@Override
public ConfigurationItemOrigin child(@NotNull ItemPath path) {
return this;
Expand Down Expand Up @@ -223,61 +231,70 @@ public ConfigurationItemOrigin child(@NotNull ItemPath path) {
public static class InObject extends ConfigurationItemOrigin {

private final @NotNull ObjectType originatingObject;

/**
* If `false`, we know the object only approximately. This is currently the case for resource inheritance.
* See MID-9018.
*/
private final boolean preciseObject;

private final @NotNull ItemPath path;

/**
* If `false`, we know the position only approximately. The {@link #path} is then all that we know;
* the value can be anywhere in that subtree.
*/
private final boolean precise;
private final boolean precisePath;

private InObject(
@NotNull ObjectType originatingObject, @NotNull ItemPath path, boolean precise) {
@NotNull ObjectType originatingObject, boolean preciseObject, @NotNull ItemPath path, boolean precisePath) {
// explicit nullity check is here for additional safety
this.originatingObject = Objects.requireNonNull(originatingObject);
this.preciseObject = preciseObject;
this.path = path;
this.precise = precise;
this.precisePath = precisePath;
if (!preciseObject) {
argCheck(!precisePath, "Not knowing the object but knowing the path?! %s, %s", originatingObject, path);
}
}

@Override
public @NotNull ConfigurationItemOrigin toApproximate() {
if (precise) {
return new InObject(originatingObject, path, false);
if (precisePath) {
return new InObject(originatingObject, preciseObject, path, false);
} else {
return this;
}
}

public @NotNull ObjectType getOriginatingObject() {
return originatingObject;
}

public @NotNull PrismObject<? extends ObjectType> getOriginatingPrismObject() {
return originatingObject.asPrismObject();
}

public @NotNull String getOriginatingObjectOid() {
return originatingObject.getOid();
}

public @NotNull ItemPath getPath() {
return path;
}

public boolean isPrecise() {
return precise;
}

@Override
public ConfigurationItemOrigin child(@NotNull ItemPath path) {
return precise ?
new InObject(originatingObject, this.path.append(path), true) :
return precisePath ?
new InObject(originatingObject, preciseObject, this.path.append(path), true) :
this;
}

@Override
public String toString() {
return "in " + originatingObject + " @" + path + (precise ? "" : " (approximate)");
var sb = new StringBuilder();
sb.append("in ").append(originatingObject);
if (preciseObject) {
sb.append(" @").append(path);
if (!precisePath) {
sb.append(" (approximate)");
}
} else {
sb.append(" (or some of its ancestors");
}
return sb.toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,4 @@ public interface OriginProvider<T> {
static @NotNull <T> OriginProvider<T> generated() {
return item -> ConfigurationItemOrigin.generated();
}

/** Use with care! See {@link ConfigurationItemOrigin#undetermined()}. */
static @NotNull <T> OriginProvider<T> undetermined() {
return item -> ConfigurationItemOrigin.undetermined();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.evolveum.midpoint.repo.common.SystemObjectCache;

import com.evolveum.midpoint.schema.config.ConfigurationItemOrigin;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.expression.ExpressionProfile;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
Expand Down Expand Up @@ -111,13 +112,45 @@ public class ExpressionProfileManager {
}
}

/**
* Determines {@link ExpressionProfile} for given configuration item origin.
* Does not allow undetermined profiles, and treats external profiles with care.
*/
public @NotNull ExpressionProfile determineExpressionProfileStrict(
@NotNull ConfigurationItemOrigin origin, @NotNull Task task, @NotNull OperationResult result)
throws SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException,
SecurityViolationException, ObjectNotFoundException {
if (origin instanceof ConfigurationItemOrigin.External external) {
return determineExpressionProfileForChannel(external.getChannelUri(), task, result);
} else {
return determineExpressionProfileCommon(origin, result);
}
}

/**
* Determines {@link ExpressionProfile} for given configuration item origin.
*
* FIXME this is a transitory implementation: various unknown/external origins are treated as trustworthy.
* FIXME this is a transitory, unsafe implementation: various unknown/external origins are treated as trustworthy.
* This should be changed before expression profiles are declared fully functional.
*/
public @NotNull ExpressionProfile determineExpressionProfile(
public @NotNull ExpressionProfile determineExpressionProfileUnsafe(
@NotNull ConfigurationItemOrigin origin, @NotNull OperationResult result)
throws SchemaException, ConfigurationException {
if (origin instanceof ConfigurationItemOrigin.Undetermined undetermined) {
stateCheck(!undetermined.isSafe(), "Safe undetermined origin cannot be used to derive expression profile");
return ExpressionProfile.full(); // Later, we may throw an exception here
} else if (origin instanceof ConfigurationItemOrigin.External) {
return ExpressionProfile.full(); // FIXME we should perhaps return a restricted profile here (from the configuration?)
} else {
return determineExpressionProfileCommon(origin, result);
}
}

/**
* A strict version of {@link #determineExpressionProfileUnsafe(ConfigurationItemOrigin, OperationResult)} that does not
* allow undetermined nor external profiles.
*/
private @NotNull ExpressionProfile determineExpressionProfileCommon(
@NotNull ConfigurationItemOrigin origin, @NotNull OperationResult result)
throws SchemaException, ConfigurationException {
if (origin instanceof ConfigurationItemOrigin.InObject inObject) {
Expand All @@ -126,18 +159,45 @@ public class ExpressionProfileManager {
return determineExpressionProfile(inDelta.getTargetPrismObject(), result);
} else if (origin instanceof ConfigurationItemOrigin.Generated) {
return ExpressionProfile.full(); // Most probably OK
} else if (origin instanceof ConfigurationItemOrigin.Undetermined undetermined) {
stateCheck(!undetermined.isSafe(), "Safe undetermined origin cannot be used to derive expression profile");
return ExpressionProfile.full(); // Later, we may throw an exception here
} else if (origin instanceof ConfigurationItemOrigin.Undetermined) {
throw new IllegalStateException("Undetermined origin for expression profile is not supported");
} else if (origin instanceof ConfigurationItemOrigin.External) {
return ExpressionProfile.full(); // FIXME we should perhaps return a restricted profile here (from the configuration?)
throw new IllegalStateException("'External' origin for expression profile is not supported");
} else {
throw new AssertionError(origin);
}
}

/**
* Special version of {@link #determineExpressionProfile(ConfigurationItemOrigin, OperationResult)}
* Determines expression profile for {@link ConfigurationItemOrigin.External} origin.
*
* We assume that the configuration item was entered via GUI or provided via REST right by the logged-in principal.
* Hence, we allow everything only if the principal is a root. Otherwise, we allow nothing.
*
* For the "init" channel, we could allow everything, but its safer to assume that the initialization is always
* carried out with full privileges, and that it's safe to check the authorization there as well.
*
* This could be made configurable in the future.
*/
private @NotNull ExpressionProfile determineExpressionProfileForChannel(
@NotNull String channelUri, @NotNull Task task, @NotNull OperationResult result)
throws SchemaException, ExpressionEvaluationException, CommunicationException,
SecurityViolationException, ConfigurationException, ObjectNotFoundException {
if (SchemaConstants.CHANNEL_INIT_URI.equals(channelUri)
|| SchemaConstants.CHANNEL_REST_URI.equals(channelUri)
|| SchemaConstants.CHANNEL_USER_URI.equals(channelUri)) {
if (securityEnforcer.isAuthorizedAll(task, result)) {
return ExpressionProfile.full();
} else {
return ExpressionProfile.none();
}
} else {
throw new UnsupportedOperationException("The expression profile cannot be determined for channel: " + channelUri);
}
}

/**
* Special version of {@link #determineExpressionProfileUnsafe(ConfigurationItemOrigin, OperationResult)}
* for scripting (bulk actions). It is not as permissive: some origins are banned, and the default for non-root users
* is the restricted profile (unless `privileged` is set to true - in order to provide backwards compatibility with 4.7).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class FunctionExpressionEvaluator<V extends PrismValue, D extends ItemDef
super(elementName, functionEvaluatorBean, outputDefinition, protector);
this.functionCallCI = FunctionExpressionEvaluatorConfigItem.of(
functionEvaluatorBean,
ConfigurationItemOrigin.undetermined()); // TODO origin
ConfigurationItemOrigin.undeterminedSafe()); // TODO origin
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

Expand All @@ -19,11 +18,8 @@
import com.evolveum.midpoint.schema.config.ConfigurationItemOrigin;
import com.evolveum.midpoint.schema.processor.ResourceObjectDefinition;

import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.SimulationUtil;

import com.evolveum.midpoint.util.exception.ConfigurationException;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -42,6 +38,8 @@
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.jetbrains.annotations.VisibleForTesting;

/**
* Builder is used to construct a configuration of Mapping object, which - after building - becomes
* immutable.
Expand Down Expand Up @@ -70,7 +68,8 @@ public abstract class AbstractMappingBuilder<
private Source<?, ?> defaultSource;
private final List<Source<?, ?>> additionalSources = new ArrayList<>();
private D defaultTargetDefinition;
private ExpressionProfile expressionProfile;
@VisibleForTesting // NEVER use for production code
private ExpressionProfile explicitExpressionProfile;
private ItemPath defaultTargetPath;
private Collection<V> originalTargetValues;
private ObjectDeltaObject<?> sourceContext;
Expand Down Expand Up @@ -133,14 +132,9 @@ public RT defaultTargetDefinition(D val) {
return typedThis();
}

@VisibleForTesting // NEVER use for production code
RT explicitExpressionProfile(ExpressionProfile val) {
expressionProfile = val;
return typedThis();
}

public RT computeExpressionProfile(@NotNull OperationResult result) throws SchemaException, ConfigurationException {
var configItem = Objects.requireNonNull(mappingConfigItem, "no mapping");
expressionProfile = beans.expressionProfileManager.determineExpressionProfile(configItem.origin(), result);
explicitExpressionProfile = val;
return typedThis();
}

Expand Down Expand Up @@ -398,8 +392,8 @@ public D getDefaultTargetDefinition() {
return defaultTargetDefinition;
}

public ExpressionProfile getExpressionProfile() {
return expressionProfile;
public ExpressionProfile getExplicitExpressionProfile() {
return explicitExpressionProfile;
}

ItemPath getDefaultTargetPath() {
Expand Down

0 comments on commit 7d69779

Please sign in to comment.