Skip to content

Commit

Permalink
Improve delegation-related authorizations [PoC]
Browse files Browse the repository at this point in the history
When giving the delegator the rights to see the delegate's assignments
and delegateRef values, before 4.8 we had no choice but to allow him to
see all the values. This was sometimes unacceptable from the security
viewpoint.

In 4.8 we can filter not only items, but also the values. In theory.
Currently, there are some roadblocks regarding query language(s). Hence,
and also from the general usability point of view, we introduced two
variants of "self" clause: selfDeputyAssignment and selfDeputyRef.
These can be used to easily provide required value filters.

Work in progress. To be discussed.

Related to MID-4938.
  • Loading branch information
mederly committed Aug 24, 2023
1 parent ff4f4bc commit ae84b41
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;

/**
* See https://docs.evolveum.com/midpoint/reference/security/authorization/configuration/selectors/delegator/.
*/
public class DelegatorClause extends SelectorClause {

@NotNull private final ValueSelector selector;
Expand Down Expand Up @@ -69,7 +72,7 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
}

if (allowInactive) {
for (AssignmentType assignment : ((UserType) object).getAssignment()) {
for (AssignmentType assignment : user.getAssignment()) {
ObjectReferenceType assignmentTargetRef = assignment.getTargetRef();
if (assignmentTargetRef == null) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,101 @@

package com.evolveum.midpoint.schema.selector.spec;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismValue;
import javax.xml.namespace.QName;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.schema.RelationRegistry;
import com.evolveum.midpoint.schema.SchemaService;
import com.evolveum.midpoint.schema.selector.eval.FilteringContext;
import com.evolveum.midpoint.schema.selector.eval.MatchingContext;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.util.exception.*;

import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;

import org.jetbrains.annotations.NotNull;

public class SelfClause extends SelectorClause {

@NotNull private final RelationRegistry relationRegistry = SchemaService.get().relationRegistry();
@NotNull private final Matching matching;

private SelfClause(@NotNull Matching matching) {
this.matching = matching;
}

static SelfClause object() {
return new SelfClause(Matching.OBJECT);
}

static SelfClause deputyAssignment() {
return new SelfClause(Matching.DEPUTY_ASSIGNMENT);
}

static SelfClause deputyReference() {
return new SelfClause(Matching.DEPUTY_REFERENCE);
}

@Override
public @NotNull String getName() {
return "self";
return "self(%s)".formatted(matching);
}

@Override
public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
throws SchemaException, ExpressionEvaluationException, CommunicationException, SecurityViolationException,
ConfigurationException, ObjectNotFoundException {
var object = ObjectTypeUtil.asObjectTypeIfPossible(value);
if (object == null) {
traceNotApplicable(ctx, "Not an object");

// "Parsing" the incoming value
String objectOid;
switch (matching) {
case OBJECT -> {
var object = ObjectTypeUtil.asObjectTypeIfPossible(value);
if (object == null) {
traceNotApplicable(ctx, "Not an object");
return false;
}
objectOid = object.getOid();
}
case DEPUTY_ASSIGNMENT -> {
var assignment = toAssignmentIfPossible(value);
if (assignment == null) {
traceNotApplicable(ctx, "Not an assignment");
return false;
}
var targetRef = assignment.getTargetRef();
if (targetRef == null) {
traceNotApplicable(ctx, "Assignment without targetRef");
return false;
}
if (!relationMatches(targetRef, ctx)) {
return false;
}
objectOid = targetRef.getOid();
}
case DEPUTY_REFERENCE -> {
if (!(value instanceof PrismReferenceValue prv)) {
traceNotApplicable(ctx, "Not a reference value");
return false;
}
if (!relationMatches(prv.asReferencable(), ctx)) {
return false;
}
objectOid = prv.getOid();
}
default -> throw new AssertionError(matching);
}
if (objectOid == null) {
traceNotApplicable(ctx, "No OID in object");
return false;
}

// Comparing OIDs (relations were already matched)
String principalOid = ctx.getPrincipalOid();
if (principalOid == null) {
traceNotApplicable(ctx, "no principal OID");
return false;
} else {
String objectOid = object.getOid();
if (principalOid.equals(objectOid)) {
traceApplicable(ctx, "match on principal OID (%s)", objectOid);
return true;
Expand All @@ -54,6 +116,28 @@ public boolean matches(@NotNull PrismValue value, @NotNull MatchingContext ctx)
}
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean relationMatches(@NotNull Referencable referencable, @NotNull MatchingContext ctx) {
QName objectRelation = referencable.getRelation();
if (relationRegistry.isDelegation(objectRelation)) {
return true;
} else {
traceNotApplicable(ctx, "Relation is not a deputy: %s", objectRelation);
return false;
}
}

private AssignmentType toAssignmentIfPossible(PrismValue value) {
if (!(value instanceof PrismContainerValue<?> pcv)) {
return null;
}
var clazz = pcv.getCompileTimeClass();
if (clazz == null || !AssignmentType.class.isAssignableFrom(clazz)) {
return null;
}
return (AssignmentType) pcv.asContainerable();
}

@Override
public boolean toFilter(@NotNull FilteringContext ctx) {
if (!ObjectType.class.isAssignableFrom(ctx.getRestrictedType())) {
Expand Down Expand Up @@ -82,4 +166,8 @@ void addDebugDumpContent(StringBuilder sb, int indent) {
public String toString() {
return "SelfClause{}";
}

enum Matching {
OBJECT, DEPUTY_ASSIGNMENT, DEPUTY_REFERENCE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;

import com.google.common.base.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -183,23 +184,22 @@ public static ValueSelector of(SelectorClause... clauses) {
}

for (SpecialObjectSpecificationType special : new HashSet<>(sBean.getSpecial())) {
if (special == SpecialObjectSpecificationType.SELF) {
clauses.add(new SelfClause());

if (filter != null
|| orgRef != null
|| orgRelation != null
|| roleRelation != null
|| (bean instanceof OwnedObjectSelectorType oBean && oBean.getTenant() != null)
|| !archetypeRefList.isEmpty()) {
throw new ConfigurationException(String.format(
"Both filter/org/role/archetype/tenant and special clause specified in %s",
ConfigErrorReporter.describe(bean)));
}

} else {
throw new ConfigurationException(
"Unsupported special clause: " + special + " in " + ConfigErrorReporter.describe(sBean));
clauses.add(
switch (special) {
case SELF -> SelfClause.object();
case SELF_DEPUTY_ASSIGNMENT -> SelfClause.deputyAssignment();
case SELF_DEPUTY_REF -> SelfClause.deputyReference();
});

if (filter != null
|| orgRef != null
|| orgRelation != null
|| roleRelation != null
|| (bean instanceof OwnedObjectSelectorType oBean && oBean.getTenant() != null)
|| !archetypeRefList.isEmpty()) {
throw new ConfigurationException(String.format(
"Both filter/org/role/archetype/tenant and special clause specified in %s",
ConfigErrorReporter.describe(bean)));
}
}
}
Expand Down Expand Up @@ -295,17 +295,6 @@ public static ValueSelector forType(@NotNull QName typeName) {
return effectiveTypeClause;
}

/** Returns complex type definition for the object, derived from [effective] type clause. */
@NotNull ComplexTypeDefinition getComplexTypeDefinitionRequired() throws ConfigurationException {
TypeClause typeClause = getEffectiveTypeClause();
var typeDef = typeClause.getTypeDefinitionRequired();
if (typeDef instanceof ComplexTypeDefinition ctd) {
return ctd;
} else {
throw new ConfigurationException("Type clause for " + this + " does not correspond to a complex type definition");
}
}

public @NotNull List<SelectorClause> getClauses() {
return clauses;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8640,6 +8640,32 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="selfDeputyAssignment">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
"Deputy" assignment that points to the subject.
May be replaced with something more general in the future.
</xsd:documentation>
<jaxb:typesafeEnumMember name="SELF_DEPUTY_ASSIGNMENT"/>
<a:since>4.8</a:since>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="selfDeputyRef">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
"Deputy" reference that points to the subject.
May be replaced with something more general in the future.
</xsd:documentation>
<jaxb:typesafeEnumMember name="SELF_DEPUTY_REF"/>
<a:since>4.8</a:since>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>

Expand Down

0 comments on commit ae84b41

Please sign in to comment.