Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
*/
package org.eclipse.ditto.policies.model.enforcers;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.ditto.base.model.auth.AuthorizationContext;
Expand Down Expand Up @@ -355,4 +358,51 @@ default Set<JsonPointer> getAccessiblePaths(ResourceKey resourceKey, Iterable<Js
permissions);
}

/**
* Returns the set of accessible JSON paths for each of the given {@code authorizationSubjects} based on
* the provided {@code jsonFields}. This batch method avoids redundant JSON flattening when checking
* multiple subjects against the same JSON structure.
*
* @param resourceKey the ResourceKey (containing Resource type and path) to start from.
* @param jsonFields the full JsonFields from which to determine accessible paths.
* @param authorizationSubjects the set of AuthorizationSubjects to check.
* @param permissions the permissions to check.
* @return a map from AuthorizationSubject to their accessible paths (subjects with no accessible paths are omitted).
* @throws NullPointerException if any argument is {@code null}.
* @since 3.9.0
*/
default Map<AuthorizationSubject, Set<JsonPointer>> getAccessiblePathsForSubjects(
final ResourceKey resourceKey, final Iterable<JsonField> jsonFields,
final Set<AuthorizationSubject> authorizationSubjects, final Permissions permissions) {
final Map<AuthorizationSubject, Set<JsonPointer>> result = new HashMap<>();
for (final AuthorizationSubject subject : authorizationSubjects) {
final Set<JsonPointer> paths = getAccessiblePaths(resourceKey, jsonFields, subject, permissions);
if (!paths.isEmpty()) {
result.put(subject, paths);
}
}
return result;
}

/**
* Classifies all authorization subjects into three categories based on their permissions on the given resource:
* unrestricted, partial-only, and effected granted. This performs a single classification pass instead of
* requiring separate calls to {@link #getSubjectsWithPartialPermission}, {@link #getSubjectsWithUnrestrictedPermission},
* and {@link #getSubjectsWithPermission}.
*
* @param resourceKey the ResourceKey (containing Resource type and path) to classify subjects for.
* @param permissions the permissions to check.
* @return the classification of subjects.
* @throws NullPointerException if any argument is {@code null}.
* @since 3.9.0
*/
default SubjectClassification classifySubjects(final ResourceKey resourceKey, final Permissions permissions) {
final Set<AuthorizationSubject> partial = getSubjectsWithPartialPermission(resourceKey, permissions);
final Set<AuthorizationSubject> unrestricted = getSubjectsWithUnrestrictedPermission(resourceKey, permissions);
final EffectedSubjects effected = getSubjectsWithPermission(resourceKey, permissions);
final Set<AuthorizationSubject> partialOnly = new HashSet<>(partial);
partialOnly.removeAll(unrestricted);
return SubjectClassification.of(unrestricted, partialOnly, effected.getGranted());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2026 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;

import java.util.Collections;
import java.util.Objects;
import java.util.Set;

import org.eclipse.ditto.base.model.auth.AuthorizationSubject;

/**
* Immutable result of classifying authorization subjects into three categories based on their permissions
* on a specific resource:
* <ul>
* <li>{@code unrestricted} - subjects with full access, no revocations below</li>
* <li>{@code partialOnly} - subjects with some grants but not unrestricted (partial minus unrestricted)</li>
* <li>{@code effectedGranted} - subjects with grants exactly at the resource level</li>
* </ul>
*
* @since 3.9.0
*/
public final class SubjectClassification {

private static final SubjectClassification EMPTY = new SubjectClassification(
Collections.emptySet(), Collections.emptySet(), Collections.emptySet());

private final Set<AuthorizationSubject> unrestricted;
private final Set<AuthorizationSubject> partialOnly;
private final Set<AuthorizationSubject> effectedGranted;

private SubjectClassification(final Set<AuthorizationSubject> unrestricted,
final Set<AuthorizationSubject> partialOnly,
final Set<AuthorizationSubject> effectedGranted) {
this.unrestricted = Collections.unmodifiableSet(unrestricted);
this.partialOnly = Collections.unmodifiableSet(partialOnly);
this.effectedGranted = Collections.unmodifiableSet(effectedGranted);
}

/**
* Creates a new {@code SubjectClassification} from the given sets.
*
* @param unrestricted subjects with full access, no revocations below.
* @param partialOnly subjects with some grants but not unrestricted.
* @param effectedGranted subjects with grants exactly at the resource level.
* @return the new SubjectClassification.
*/
public static SubjectClassification of(final Set<AuthorizationSubject> unrestricted,
final Set<AuthorizationSubject> partialOnly,
final Set<AuthorizationSubject> effectedGranted) {
return new SubjectClassification(unrestricted, partialOnly, effectedGranted);
}

/**
* Returns an empty {@code SubjectClassification} with no subjects in any category.
*
* @return the empty SubjectClassification.
*/
public static SubjectClassification empty() {
return EMPTY;
}

/**
* Returns the subjects with unrestricted (full) access.
*
* @return an unmodifiable set of unrestricted subjects.
*/
public Set<AuthorizationSubject> getUnrestricted() {
return unrestricted;
}

/**
* Returns the subjects with partial access only (not unrestricted).
*
* @return an unmodifiable set of partial-only subjects.
*/
public Set<AuthorizationSubject> getPartialOnly() {
return partialOnly;
}

/**
* Returns the subjects with grants exactly at the resource level.
*
* @return an unmodifiable set of effected granted subjects.
*/
public Set<AuthorizationSubject> getEffectedGranted() {
return effectedGranted;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final SubjectClassification that = (SubjectClassification) o;
return Objects.equals(unrestricted, that.unrestricted) &&
Objects.equals(partialOnly, that.partialOnly) &&
Objects.equals(effectedGranted, that.effectedGranted);
}

@Override
public int hashCode() {
return Objects.hash(unrestricted, partialOnly, effectedGranted);
}

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"unrestricted=" + unrestricted +
", partialOnly=" + partialOnly +
", effectedGranted=" + effectedGranted +
"]";
}

}
Loading
Loading