Skip to content

Commit

Permalink
Check authorizations after Projector starts
Browse files Browse the repository at this point in the history
This resolves MID-9459.
  • Loading branch information
mederly committed Feb 8, 2024
1 parent ac248f9 commit 9ae6f9c
Show file tree
Hide file tree
Showing 44 changed files with 425 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

import static com.evolveum.midpoint.schema.selector.eval.SubjectedEvaluationContext.*;

/**
Expand All @@ -26,6 +28,13 @@
*/
public class MatchingContext extends SelectorProcessingContext {

/**
* Do we have the full information about the object being matched?
*
* @see SelectorClause#requiresFullInformation()
*/
private final boolean fullInformationAvailable;

public MatchingContext(
@Nullable ObjectFilterExpressionEvaluator filterEvaluator,
@NotNull ProcessingTracer<? super SelectorTraceEvent> tracer,
Expand All @@ -34,7 +43,8 @@ public MatchingContext(
@Nullable OwnerResolver ownerResolver,
@Nullable ObjectResolver objectResolver,
@NotNull ClauseProcessingContextDescription description,
@NotNull DelegatorSelection delegatorSelection) {
@NotNull DelegatorSelection delegatorSelection,
boolean fullInformationAvailable) {
super(
filterEvaluator,
tracer,
Expand All @@ -44,10 +54,14 @@ public MatchingContext(
objectResolver,
description,
delegatorSelection);
this.fullInformationAvailable = fullInformationAvailable;
}

public @NotNull MatchingContext next(
@NotNull DelegatorSelection delegatorSelection, @NotNull String idDelta, @NotNull String textDelta) {
@NotNull DelegatorSelection delegatorSelection,
@NotNull String idDelta,
@NotNull String textDelta,
boolean fullInformationAvailable) {
return new MatchingContext(
filterEvaluator,
tracer,
Expand All @@ -56,10 +70,12 @@ public MatchingContext(
ownerResolver,
objectResolver,
description.child(idDelta, textDelta),
delegatorSelection);
delegatorSelection,
fullInformationAvailable);
}

public @NotNull MatchingContext next(@NotNull String idDelta, @NotNull String textDelta) {
public @NotNull MatchingContext next(
@NotNull String idDelta, @NotNull String textDelta, Boolean fullInformationAvailable) {
return new MatchingContext(
filterEvaluator,
tracer,
Expand All @@ -68,6 +84,11 @@ public MatchingContext(
ownerResolver,
objectResolver,
description.child(idDelta, textDelta),
delegatorSelection);
delegatorSelection,
Objects.requireNonNullElse(fullInformationAvailable, this.fullInformationAvailable));
}

public boolean isFullInformationAvailable() {
return fullInformationAvailable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
}
var assignees = getAssignees(realValue, ctx);
if (!assignees.isEmpty()) {
var childCtx = ctx.next(getDelegatorSelectionMode(realValue), "a", "assignee");
// The assignees are always known "in full", as they are fetched from the repository via objectResolver.
var childCtx = ctx.next(getDelegatorSelectionMode(realValue), "a", "assignee", true);
for (PrismObject<? extends ObjectType> assignee : assignees) {
assert assignee != null;
if (selector.matches(assignee.getValue(), childCtx)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ static DelegatorClause of(@NotNull ValueSelector selector, boolean allowInactive
return new DelegatorClause(selector, allowInactive);
}

@Override
public boolean requiresFullInformation() {
return true;
}

@Override
public @NotNull String getName() {
return "delegator";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ static OrgRefClause of(@NotNull ObjectReferenceType orgRef) throws Configuration
configNonNull(orgRef.getOid(), "No OID in orgRef clause: %s", orgRef));
}

@Override
public boolean requiresFullInformation() {
return true;
}

public @NotNull String getOrgOid() {
return orgOid;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ static OrgRelationClause of(@NotNull OrgRelationObjectSpecificationType bean) {
return new OrgRelationClause(bean);
}

@Override
public boolean requiresFullInformation() {
return true;
}

@Override
public @NotNull String getName() {
return "orgRelation";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
traceNotApplicable(ctx, "no owner");
return false;
}
// The availability of the full information about the parent is the same as for the current value (for shadows),
// or guaranteed for other kind of data. Let's keep it simple and assume the worst (i.e. the same as for current value).
boolean matches =
selector.matches(owner.getValue(), ctx.next("o", "owner"));
selector.matches(owner.getValue(), ctx.next("o", "owner", null));
traceApplicability(ctx, matches, "owner (%s) matches: %s", owner, matches);
return matches;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
}
value = parent2;
}
boolean matches = parentSelector.matches(value, ctx.next("p", "parent"));
// The availability of the full information about the parent is the same as for the current value.
boolean matches = parentSelector.matches(value, ctx.next("p", "parent", null));
traceApplicability(ctx, matches, "parent specification matches: %s", matches);
return matches;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,20 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
traceNotApplicable(ctx, "has no related object");
return false;
}
// The related object is always known "in full", as it is fetched from the repository via objectResolver.
boolean matches =
selector.matches(
relatedObject.getValue(),
ctx.next(DelegatorSelection.NO_DELEGATOR, "rel", "related object"));
ctx.next(DelegatorSelection.NO_DELEGATOR, "rel", "related object", true));
traceApplicability(ctx, matches, "related object (%s) matches: %s", relatedObject, matches);
return matches;
}

private PrismObject<? extends ObjectType> getRelatedObject(ObjectType object, @NotNull SelectorProcessingContext ctx) {
if (object instanceof CaseType) {
return ctx.resolveReference(((CaseType) object).getObjectRef(), object, "related object");
} else if (object instanceof TaskType) {
return ctx.resolveReference(((TaskType) object).getObjectRef(), object, "related object");
if (object instanceof CaseType aCase) {
return ctx.resolveReference(aCase.getObjectRef(), object, "related object");
} else if (object instanceof TaskType task) {
return ctx.resolveReference(task.getObjectRef(), object, "related object");
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
traceNotApplicable(ctx, "no requestor");
return false;
}
// The requestor is always known "in full", as it is fetched from the repository via objectResolver.
boolean matches =
selector.matches(
requestor.getValue(),
ctx.next(DelegatorSelection.NO_DELEGATOR, "req", "requestor"));
ctx.next(DelegatorSelection.NO_DELEGATOR, "req", "requestor", true));
traceApplicability(ctx, matches, "requestor object (%s) matches: %s", requestor, matches);
return matches;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
import com.evolveum.midpoint.schema.selector.eval.MatchingContext;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleRelationObjectSpecificationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.apache.commons.lang3.BooleanUtils;
import org.jetbrains.annotations.NotNull;
Expand All @@ -48,6 +45,11 @@ static RoleRelationClause of(@NotNull RoleRelationObjectSpecificationType bean)
return new RoleRelationClause(bean);
}

@Override
public boolean requiresFullInformation() {
return true;
}

@Override
public @NotNull String getName() {
return "roleRelation";
Expand Down Expand Up @@ -86,10 +88,10 @@ private boolean matchesRoleRelation(ObjectType object, ObjectReferenceType subje
return true;
}
if (!BooleanUtils.isFalse(bean.isIncludeMembers())) {
if (!(object instanceof FocusType)) {
if (!(object instanceof AssignmentHolderType assignmentHolder)) {
return false;
}
for (ObjectReferenceType objectRoleMembershipRef : ((FocusType) object).getRoleMembershipRef()) {
for (ObjectReferenceType objectRoleMembershipRef : assignmentHolder.getRoleMembershipRef()) {
if (!subjectRoleMembershipRef.getOid().equals(objectRoleMembershipRef.getOid())) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
*/
public abstract class SelectorClause implements DebugDumpable, Serializable {

/**
* Does this clause require full information about the object that is provided by the Projector?
* For example, any clauses referring to `parentOrgRef` or `tenantRef` need that information
* (which is computed from the assignments).
*/
public boolean requiresFullInformation() {
return false;
}

/** Returns `true` if the `value` matches this clause. */
public abstract boolean matches(
@NotNull PrismValue value, @NotNull MatchingContext ctx)
Expand Down Expand Up @@ -89,5 +98,4 @@ public String debugDump(int indent) {
.map(c -> (T) c)
.collect(Collectors.toList()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ static SubtypeClause of(@NotNull String subtype) throws ConfigurationException {
public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx) {
Object realValue = value.getRealValueIfExists();
List<String> actualSubtypes;
if (realValue instanceof ObjectType) {
actualSubtypes = ((ObjectType) realValue).getSubtype();
} else if (realValue instanceof AssignmentType) {
actualSubtypes = ((AssignmentType) realValue).getSubtype();
if (realValue instanceof ObjectType object) {
actualSubtypes = object.getSubtype();
} else if (realValue instanceof AssignmentType assignment) {
actualSubtypes = assignment.getSubtype();
} else {
traceNotApplicable(
ctx, "subtype mismatch, expected '%s' but object has none (it is neither object nor assignment)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ static TenantClause of(@NotNull TenantSelectorType bean) {
return new TenantClause(bean);
}

@Override
public boolean requiresFullInformation() {
return true;
}

@Override
public @NotNull String getName() {
return "tenant";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
SecurityViolationException, ConfigurationException, ObjectNotFoundException {
ctx.traceMatchingStart(this, value);
for (SelectorClause clause : clauses) {
if (!ctx.isFullInformationAvailable() && clause.requiresFullInformation()) {
ctx.traceClauseNotApplicable(clause, "requires full information but it's (potentially) not available");
continue;
}
if (!clause.matches(value, ctx)) {
ctx.traceMatchingEnd(this, value, false);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,11 @@ private void addSecurityConfig(FilterInvocation filterInvocation, List<String> r

@Override
public @NotNull <O extends ObjectType> ObjectSecurityConstraints compileSecurityConstraints(
@NotNull PrismObject<O> object, @NotNull Options options,
@NotNull PrismObject<O> object, boolean fullInformationAvailable, @NotNull Options options,
@NotNull Task task, @NotNull OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
ConfigurationException, SecurityViolationException {
return securityEnforcer.compileSecurityConstraints(object, options, task, result);
return securityEnforcer.compileSecurityConstraints(object, fullInformationAvailable, options, task, result);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,10 @@ public Collection<ObjectDeltaOperation<? extends ObjectType>> executeChanges(
// 3) for MODIFY operation: filters contained in deltas -> these have to be treated here, because if OID is missing from such a delta, the change would be rejected by the repository
if (ModelExecuteOptions.isReevaluateSearchFilters(options)) {
for (ObjectDelta<? extends ObjectType> delta : deltas) {
ModelImplUtils.resolveReferences(delta, cacheRepositoryService, false, true, EvaluationTimeType.IMPORT, true, result);
ModelImplUtils.resolveReferences(
delta, cacheRepositoryService,
false, true, EvaluationTimeType.IMPORT,
true, result);
}
} else if (ModelExecuteOptions.isIsImport(options)) {
// if plain import is requested, we simply evaluate filters in ADD operation (and we do not force reevaluation if OID is already set)
Expand Down Expand Up @@ -426,6 +429,9 @@ private void authorizePartialExecution(LensContext<? extends ObjectType> context
PartialProcessingOptionsType partialProcessing = ModelExecuteOptions.getPartialProcessing(options);
if (partialProcessing != null) {
PrismObject<? extends ObjectType> object = context.getFocusContext().getObjectAny();
// FIXME the information about the object may be incomplete (orgs, tenants, roles) but we treat it as complete here.
// See also MID-9454.
// TODO audit the request failure if this check fails
securityEnforcer.authorize(
ModelAuthorizationAction.PARTIAL_EXECUTION.getUrl(),
null, AuthorizationParameters.Builder.buildObject(object), task, result);
Expand Down Expand Up @@ -469,12 +475,8 @@ public <F extends ObjectType> void recompute(
// Not using read-only for now
//noinspection unchecked
focus = objectResolver.getObject(type, oid, null, task, result).asPrismContainer();
LOGGER.debug("Recomputing {}", focus);

LensContext<F> lensContext = contextFactory.createRecomputeContext(focus, options, task, result);
LOGGER.trace("Recomputing {}, context:\n{}", focus, lensContext.debugDumpLazily());

clockwork.run(lensContext, task, result);
executeRecompute(focus, options, task, result);

} catch (Throwable t) {
ModelImplUtils.recordException(result, t);
Expand All @@ -488,6 +490,21 @@ public <F extends ObjectType> void recompute(
LOGGER.trace("Recomputing of {}: {}", focus, result.getStatus());
}

/** Generally useful convenience method. */
public <F extends ObjectType> void executeRecompute(
@NotNull PrismObject<F> focus,
@Nullable ModelExecuteOptions options,
@NotNull Task task,
@NotNull OperationResult result)
throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException,
ObjectNotFoundException, SecurityViolationException, PolicyViolationException, ObjectAlreadyExistsException {
LOGGER.debug("Recomputing {}", focus);
LensContext<F> lensContext = contextFactory.createRecomputeContext(focus, options, task, result);

LOGGER.trace("Recomputing {}, context:\n{}", focus, lensContext.debugDumpLazily());
clockwork.run(lensContext, task, result);
}

private void applyDefinitions(
Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
Expand Down Expand Up @@ -1548,6 +1565,7 @@ public void importFromResource(String shadowOid, Task task, OperationResult pare
// TODO: add context to the result

try {
// FIXME autz?
importFromResourceLauncher.importSingleShadow(shadowOid, task, result);
} catch (Throwable t) {
ModelImplUtils.recordException(result, t);
Expand Down Expand Up @@ -2565,6 +2583,7 @@ public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescrip
PrismObject<ShadowType> resourceObject = getResourceObject(changeDescription);
ObjectDelta<ShadowType> objectDelta = getObjectDelta(changeDescription, result);

// FIXME autz
ExternalResourceEvent event = new ExternalResourceEvent(objectDelta, resourceObject,
oldRepoShadow, changeDescription.getChannel());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public <O extends ObjectType> PrismObjectDefinition<O> getEditObjectDefinition(

// TODO: maybe we need to expose owner resolver in the interface?
ObjectSecurityConstraints securityConstraints =
securityEnforcer.compileSecurityConstraints(fullObject, SecurityEnforcer.Options.create(), task, result);
securityEnforcer.compileSecurityConstraints(fullObject, true, SecurityEnforcer.Options.create(), task, result);
LOGGER.trace("Security constrains for {}:\n{}", object, DebugUtil.debugDumpLazily(securityConstraints));
TransformableObjectDefinition<O> objectDefinition = schemaTransformer.transformableDefinition(object.getDefinition());
applyArchetypePolicy(objectDefinition, object, task, result);
Expand Down Expand Up @@ -382,7 +382,7 @@ public ResourceObjectDefinition getEditObjectClassDefinition(

// TODO: maybe we need to expose owner resolver in the interface?
ObjectSecurityConstraints securityConstraints =
securityEnforcer.compileSecurityConstraints(shadow, SecurityEnforcer.Options.create(), task, result);
securityEnforcer.compileSecurityConstraints(shadow, true, SecurityEnforcer.Options.create(), task, result);
LOGGER.trace("Security constrains for {}:\n{}", shadow, DebugUtil.debugDumpLazily(securityConstraints));

AuthorizationDecisionType attributesReadDecision =
Expand Down Expand Up @@ -516,7 +516,7 @@ public <H extends AssignmentHolderType, R extends AbstractRoleType> RoleSelectio
ObjectSecurityConstraints securityConstraints;
try {
securityConstraints = securityEnforcer.compileSecurityConstraints(
focus, SecurityEnforcer.Options.create(), task, result);
focus, true, SecurityEnforcer.Options.create(), task, result);
} catch (ExpressionEvaluationException | ObjectNotFoundException | SchemaException | CommunicationException |
SecurityViolationException e) {
result.recordFatalError(e);
Expand Down

0 comments on commit 9ae6f9c

Please sign in to comment.