Skip to content

Commit

Permalink
add method to get a list of unrestricted authorization subjects to en…
Browse files Browse the repository at this point in the history
…forcers and use it in concierge to calculate read-subjects

Signed-off-by: Johannes Schneider <johannes.schneider@bosch.io>
  • Loading branch information
jokraehe committed Sep 29, 2021
1 parent eb3c00a commit 1417922
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,9 @@ protected static <T extends Signal<T>> T addEffectedReadSubjectsToThingSignal(fi
final Enforcer enforcer) {

final var resourceKey = ResourceKey.newInstance(ThingConstants.ENTITY_TYPE, signal.getResourcePath());
final var effectedSubjects = enforcer.getSubjectsWithPermission(resourceKey, Permission.READ);
final var authorizationSubjects = enforcer.getSubjectsWithUnrestrictedPermission(resourceKey, Permission.READ);
final var newHeaders = DittoHeaders.newBuilder(signal.getDittoHeaders())
.readGrantedSubjects(effectedSubjects.getGranted())
.readRevokedSubjects(effectedSubjects.getRevoked())
.readGrantedSubjects(authorizationSubjects)
.build();

return signal.setDittoHeaders(newHeaders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ public boolean hasPartialPermissions(final ResourceKey resourceKey, final Author
return hasUnrestrictedPermissions(resourceKey, authorizationContext, permissions);
}

@Override
public Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(final ResourceKey resourceKey,
final Permissions permissions) {
return new HashSet<>(authorizationContext.getAuthorizationSubjects());
}

@Override
public JsonObject buildJsonView(final ResourceKey resourceKey,
final Iterable<JsonField> jsonFields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,41 @@ default boolean hasPartialPermissions(final ResourceKey resourceKey,
boolean hasPartialPermissions(ResourceKey resourceKey, AuthorizationContext authorizationContext,
Permissions permissions);

/**
* Returns a set of authorization subjects each of which has all the given permissions granted on the given resource
* or on any sub resource down in the hierarchy.
* Revoked permissions are taken into account.
*
* @param resourceKey the ResourceKey (containing Resource type and path) to use as starting point to check the
* partial permission(s) in the hierarchy for.
* @param permission the permission to check.
* @param furtherPermissions further permissions to check.
* @return the authorization subjects with unrestricted permissions on the passed resourceKey or any other
* resources in the hierarchy below.
* @throws NullPointerException if any argument is {@code null}.
* @since 2.2.0
*/
default Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(ResourceKey resourceKey,
final String permission, final String... furtherPermissions) {
return getSubjectsWithUnrestrictedPermission(resourceKey,
Permissions.newInstance(permission, furtherPermissions));
}

/**
* Returns a set of authorization subjects each of which has all the given permissions granted on the given resource
* or on any sub resource down in the hierarchy.
* Revoked permissions are taken into account.
*
* @param resourceKey the ResourceKey (containing Resource type and path) to use as starting point to check the
* partial permission(s) in the hierarchy for.
* @param permissions the permissions to be checked.
* @return the authorization subjects with unrestricted permissions on the passed resourceKey or any other
* resources in the hierarchy below.
* @throws NullPointerException if any argument is {@code null}.
* @since 2.2.0
*/
Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(ResourceKey resourceKey, Permissions permissions);

/**
* Builds a view of the passed {@code jsonFields} (e.g. a {@link org.eclipse.ditto.json.JsonObject} or a {@link
* org.eclipse.ditto.json.JsonObjectBuilder}) {@code authorizationContext} and {@code permissions}. The resulting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@
import java.util.Map;
import java.util.function.Function;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.NotThreadSafe;

import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.base.model.auth.AuthorizationModelFactory;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.policies.model.enforcers.tree.PointerLocation;
import org.eclipse.ditto.policies.model.enforcers.tree.PolicyTreeNode;
import org.eclipse.ditto.policies.model.enforcers.tree.ResourceNode;
import org.eclipse.ditto.policies.model.enforcers.tree.Visitor;
import org.eclipse.ditto.policies.model.enforcers.EffectedSubjects;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.policies.model.EffectedPermissions;
import org.eclipse.ditto.policies.model.Permissions;
import org.eclipse.ditto.policies.model.enforcers.EffectedSubjects;

/**
* @since 1.1.0
Expand All @@ -43,13 +40,11 @@ final class CollectEffectedSubjectsVisitor implements Visitor<EffectedSubjects>
private final Function<JsonPointer, PointerLocation> pointerLocationEvaluator;
private final EffectedSubjectsBuilder effectedSubjectsBuilder;
private final Collection<ResourceNodeEvaluator> evaluators;
private ResourceNodeEvaluator currentEvaluator;
@Nullable private ResourceNodeEvaluator currentEvaluator;

/**
* Constructs a new {@code CollectEffectedSubjectIdsVisitor} object.
*
* @param resourcePointer
* @param expectedPermissions
* @throws NullPointerException if any argument is {@code null}.
*/
CollectEffectedSubjectsVisitor(final JsonPointer resourcePointer, final Permissions expectedPermissions) {
Expand Down Expand Up @@ -79,7 +74,9 @@ private void visitSubjectNode(final PolicyTreeNode subjectNode) {
}

private void visitResourceNode(final ResourceNode resourceNode) {
currentEvaluator.aggregateWeightedPermissions(resourceNode);
if (null != currentEvaluator) {
currentEvaluator.aggregateWeightedPermissions(resourceNode);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.policies.model.enforcers.tree;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.NotThreadSafe;

import org.eclipse.ditto.base.model.auth.AuthorizationModelFactory;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.policies.model.EffectedPermissions;
import org.eclipse.ditto.policies.model.Permissions;

/**
* @since 2.2.0
*/
@NotThreadSafe
@ParametersAreNonnullByDefault
final class CollectUnrestrictedSubjectsVisitor implements Visitor<Set<AuthorizationSubject>> {

private final Permissions expectedPermissions;

private final Function<JsonPointer, PointerLocation> pointerLocationEvaluator;
private final Collection<ResourceNodeEvaluator> evaluators;
private final Set<AuthorizationSubject> unrestrictedSubjects;
@Nullable private ResourceNodeEvaluator currentEvaluator;

/**
* Constructs a new {@code CollectUnrestrictedSubjectsVisitor} object.
*
* @throws NullPointerException if any argument is {@code null}.
*/
CollectUnrestrictedSubjectsVisitor(final JsonPointer resourcePointer, final Permissions expectedPermissions) {
this.expectedPermissions = expectedPermissions;

pointerLocationEvaluator = new PointerLocationEvaluator(resourcePointer);
evaluators = new HashSet<>();
unrestrictedSubjects = new HashSet<>();
currentEvaluator = null;
}

@Override
public void visitTreeNode(final PolicyTreeNode node) {
if (PolicyTreeNode.Type.SUBJECT == node.getType()) {
visitSubjectNode(node);
} else {
visitResourceNode((ResourceNode) node);
}
}

private void visitSubjectNode(final PolicyTreeNode subjectNode) {
final String currentSubjectId = subjectNode.getName();

// We know that we visit each SubjectNode exactly once.
currentEvaluator = new ResourceNodeEvaluator(currentSubjectId);
evaluators.add(currentEvaluator);
}

private void visitResourceNode(final ResourceNode resourceNode) {
if (null != currentEvaluator) {
currentEvaluator.aggregateWeightedPermissions(resourceNode);
}
}

@Override
public Set<AuthorizationSubject> get() {

// populate effectedSubjectIdsBuilder via side effect
evaluators.forEach(ResourceNodeEvaluator::evaluate);
return new HashSet<>(unrestrictedSubjects);
}

/**
* The permissions have to be evaluated for each subject ID separately as this visitor's task is to collect
* subject IDs. This class aggregates and evaluates the permissions for a particular subject ID. If the subject
* ID is granted or revoked an expected permission it is added to the effectedSubjectIdsBuilder via side effect.
*/
@NotThreadSafe
private final class ResourceNodeEvaluator {

private final AuthorizationSubject subject;
private final WeightedPermissions weightedPermissions;

private ResourceNodeEvaluator(final CharSequence subjectId) {
subject = AuthorizationModelFactory.newAuthSubject(subjectId);
weightedPermissions = new WeightedPermissions();
}

private void aggregateWeightedPermissions(final ResourceNode resourceNode) {
final EffectedPermissions effectedPermissions = resourceNode.getPermissions();
final Permissions grantedPermissions = effectedPermissions.getGrantedPermissions();
final Permissions revokedPermissions = effectedPermissions.getRevokedPermissions();

final PointerLocation pointerLocation = getLocationInRelationToTargetPointer(resourceNode);
if (PointerLocation.ABOVE == pointerLocation || PointerLocation.SAME == pointerLocation) {
weightedPermissions.addGranted(grantedPermissions, resourceNode.getLevel());
weightedPermissions.addRevoked(revokedPermissions, resourceNode.getLevel());
} else if (PointerLocation.BELOW == pointerLocation) {
weightedPermissions.addRevoked(revokedPermissions, resourceNode.getLevel());
}
}

private PointerLocation getLocationInRelationToTargetPointer(final ResourceNode resourceNode) {
return pointerLocationEvaluator.apply(resourceNode.getAbsolutePointer());
}

private void evaluate() {
final Map<String, WeightedPermission> revoked =
weightedPermissions.getRevokedWithHighestWeight(expectedPermissions);
final Map<String, WeightedPermission> granted =
weightedPermissions.getGrantedWithHighestWeight(expectedPermissions);
if (areExpectedPermissionsEffectivelyRevoked(revoked, granted)) {
unrestrictedSubjects.remove(subject);
} else if (areExpectedPermissionsEffectivelyGranted(granted, revoked)) {
unrestrictedSubjects.add(subject);
} // else the expected permissions are undefined
}

private boolean areExpectedPermissionsEffectivelyRevoked(final Map<String, WeightedPermission> revoked,
final Map<String, WeightedPermission> granted) {

if (revoked.size() != expectedPermissions.size()) {
return false;
}

for (final String expectedPermission : expectedPermissions) {
final WeightedPermission revokedPermission = revoked.get(expectedPermission);
final WeightedPermission grantedPermission = granted.get(expectedPermission);
if (null != grantedPermission) {
final int grantedPermissionWeight = grantedPermission.getWeight();
final int revokedPermissionWeight = revokedPermission.getWeight();
if (grantedPermissionWeight > revokedPermissionWeight) {
return false;
}
}
}

return true;
}

private boolean areExpectedPermissionsEffectivelyGranted(final Map<String, WeightedPermission> granted,
final Map<String, WeightedPermission> revoked) {

if (granted.size() != expectedPermissions.size()) {
return false;
}

for (final String expectedPermission : expectedPermissions) {
final WeightedPermission grantedPermission = granted.get(expectedPermission);
final WeightedPermission revokedPermission = revoked.get(expectedPermission);
if (null != revokedPermission) {
final int revokedPermissionWeight = revokedPermission.getWeight();
final int grantedPermissionWeight = grantedPermission.getWeight();
if (revokedPermissionWeight >= grantedPermissionWeight) {
return false;
}
}
}

return true;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,15 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.auth.AuthorizationContext;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonKey;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonObjectBuilder;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.base.model.auth.AuthorizationContext;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.policies.model.enforcers.tree.EffectedResources;
import org.eclipse.ditto.policies.model.enforcers.tree.PolicyTreeNode;
import org.eclipse.ditto.policies.model.enforcers.tree.ResourceNode;
import org.eclipse.ditto.policies.model.enforcers.tree.SubjectNode;
import org.eclipse.ditto.policies.model.enforcers.tree.Visitor;
import org.eclipse.ditto.policies.model.enforcers.EffectedSubjects;
import org.eclipse.ditto.policies.model.enforcers.Enforcer;
import org.eclipse.ditto.policies.model.EffectedPermissions;
import org.eclipse.ditto.policies.model.Permissions;
import org.eclipse.ditto.policies.model.Policy;
Expand All @@ -55,6 +48,8 @@
import org.eclipse.ditto.policies.model.Resources;
import org.eclipse.ditto.policies.model.SubjectId;
import org.eclipse.ditto.policies.model.Subjects;
import org.eclipse.ditto.policies.model.enforcers.EffectedSubjects;
import org.eclipse.ditto.policies.model.enforcers.Enforcer;

/**
* Holds Algorithms to create a policy tree and to perform different policy checks on this tree.
Expand Down Expand Up @@ -224,6 +219,16 @@ public boolean hasPartialPermissions(final ResourceKey resourceKey, final Author
return visitTree(new CheckPartialPermissionsVisitor(resourcePointer, authSubjectIds, permissions));
}

@Override
public Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(final ResourceKey resourceKey,
final Permissions permissions) {

checkResourceKey(resourceKey);
checkPermissions(permissions);
final JsonPointer resourcePointer = createAbsoluteResourcePointer(resourceKey);
return visitTree(new CollectUnrestrictedSubjectsVisitor(resourcePointer, permissions));
}

@Override
public JsonObject buildJsonView(
final ResourceKey resourceKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ public Set<AuthorizationSubject> getSubjectsWithPartialPermission(final Resource
return grantRevokeIndex.getGrantedSubjects(permissions);
}

@Override
public Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(final ResourceKey resourceKey,
final Permissions permissions) {
checkResourceKey(resourceKey);
checkPermissions(permissions);

final PolicyTrie policyTrie = seekWithFallback(resourceKey, bottomUpRevokeTrie, inheritedTrie);
final GrantRevokeIndex grantRevokeIndex = policyTrie.getGrantRevokeIndex();
final Set<AuthorizationSubject> grantedSubjects = grantRevokeIndex.getGrantedSubjects(permissions);
grantedSubjects.removeAll(grantRevokeIndex.getRevokedSubjects(permissions));

return grantedSubjects;
}

@Override
public JsonObject buildJsonView(final ResourceKey resourceKey,
final Iterable<JsonField> jsonFields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public boolean hasPartialPermissions(final ResourceKey resourceKey,
return treeBasedPolicyEvaluator.hasPartialPermissions(resourceKey, authorizationContext, permissions);
}

@Override
public Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(final ResourceKey resourceKey,
final Permissions permissions) {
return treeBasedPolicyEvaluator.getSubjectsWithUnrestrictedPermission(resourceKey, permissions);
}

@Override
public JsonObject buildJsonView(final ResourceKey resourceKey, final Iterable<JsonField> jsonFields,
final AuthorizationContext authorizationContext, final Permissions permissions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public boolean hasPartialPermissions(final ResourceKey resourceKey,
return trieBasedPolicyEvaluator.hasPartialPermissions(resourceKey, authorizationContext, permissions);
}

@Override
public Set<AuthorizationSubject> getSubjectsWithUnrestrictedPermission(final ResourceKey resourceKey,
final Permissions permissions) {
return trieBasedPolicyEvaluator.getSubjectsWithUnrestrictedPermission(resourceKey, permissions);
}

@Override
public JsonObject buildJsonView(final ResourceKey resourceKey, final Iterable<JsonField> jsonFields,
final AuthorizationContext authorizationContext, final Permissions permissions) {
Expand Down

0 comments on commit 1417922

Please sign in to comment.