Skip to content

Commit

Permalink
align format of policy in index document with thing format
Browse files Browse the repository at this point in the history
Signed-off-by: Dominik Guggemos <dominik.guggemos@bosch.io>
  • Loading branch information
dguggemos committed Apr 19, 2022
1 parent d671946 commit 06deaa3
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,44 +142,21 @@ public final class PersistenceConstants {
public static final String FIELD_POLICY = "p";

/**
* Field name for internal features.
* Special character used as prefix for grant (g) and revoke (r) fields in index document to avoid conflict
* actual fields in a thing. The character is part of the restricted fields (see
* {@link org.eclipse.ditto.base.model.entity.id.RegexPatterns#CONTROL_CHARS}).
*/
public static final String FIELD_INTERNAL = "d";

/**
* Field name of attribute's value.
*/
public static final String FIELD_INTERNAL_VALUE = "v";

/**
* Path to an attribute's value.
*/
public static final String FIELD_PATH_VALUE = FIELD_INTERNAL + DOT + FIELD_INTERNAL_VALUE;

/**
* Field name of a key of an attribute or feature property.
*/
public static final String FIELD_INTERNAL_KEY = "k";

/**
* Path to an attributes/properties key.
*/
public static final String FIELD_PATH_KEY = FIELD_INTERNAL + DOT + FIELD_INTERNAL_KEY;
private static final String FIELD_PERMISSION_PREFIX = Character.valueOf((char) 183).toString();

/**
* Field name for policy read grants.
*/
public static final String FIELD_GRANTED = "g";

/**
* Full path of the granted field.
*/
public static final String FIELD_GRANTED_PATH = FIELD_INTERNAL + DOT + FIELD_GRANTED;
public static final String FIELD_GRANTED = FIELD_PERMISSION_PREFIX + "g";

/**
* Field name for policy read revokes.
*/
public static final String FIELD_REVOKED = "r";
public static final String FIELD_REVOKED = FIELD_PERMISSION_PREFIX + "r";

/**
* Mark a document for deletion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ Optional<Bson> getAuthorizationBson(final JsonPointer pointer) {
}

Optional<Bson> getFeatureWildcardAuthorizationBson(final JsonPointer pointer) {
final Stream<CharSequence> fixedPaths = Stream.of(
final Stream<JsonPointer> fixedPaths = Stream.of(
JsonPointer.empty(), // root grants
JsonPointer.of("/features"), // features grants
JsonPointer.of("/id")); // feature grants
final Stream<CharSequence> collectedPaths = collectPaths(pointer);
final List<CharSequence> allPaths = Stream.concat(fixedPaths, collectedPaths).toList();
final Stream<JsonPointer> collectedPaths = collectPaths(pointer);
final List<JsonPointer> allPaths = Stream.concat(fixedPaths, collectedPaths).toList();
return Optional.ofNullable(authorizationSubjectIds).map(nonNullSubjectsIds -> {
Bson child = null;
for (final CharSequence path : allPaths) {
for (final JsonPointer path : allPaths) {
child = getAuthFilter(nonNullSubjectsIds, path, child);
}
return child;
Expand All @@ -99,13 +99,13 @@ private Bson getAuthFilter(final List<String> authSubjectIds, final JsonPointer
}
}

private Bson getAuthFilter(final Iterable<String> authSubjectIds, final CharSequence pointer,
private Bson getAuthFilter(final Iterable<String> authSubjectIds, final JsonPointer pointer,
@Nullable final Bson child) {

// p.<pointer>.g
final var grant = String.join(DOT, FIELD_POLICY, pointer, FIELD_GRANTED);
// p.<pointer>.r
final var revoke = String.join(DOT, FIELD_POLICY, pointer, FIELD_REVOKED);
// p.<dotted-path>.·g
final var grant = toDottedPath(FIELD_POLICY, pointer, FIELD_GRANTED);
// p.<dotted-path>.·r
final var revoke = toDottedPath(FIELD_POLICY, pointer, FIELD_REVOKED);

final Bson or = child != null
? Filters.or(Filters.in(grant, authSubjectIds), child)
Expand All @@ -119,7 +119,7 @@ private Bson getAuthFilter(final Iterable<String> authSubjectIds, final CharSequ
* @param pointer the pointer for which to collect all contained paths
* @return stream of paths contained in pointer, without root pointer.
*/
private Stream<CharSequence> collectPaths(final JsonPointer pointer) {
private Stream<JsonPointer> collectPaths(final JsonPointer pointer) {
if (pointer.isEmpty()) {
return Stream.empty();
}
Expand All @@ -130,10 +130,19 @@ static String toDottedPath(final CharSequence prefix, final Iterable<JsonKey> js
return String.join(DOT, prefix, toDottedPath(jsonPointer));
}

static String toDottedPath(final CharSequence prefix, final Iterable<JsonKey> jsonPointer,
final CharSequence suffix) {
if (jsonPointer.iterator().hasNext()) {
return String.join(DOT, prefix, toDottedPath(jsonPointer), suffix);
} else {
return String.join(DOT, prefix, suffix);
}
}

static String toDottedPath(final Iterable<JsonKey> jsonPointer) {
return StreamSupport.stream(jsonPointer.spliterator(), false)
.map(k -> KEY_NAME_REVISER.apply(k.toString()))
.collect(Collectors.joining("."));
.collect(Collectors.joining(DOT));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.eclipse.ditto.internal.utils.persistence.mongo.KeyNameReviser;
import org.eclipse.ditto.json.JsonKey;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.policies.api.Permission;
Expand Down Expand Up @@ -86,7 +89,8 @@ BsonDocument forFeature(final String featureId) {
addPermissions(doc, FEATURES_POINTER, thingPermissions.get(FEATURES_POINTER));
}
if (featurePermissions.containsKey(featureId)) {
featurePermissions.get(featureId).forEach((path, permissions) -> addPermissions(doc, path, permissions));
featurePermissions.get(featureId)
.forEach((path, permissions) -> addPermissions(doc, path, permissions));
}
return doc;
}
Expand Down Expand Up @@ -114,11 +118,34 @@ private static void addPermissions(final BsonDocument doc,
if (!permissions.second().isEmpty()) {
innerDoc.put(FIELD_REVOKED, toSubjectsBson(permissions.second()));
}
doc.put(toBsonKey(path), innerDoc);
setDocumentAtPath(doc, path, innerDoc);
}

private static String toBsonKey(final JsonPointer path) {
return KEY_NAME_REVISER.apply(path.toString());
private static void setDocumentAtPath(final BsonDocument doc, final JsonPointer path, final BsonDocument innerDoc) {
final BsonDocument docAtPath;
// find/add the document where to append innerDoc
if (path.isEmpty()) {
docAtPath = doc;
} else {
docAtPath = StreamSupport.stream(path.spliterator(), false)
.reduce(doc, (d, jsonKey) -> {
final String bsonKey = toBsonKey(jsonKey);
final BsonValue child = d.get(bsonKey);
if (child == null) {
final BsonDocument newChild = new BsonDocument();
d.append(bsonKey, newChild);
return newChild;
} else {
return child.asDocument();
}
}, EvaluatedPolicy::merge);
}
// append all fields of innerDoc
innerDoc.forEach(docAtPath::append);
}

private static String toBsonKey(final JsonKey key) {
return KEY_NAME_REVISER.apply(key.toString());
}

private static BsonArray toSubjectsBson(final Set<String> subjects) {
Expand Down Expand Up @@ -186,4 +213,9 @@ private static void addPathToFeaturePermissions(
addPathToPermissions(map, featureLevelPointer, isGrant, subjects);
}
}

private static BsonDocument merge(final BsonDocument b1, final BsonDocument b2) {
b1.forEach(b2::append);
return b1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.conversions.Bson;
import org.eclipse.ditto.thingsearch.service.persistence.PersistenceConstants;

import com.mongodb.reactivestreams.client.MongoClients;

Expand Down Expand Up @@ -63,13 +64,20 @@ static void assertAuthFilter(final BsonValue authFilter, final List<String> subj
}

private static String revokePath(final String path) {
return String.join(".", "p", path, "r");
return toPolicyPath(path, PersistenceConstants.FIELD_REVOKED);
}

private static String grantPath(final String path) {
return String.join(".", "p", path, "g");
return toPolicyPath(path, PersistenceConstants.FIELD_GRANTED);
}

private static String toPolicyPath(final String path, final String lastField) {
if (path.isEmpty()) {
return String.join(PersistenceConstants.DOT, PersistenceConstants.FIELD_POLICY, lastField);
} else {
return String.join(PersistenceConstants.DOT, PersistenceConstants.FIELD_POLICY, path, lastField);
}
}

private static BsonDocument nin(final Collection<String> subjects) {
return new BsonDocument("$nin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void testAttribute() {
final BsonValue existsFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.attributes.a1", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/attributes/a1", "/attributes", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "attributes.a1", "attributes", "");
}

@Test
Expand All @@ -59,7 +59,7 @@ public void testFeature() {
final BsonValue existsFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.features.f1", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/features/f1", "/features", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "features.f1", "features", "");
}

@Test
Expand All @@ -69,8 +69,8 @@ public void testFeatureProperties() {
final BsonValue existsFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.features.f1.properties", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/features/f1/properties","/features/f1",
"/features", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "features.f1.properties","features.f1",
"features", "");
}

@Test
Expand All @@ -80,8 +80,8 @@ public void testFeatureDesiredProperties() {
final BsonValue existsFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.features.f1.desiredProperties", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/features/f1/desiredProperties","/features/f1",
"/features", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "features.f1.desiredProperties","features.f1",
"features", "");
}

@Test
Expand All @@ -92,11 +92,11 @@ public void testFeatureProperty() {
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.features.f1.properties.temperature", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/features/f1/properties/temperature",
"/features/f1/properties",
"/features/f1",
"/features",
"/");
"features.f1.properties.temperature",
"features.f1.properties",
"features.f1",
"features",
"");
}

@Test
Expand All @@ -107,11 +107,11 @@ public void testFeatureDesiredProperty() {
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t.features.f1.desiredProperties.targetTemperature", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/features/f1/desiredProperties/targetTemperature",
"/features/f1/desiredProperties",
"/features/f1",
"/features",
"/");
"features.f1.desiredProperties.targetTemperature",
"features.f1.desiredProperties",
"features.f1",
"features",
"");
}
@Test
public void testWildcardFeatureProperty() {
Expand All @@ -122,11 +122,11 @@ public void testWildcardFeatureProperty() {
final BsonValue authFilter = elemMatchAnd.get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("properties.temperature", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/properties/temperature",
"/properties",
"/id",
"/features",
"/");
"properties.temperature",
"properties",
"id",
"features",
"");
}

@Test
Expand All @@ -137,8 +137,8 @@ public void testSimplePropertyWithLeadingSlash() {
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(existsFilter).isEqualTo(new BsonDocument("t._modified", EXISTS));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/_modified",
"/");
"_modified",
"");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void testAttribute() {
final BsonValue valueFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(valueFilter).isEqualTo(new BsonDocument("t.attributes.a1", BSON_VALUE));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/attributes/a1", "/attributes", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "attributes.a1", "attributes", "");
}

@Test
Expand All @@ -69,11 +69,11 @@ public void testFeatureProperty() {
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(valueFilter).isEqualTo(new BsonDocument("t.features.f1.properties.temperature", BSON_VALUE));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/features/f1/properties/temperature",
"/features/f1/properties",
"/features/f1",
"/features",
"/");
"features.f1.properties.temperature",
"features.f1.properties",
"features.f1",
"features",
"");
}

@Test
Expand All @@ -84,11 +84,11 @@ public void testFeatureDesiredProperty() {
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(valueFilter).isEqualTo(new BsonDocument("t.features.f1.desiredProperties.targetTemperature", BSON_VALUE));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/features/f1/desiredProperties/targetTemperature",
"/features/f1/desiredProperties",
"/features/f1",
"/features",
"/");
"features.f1.desiredProperties.targetTemperature",
"features.f1.desiredProperties",
"features.f1",
"features",
"");
}
@Test
public void testWildcardFeatureProperty() {
Expand All @@ -99,11 +99,11 @@ public void testWildcardFeatureProperty() {
final BsonValue authFilter = elemMatchAnd.get(1);
assertThat(valueFilter).isEqualTo(new BsonDocument("properties.temperature", BSON_VALUE));
assertAuthFilter(authFilter, List.of("subject1", "subject2"),
"/properties/temperature",
"/properties",
"/id",
"/features",
"/");
"properties.temperature",
"properties",
"id",
"features",
"");
}

@Test
Expand All @@ -113,7 +113,7 @@ public void testSimplePropertyWithLeadingSlash() {
final BsonValue valueFilter = document.getArray(AND).get(0);
final BsonValue authFilter = document.getArray(AND).get(1);
assertThat(valueFilter).isEqualTo(new BsonDocument("t._modified", BSON_VALUE));
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "/_modified", "/");
assertAuthFilter(authFilter, List.of("subject1", "subject2"), "_modified", "");
}

@Test
Expand Down
Loading

0 comments on commit 06deaa3

Please sign in to comment.