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 @@ -54,11 +54,9 @@
import org.eclipse.ditto.things.model.ThingId;
import org.eclipse.ditto.things.model.signals.events.ThingEvent;
import org.eclipse.ditto.things.service.common.config.PreDefinedExtraFieldsConfig;
import org.eclipse.ditto.things.service.utils.IndexedReadGrant;
import org.eclipse.ditto.things.service.utils.PartialAccessPathCalculator;
import org.eclipse.ditto.things.service.utils.ReadGrant;
import org.eclipse.ditto.things.service.utils.ReadGrantCollector;
import org.eclipse.ditto.things.service.utils.ReadGrantIndexer;

/**
* Encapsulates functionality in order to enrich ThingEvents with pre-defined {@code extraFields} via DittoHeaders
Expand Down Expand Up @@ -142,15 +140,15 @@ public <T extends DittoHeadersSettable<? extends T>> CompletionStage<T> enrichWi
final DittoHeaders dittoHeaders = enrichedWithPartialAccessPaths.getDittoHeaders();
return buildPredefinedExtraFieldsHeaderReadGrantObject(
policyId, combinedPredefinedExtraFields, thingJson, dittoHeaders)
.thenApply(indexedGrants -> {
.thenApply(readGrant -> {
final String extraFieldsKey = DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS.getKey();
final String readGrantKey = DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.getKey();
final String extraFieldsObjectKey = DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey();

final DittoHeadersBuilder<?, ?> headersBuilder = dittoHeaders.toBuilder()
.putHeader(extraFieldsKey,
buildPredefinedExtraFieldsHeaderList(combinedPredefinedExtraFields))
.putHeader(readGrantKey, indexedGrants.pathsToJson().toString())
.putHeader(readGrantKey, readGrant.toJson().toString())
.putHeader(extraFieldsObjectKey,
buildPredefinedExtraFieldsHeaderObject(thingJson,
combinedPredefinedExtraFields).toString());
Expand Down Expand Up @@ -253,9 +251,11 @@ private CompletionStage<Optional<PolicyEnforcer>> getPolicyEnforcerSafely(@Nulla

/**
* Builds the read grant object using the new helper classes.
* Returns indexed format to reduce header size.
* Returns the ReadGrant with string subject IDs (not indexed integers), because
* DittoCachingSignalEnrichmentFacade.filterAskedForFieldSelectorToGrantedFields() matches
* authorization subject IDs directly against the array values in this header.
*/
private CompletionStage<IndexedReadGrant> buildPredefinedExtraFieldsHeaderReadGrantObject(
private CompletionStage<ReadGrant> buildPredefinedExtraFieldsHeaderReadGrantObject(
@Nullable final PolicyId policyId,
final JsonFieldSelector preDefinedExtraFields,
final JsonObject thingJson,
Expand All @@ -265,18 +265,16 @@ private CompletionStage<IndexedReadGrant> buildPredefinedExtraFieldsHeaderReadGr
if (policyEnforcerOpt.isEmpty()) {
LOGGER.withCorrelationId(dittoHeaders)
.warn("No policy enforcer found for policyId: {}, returning empty read grant object", policyId);
return IndexedReadGrant.empty();
return ReadGrant.empty();
}

final PolicyEnforcer policyEnforcer = policyEnforcerOpt.get();

final ReadGrant readGrant = ReadGrantCollector.collect(
return ReadGrantCollector.collect(
preDefinedExtraFields,
thingJson,
policyEnforcer
);

return ReadGrantIndexer.index(readGrant);
});
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@
import java.util.Map;
import java.util.Set;

import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
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;

/**
* Immutable model representing read grants: which subjects can read which paths.
Expand All @@ -40,5 +47,31 @@ public static ReadGrant empty() {
public boolean isEmpty() {
return pointerToSubjects.isEmpty();
}

/**
* Converts this read grant to a JSON object mapping paths to arrays of subject ID strings.
* <p>
* Example output: {@code {"/attributes": ["connection:foo"], "/definition": ["connection:foo", "connection:bar"]}}
* <p>
* This format is required by
* {@code DittoCachingSignalEnrichmentFacade.filterAskedForFieldSelectorToGrantedFields()},
* which matches authorization subject IDs directly against the array values in the
* {@code PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT} header.
*
* @return JSON object with path keys and string subject ID arrays as values
* @since 3.9.0
*/
public JsonObject toJson() {
final JsonObjectBuilder builder = JsonFactory.newObjectBuilder();
for (final Map.Entry<JsonPointer, Set<String>> entry : pointerToSubjects.entrySet()) {
final JsonKey pathKey = JsonKey.of(entry.getKey().toString());
final JsonArray subjectsArray = entry.getValue().stream()
.sorted()
.map(JsonValue::of)
.collect(JsonCollectors.valuesToArray());
builder.set(pathKey, subjectsArray);
}
return builder.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public void ensureDefinitionIsEnrichedAsPreDefinedFromConfiguration() {
predefinedExtraFields -> predefinedExtraFields.add("/definition"),
preDefinedExtraFieldsReadGrantObject -> preDefinedExtraFieldsReadGrantObject
.set(JsonKey.of("/definition"), JsonArray.newBuilder()
.add(0)
.add(KNOWN_ISSUER_FULL_SUBJECT)
.build()
),
preDefinedFieldsObject -> preDefinedFieldsObject.set("definition", KNOWN_DEFINITION)
Expand All @@ -157,17 +157,17 @@ public void ensureDefinitionAndAdditionalNamespaceSpecificIsEnrichedAsPreDefined
.add("/attributes/public1")
.add("/attributes/private"),
preDefinedExtraFieldsReadGrantObject -> preDefinedExtraFieldsReadGrantObject
.set(JsonKey.of("/attributes/public1"), JsonArray.newBuilder()
.add(0)
.add(1)
.set(JsonKey.of("/definition"), JsonArray.newBuilder()
.add(KNOWN_ISSUER_FULL_SUBJECT)
.build()
)
.set(JsonKey.of("/definition"), JsonArray.newBuilder()
.add(0)
.set(JsonKey.of("/attributes/public1"), JsonArray.newBuilder()
.add(KNOWN_ISSUER_FULL_SUBJECT)
.add(KNOWN_ISSUER_RESTRICTED_SUBJECT)
.build()
)
.set(JsonKey.of("/attributes/private"), JsonArray.newBuilder()
.add(0)
.add(KNOWN_ISSUER_FULL_SUBJECT)
.build()
),
preDefinedFieldsObject -> preDefinedFieldsObject
Expand Down Expand Up @@ -269,17 +269,17 @@ public void ensureConditionBasedEnrichmentAsPreDefinedFromConfiguration() {
.add("/attributes/folder"),
preDefinedExtraFieldsReadGrantObject -> preDefinedExtraFieldsReadGrantObject
.set(JsonKey.of("/attributes/public1"), JsonArray.newBuilder()
.add(0)
.add(1)
.add(KNOWN_ISSUER_FULL_SUBJECT)
.add(KNOWN_ISSUER_RESTRICTED_SUBJECT)
.build()
)
.set(JsonKey.of("/attributes/folder"), JsonArray.newBuilder()
.add(0)
.add(KNOWN_ISSUER_FULL_SUBJECT)
.build()
)
.set(JsonKey.of("/attributes/folder/public"), JsonArray.newBuilder()
.add(2)
.add(1)
.add(KNOWN_ISSUER_ANOTHER_SUBJECT)
.add(KNOWN_ISSUER_RESTRICTED_SUBJECT)
.build()
),
preDefinedFieldsObject -> preDefinedFieldsObject
Expand All @@ -289,6 +289,47 @@ public void ensureConditionBasedEnrichmentAsPreDefinedFromConfiguration() {
);
}

/**
* Reproduces the production scenario: connection with {@code thing:/ READ} requesting
* {@code attributes} as extraFields. Verifies that the read grant header contains string
* subject IDs that {@code DittoCachingSignalEnrichmentFacade} can match against.
*/
@Test
public void readGrantHeaderContainsStringSubjectIdsForRootReadPolicy() {
// GIVEN: a policy with only thing:/ READ (like lora-downlink-processor in production)
final PolicyEnforcerProvider fullAccessProvider = mock(PolicyEnforcerProvider.class);
when(fullAccessProvider.getPolicyEnforcer(KNOWN_POLICY_ID))
.thenReturn(CompletableFuture.completedFuture(Optional.of(PolicyEnforcer.of(FULL_ACCESS_POLICY))));
final ThingEventEnricher sut = new ThingEventEnricher(fullAccessProvider, true);
final var configs = getPreDefinedExtraFieldsConfigs(
"namespaces = [\"org.eclipse.ditto.some\"]\n" +
"extra-fields = [\"attributes\"]"
);

// WHEN: enriched headers are calculated
final CompletionStage<DittoHeaders> resultHeadersStage = calculateEnrichedSignalHeaders(sut, configs);
final DittoHeaders headers = resultHeadersStage.toCompletableFuture().join();

// THEN: the read grant header must contain string subject IDs, not integer indices
final String readGrantHeader = headers.get(
DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.getKey());
assertThat(readGrantHeader)
.as("read grant header should be present")
.isNotNull();
final JsonObject readGrant = JsonFactory.readFrom(readGrantHeader).asObject();
// Verify it contains string subject IDs (e.g. "foo-issuer:full-subject"), not integers
readGrant.forEach(field -> {
final JsonArray subjects = field.getValue().asArray();
subjects.forEach(value ->
assertThat(value.isString())
.as("read grant array values must be strings, not integers: %s", value)
.isTrue()
);
});
// Verify the specific subject is present for /attributes
assertThat(readGrantHeader).contains(KNOWN_ISSUER_FULL_SUBJECT);
}

private List<PreDefinedExtraFieldsConfig> getPreDefinedExtraFieldsConfigs(final String... configurations) {
return Arrays.stream(configurations)
.map(configString ->
Expand Down
Loading
Loading