diff --git a/model/placeholders/src/main/java/org/eclipse/ditto/model/placeholders/ExpressionResolver.java b/model/placeholders/src/main/java/org/eclipse/ditto/model/placeholders/ExpressionResolver.java index 0b1af97179..6a5c4582e7 100644 --- a/model/placeholders/src/main/java/org/eclipse/ditto/model/placeholders/ExpressionResolver.java +++ b/model/placeholders/src/main/java/org/eclipse/ditto/model/placeholders/ExpressionResolver.java @@ -64,7 +64,7 @@ default PipelineElement resolve(final String expressionTemplate) { * Resolves a complete expression template starting with a {@link Placeholder} followed by optional pipeline stages * (e.g. functions). Keep unresolvable expressions as is. * - * @param expressionTemplate the expressionTemplate to resolve {@link org.eclipse.ditto.model.placeholders.Placeholder}s and and execute optional + * @param expressionTemplate the expressionTemplate to resolve {@link Placeholder}s and and execute optional * pipeline stages * @return the resolved String, a signifier for resolution failure, or one for deletion. * @throws PlaceholderFunctionTooComplexException thrown if the {@code expressionTemplate} contains a placeholder diff --git a/services/concierge/enforcement/src/test/java/org/eclipse/ditto/services/concierge/enforcement/PolicyCommandEnforcementTest.java b/services/concierge/enforcement/src/test/java/org/eclipse/ditto/services/concierge/enforcement/PolicyCommandEnforcementTest.java index 2f5775e66e..454d6f5d28 100644 --- a/services/concierge/enforcement/src/test/java/org/eclipse/ditto/services/concierge/enforcement/PolicyCommandEnforcementTest.java +++ b/services/concierge/enforcement/src/test/java/org/eclipse/ditto/services/concierge/enforcement/PolicyCommandEnforcementTest.java @@ -563,7 +563,7 @@ public void activateTopLevelTokenIntegrationWithoutPermission() { final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final Instant expiry = Instant.now(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, expiry, DITTO_HEADERS), + ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), expiry, DITTO_HEADERS), List.of()); enforcer.tell(command, getRef()); @@ -579,7 +579,7 @@ public void deactivateTopLevelTokenIntegrationWithoutPermission() { new TestKit(system) {{ final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, DITTO_HEADERS), + DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), DITTO_HEADERS), List.of()); enforcer.tell(command, getRef()); @@ -596,7 +596,7 @@ public void activateTokenIntegrationWithoutPermission() { final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final Instant expiry = Instant.now(); final ActivateTokenIntegration activateTokenIntegration = - ActivateTokenIntegration.of(POLICY_ID, Label.of("forbidden"), subjectId, expiry, DITTO_HEADERS); + ActivateTokenIntegration.of(POLICY_ID, Label.of("forbidden"), Collections.singleton(subjectId), expiry, DITTO_HEADERS); enforcer.tell(activateTokenIntegration, getRef()); @@ -611,7 +611,7 @@ public void deactivateTokenIntegrationWithoutPermission() { new TestKit(system) {{ final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final DeactivateTokenIntegration deactivateTokenIntegration = - DeactivateTokenIntegration.of(POLICY_ID, Label.of("forbidden"), subjectId, DITTO_HEADERS); + DeactivateTokenIntegration.of(POLICY_ID, Label.of("forbidden"), Collections.singleton(subjectId), DITTO_HEADERS); enforcer.tell(deactivateTokenIntegration, getRef()); @@ -627,7 +627,7 @@ public void activateTopLevelTokenIntegration() { final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final Instant expiry = Instant.now(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, expiry, DITTO_HEADERS), + ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), expiry, DITTO_HEADERS), List.of() ); @@ -639,7 +639,7 @@ public void activateTopLevelTokenIntegration() { final TopLevelPolicyActionCommand forwarded = policiesShardRegionProbe.expectMsgClass(TopLevelPolicyActionCommand.class); assertThat(forwarded).isEqualTo(TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, expiry, DITTO_HEADERS), + ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), expiry, DITTO_HEADERS), List.of(Label.of("allowed")) )); }}; @@ -650,7 +650,7 @@ public void deactivateTopLevelTokenIntegration() { new TestKit(system) {{ final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, DITTO_HEADERS), + DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), DITTO_HEADERS), List.of() ); @@ -662,7 +662,7 @@ public void deactivateTopLevelTokenIntegration() { final TopLevelPolicyActionCommand forwarded = policiesShardRegionProbe.expectMsgClass(TopLevelPolicyActionCommand.class); assertThat(forwarded).isEqualTo(TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, DITTO_HEADERS), + DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), Collections.singleton(subjectId), DITTO_HEADERS), List.of(Label.of("allowed")) )); }}; @@ -674,7 +674,7 @@ public void activateTokenIntegration() { final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final Instant expiry = Instant.now(); final ActivateTokenIntegration activateTokenIntegration = - ActivateTokenIntegration.of(POLICY_ID, Label.of("allowed"), subjectId, expiry, DITTO_HEADERS); + ActivateTokenIntegration.of(POLICY_ID, Label.of("allowed"), Collections.singleton(subjectId), expiry, DITTO_HEADERS); enforcer.tell(activateTokenIntegration, getRef()); @@ -692,7 +692,7 @@ public void deactivateTokenIntegration() { new TestKit(system) {{ final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject"); final DeactivateTokenIntegration deactivateTokenIntegration = - DeactivateTokenIntegration.of(POLICY_ID, Label.of("allowed"), subjectId, DITTO_HEADERS); + DeactivateTokenIntegration.of(POLICY_ID, Label.of("allowed"), Collections.singleton(subjectId), DITTO_HEADERS); enforcer.tell(deactivateTokenIntegration, getRef()); diff --git a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactory.java b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactory.java index bcdd025176..d834282af4 100755 --- a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactory.java +++ b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactory.java @@ -12,6 +12,10 @@ */ package org.eclipse.ditto.services.gateway.endpoints.routes.policies; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; + import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.jwt.JsonWebToken; import org.eclipse.ditto.model.placeholders.ExpressionResolver; @@ -42,11 +46,15 @@ public static OAuthTokenIntegrationSubjectIdFactory of(final OAuthConfig oAuthCo } @Override - public SubjectId getSubjectId(final DittoHeaders dittoHeaders, final JsonWebToken jwt) { + public Set getSubjectIds(final DittoHeaders dittoHeaders, final JsonWebToken jwt) { final ExpressionResolver expressionResolver = PlaceholderFactory.newExpressionResolver( PlaceholderFactory.newPlaceholderResolver(PlaceholderFactory.newHeadersPlaceholder(), dittoHeaders), PlaceholderFactory.newPlaceholderResolver(JwtPlaceholder.getInstance(), jwt) ); - return SubjectId.newInstance(expressionResolver.resolvePartially(subjectTemplate)); + final String issuerWithSubject = expressionResolver.resolvePartially(subjectTemplate); + return TokenIntegrationSubjectIdFactory.expandJsonArraysInResolvedSubject(issuerWithSubject) + .map(SubjectId::newInstance) + .collect(Collectors.toCollection(LinkedHashSet::new)); } + } diff --git a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRoute.java b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRoute.java index d43c76ac9b..cfc3c9674d 100755 --- a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRoute.java +++ b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRoute.java @@ -17,6 +17,7 @@ import java.time.Instant; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import org.eclipse.ditto.json.JsonFactory; @@ -198,19 +199,19 @@ private Route policyActions(final RequestContext ctx, final DittoHeaders dittoHe private TopLevelPolicyActionCommand topLevelActivateTokenIntegration(final DittoHeaders dittoHeaders, final PolicyId policyId, final JsonWebToken jwt) { - final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt); + final Set subjectIds = tokenIntegrationSubjectIdFactory.getSubjectIds(dittoHeaders, jwt); final Instant expiry = jwt.getExpirationTime(); final ActivateTokenIntegration activateTokenIntegration = - ActivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectId, expiry, dittoHeaders); + ActivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectIds, expiry, dittoHeaders); return TopLevelPolicyActionCommand.of(activateTokenIntegration, List.of()); } private TopLevelPolicyActionCommand topLevelDeactivateTokenIntegration(final DittoHeaders dittoHeaders, final PolicyId policyId, final JsonWebToken jwt) { - final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt); + final Set subjectIds = tokenIntegrationSubjectIdFactory.getSubjectIds(dittoHeaders, jwt); final DeactivateTokenIntegration deactivateTokenIntegration = - DeactivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectId, dittoHeaders); + DeactivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectIds, dittoHeaders); return TopLevelPolicyActionCommand.of(deactivateTokenIntegration, List.of()); } diff --git a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PolicyEntriesRoute.java b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PolicyEntriesRoute.java index 3a1fd7656b..9da84a9096 100755 --- a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PolicyEntriesRoute.java +++ b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PolicyEntriesRoute.java @@ -15,6 +15,8 @@ import static org.eclipse.ditto.model.base.exceptions.DittoJsonException.wrapJsonRuntimeException; import static org.eclipse.ditto.services.gateway.endpoints.routes.policies.PoliciesRoute.extractJwt; +import java.util.Set; + import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.model.base.headers.DittoHeaders; @@ -384,14 +386,14 @@ private Route policyEntryActions(final RequestContext ctx, final DittoHeaders di private ActivateTokenIntegration activateTokenIntegration(final DittoHeaders dittoHeaders, final PolicyId policyId, final String label, final JsonWebToken jwt) { - final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt); - return ActivateTokenIntegration.of(policyId, Label.of(label), subjectId, jwt.getExpirationTime(), dittoHeaders); + final Set subjectIds = tokenIntegrationSubjectIdFactory.getSubjectIds(dittoHeaders, jwt); + return ActivateTokenIntegration.of(policyId, Label.of(label), subjectIds, jwt.getExpirationTime(), dittoHeaders); } private DeactivateTokenIntegration deactivateTokenIntegration(final DittoHeaders dittoHeaders, final PolicyId policyId, final String label, final JsonWebToken jwt) { - final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt); - return DeactivateTokenIntegration.of(policyId, Label.of(label), subjectId, dittoHeaders); + final Set subjectIds = tokenIntegrationSubjectIdFactory.getSubjectIds(dittoHeaders, jwt); + return DeactivateTokenIntegration.of(policyId, Label.of(label), subjectIds, dittoHeaders); } private static ResourceKey resourceKeyFromUnmatchedPath(final String resource) { diff --git a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/TokenIntegrationSubjectIdFactory.java b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/TokenIntegrationSubjectIdFactory.java index c8c8a52f64..e2c1230f66 100755 --- a/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/TokenIntegrationSubjectIdFactory.java +++ b/services/gateway/endpoints/src/main/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/TokenIntegrationSubjectIdFactory.java @@ -12,6 +12,13 @@ */ package org.eclipse.ditto.services.gateway.endpoints.routes.policies; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.eclipse.ditto.json.JsonArray; +import org.eclipse.ditto.json.JsonValue; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.jwt.JsonWebToken; import org.eclipse.ditto.model.policies.SubjectId; @@ -22,12 +29,48 @@ public interface TokenIntegrationSubjectIdFactory { /** - * Compute the token integration subject ID from headers and JWT. + * Compiled Pattern of a string containing any unresolved JsonArray-String notations inside. + */ + Pattern JSON_ARRAY_PATTERN = Pattern.compile(".*(\\[\".*\"])+?.*"); + + /** + * Compute the token integration subject IDs from headers and JWT. * * @param dittoHeaders the Ditto headers. * @param jwt the JWT. - * @return the computed subject ID. + * @return the computed subject IDs. */ - SubjectId getSubjectId(DittoHeaders dittoHeaders, JsonWebToken jwt); + Set getSubjectIds(DittoHeaders dittoHeaders, JsonWebToken jwt); + /** + * Checks whether the passed {@code resolvedSubject} (resolved via JWT and header placeholder mechanism) contains + * JsonArrays ({@code ["..."]} and expands those JsonArrays to multiple resolved subjects returned as resulting + * stream of this operation. + *

+ * Is able to handle an arbitrary amount of JsonArrays in the passed resolvedSubjects. + * + * @param resolvedSubject the resolved subjects potentially containing JsonArrays as JsonArray-String values. + * @return a stream of a single subject when the passed in {@code resolvedSubject} did not contain any + * JsonArray-String notation or else a stream of multiple subjects with the JsonArrays being resolved to multiple + * results of the stream. + */ + static Stream expandJsonArraysInResolvedSubject(final String resolvedSubject) { + final Matcher jsonArrayMatcher = JSON_ARRAY_PATTERN.matcher(resolvedSubject); + final int group = 1; + if (jsonArrayMatcher.matches()) { + final String beforeMatched = resolvedSubject.substring(0, jsonArrayMatcher.start(group)); + final String matchedStr = + resolvedSubject.substring(jsonArrayMatcher.start(group), jsonArrayMatcher.end(group)); + final String afterMatched = resolvedSubject.substring(jsonArrayMatcher.end(group)); + return JsonArray.of(matchedStr).stream() + .filter(JsonValue::isString) + .map(JsonValue::asString) + .flatMap(arrayStringElem -> expandJsonArraysInResolvedSubject(beforeMatched) // recurse! + .flatMap(before -> expandJsonArraysInResolvedSubject(afterMatched) // recurse! + .map(after -> before.concat(arrayStringElem).concat(after)) + ) + ); + } + return Stream.of(resolvedSubject); + } } diff --git a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/DummyJwt.java b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/DummyJwt.java index 9eccb2599c..a6d14a7ee3 100644 --- a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/DummyJwt.java +++ b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/DummyJwt.java @@ -16,6 +16,7 @@ import java.time.Instant; import java.util.List; +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.model.jwt.Audience; import org.eclipse.ditto.model.jwt.JsonWebToken; @@ -42,6 +43,8 @@ public JsonObject getBody() { return JsonObject.newBuilder() .set("sub", "dummy-subject") .set("iss", "dummy-issuer") + .set("aud", JsonArray.of("aud-1", "aud-2")) + .set("foo", JsonArray.of("bar1", "bar2", "bar3")) .build(); } diff --git a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactoryTest.java b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactoryTest.java index c77cfe6c1a..b16598dc8d 100644 --- a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactoryTest.java +++ b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/OAuthTokenIntegrationSubjectIdFactoryTest.java @@ -12,6 +12,8 @@ */ package org.eclipse.ditto.services.gateway.endpoints.routes.policies; +import java.util.Set; + import org.assertj.core.api.Assertions; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.policies.SubjectId; @@ -33,7 +35,9 @@ public void resolveSubjectId() { final DittoHeaders dittoHeaders = DittoHeaders.newBuilder() .putHeader("owner", "Ditto") .build(); - final SubjectId subjectId = sut.getSubjectId(dittoHeaders, new DummyJwt()); + final Set subjectIds = sut.getSubjectIds(dittoHeaders, new DummyJwt()); + Assertions.assertThat(subjectIds).hasSize(1); + final SubjectId subjectId = subjectIds.stream().findFirst().orElseThrow(); Assertions.assertThat(subjectId.getIssuer()).hasToString("dummy-issuer"); Assertions.assertThat(subjectId).hasToString("dummy-issuer:static-part:dummy-subject:Ditto"); } @@ -45,10 +49,40 @@ public void resolveSubjectIdWithUnresolvedPlaceholder() { final DittoHeaders dittoHeaders = DittoHeaders.newBuilder() .putHeader("my-custom-header", "foo") .build(); - final SubjectId subjectId = sut.getSubjectId(dittoHeaders, new DummyJwt()); + final Set subjectIds = sut.getSubjectIds(dittoHeaders, new DummyJwt()); + Assertions.assertThat(subjectIds).hasSize(1); + final SubjectId subjectId = subjectIds.stream().findFirst().orElseThrow(); Assertions.assertThat(subjectId).hasToString("dummy-issuer:{{policy-entry:label}}:dummy-subject:foo"); } + @Test + public void resolveSubjectIdWithJsonArrayJwtClaim() { + final String subjectPattern = "integration:{{jwt:aud}}:static"; + final OAuthTokenIntegrationSubjectIdFactory sut = createSut(subjectPattern); + final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().build(); + final Set subjectIds = sut.getSubjectIds(dittoHeaders, new DummyJwt()); + Assertions.assertThat(subjectIds).hasSize(2).containsExactly( + SubjectId.newInstance("integration:aud-1:static"), + SubjectId.newInstance("integration:aud-2:static") + ); + } + + @Test + public void resolveSubjectIdWithMultipleJsonArrayJwtClaims() { + final String subjectPattern = "{{jwt:iss}}:{{jwt:aud}}:static:{{jwt:foo}}"; + final OAuthTokenIntegrationSubjectIdFactory sut = createSut(subjectPattern); + final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().build(); + final Set subjectIds = sut.getSubjectIds(dittoHeaders, new DummyJwt()); + Assertions.assertThat(subjectIds).hasSize(6).containsExactly( + SubjectId.newInstance("dummy-issuer:aud-1:static:bar1"), + SubjectId.newInstance("dummy-issuer:aud-2:static:bar1"), + SubjectId.newInstance("dummy-issuer:aud-1:static:bar2"), + SubjectId.newInstance("dummy-issuer:aud-2:static:bar2"), + SubjectId.newInstance("dummy-issuer:aud-1:static:bar3"), + SubjectId.newInstance("dummy-issuer:aud-2:static:bar3") + ); + } + private static OAuthTokenIntegrationSubjectIdFactory createSut(final String subjectPattern) { final DefaultOAuthConfig oAuthConfig = DefaultOAuthConfig.of( ConfigFactory.empty().withValue("oauth.token-integration-subject", diff --git a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRouteTest.java b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRouteTest.java index 5f02b39516..2e40b2088d 100644 --- a/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRouteTest.java +++ b/services/gateway/endpoints/src/test/java/org/eclipse/ditto/services/gateway/endpoints/routes/policies/PoliciesRouteTest.java @@ -152,7 +152,8 @@ public void activateTopLevelTokenIntegration() { .assertEntity(TopLevelPolicyActionCommand.of( ActivateTokenIntegration.of(PolicyId.of("ns:n"), Label.of("-"), - SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"), + List.of(SubjectId.newInstance("integration:{{policy-entry:label}}:aud-1"), + SubjectId.newInstance("integration:{{policy-entry:label}}:aud-2")), DummyJwt.EXPIRY, DittoHeaders.empty()), List.of() @@ -166,7 +167,8 @@ public void deactivateTopLevelTokenIntegration() { .assertEntity(TopLevelPolicyActionCommand.of( DeactivateTokenIntegration.of(PolicyId.of("ns:n"), Label.of("-"), - SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"), + List.of(SubjectId.newInstance("integration:{{policy-entry:label}}:aud-1"), + SubjectId.newInstance("integration:{{policy-entry:label}}:aud-2")), DittoHeaders.empty()), List.of() ).toJsonString()); @@ -186,7 +188,8 @@ public void activateTokenIntegrationForEntry() { .assertStatusCode(StatusCodes.OK) .assertEntity(ActivateTokenIntegration.of(PolicyId.of("ns:n"), Label.of("label"), - SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"), + List.of(SubjectId.newInstance("integration:{{policy-entry:label}}:aud-1"), + SubjectId.newInstance("integration:{{policy-entry:label}}:aud-2")), DummyJwt.EXPIRY, DittoHeaders.empty() ).toJsonString()); @@ -199,7 +202,8 @@ public void deactivateTokenIntegrationForEntry() { .assertStatusCode(StatusCodes.OK) .assertEntity(DeactivateTokenIntegration.of(PolicyId.of("ns:n"), Label.of("label"), - SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"), + List.of(SubjectId.newInstance("integration:{{policy-entry:label}}:aud-1"), + SubjectId.newInstance("integration:{{policy-entry:label}}:aud-2")), DittoHeaders.empty() ).toJsonString()); } diff --git a/services/gateway/starter/src/main/resources/gateway.conf b/services/gateway/starter/src/main/resources/gateway.conf index 28a6712531..ddac2574dc 100755 --- a/services/gateway/starter/src/main/resources/gateway.conf +++ b/services/gateway/starter/src/main/resources/gateway.conf @@ -176,7 +176,7 @@ ditto { } # template for subject to inject - token-integration-subject = "{{jwt:iss}}:{{policy-entry:label}}:{{jwt:sub}}" + token-integration-subject = "integration:{{policy-entry:label}}:{{jwt:aud}}" token-integration-subject = ${?OAUTH_TOKEN_INTEGRATION_SUBJECT} } diff --git a/services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/OAuthConfig.java b/services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/OAuthConfig.java index c0fc42ae65..8952882fe0 100644 --- a/services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/OAuthConfig.java +++ b/services/gateway/util/src/main/java/org/eclipse/ditto/services/gateway/util/config/security/OAuthConfig.java @@ -61,7 +61,7 @@ enum OAuthConfigValue implements KnownConfigValue { PROTOCOL("protocol", "https"), OPENID_CONNECT_ISSUERS("openid-connect-issuers", Collections.emptyMap()), OPENID_CONNECT_ISSUERS_EXTENSION("openid-connect-issuers-extension", Collections.emptyMap()), - TOKEN_INTEGRATION_SUBJECT("token-integration-subject", "{{jwt:iss}}:{{policy-entry:label}}:{{jwt:sub}}"); + TOKEN_INTEGRATION_SUBJECT("token-integration-subject", "integration:{{policy-entry:label}}:{{jwt:aud}}"); private final String path; private final Object defaultValue; diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolver.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolver.java index d693952fd7..a7a4911892 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolver.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolver.java @@ -12,6 +12,12 @@ */ package org.eclipse.ditto.services.policies.persistence.actors.resolvers; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.ditto.model.placeholders.ExpressionResolver; import org.eclipse.ditto.model.placeholders.PlaceholderFactory; import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException; import org.eclipse.ditto.model.policies.PolicyEntry; @@ -29,20 +35,21 @@ public DefaultSubjectIdFromActionResolver() { } @Override - public SubjectId resolveSubjectId(final PolicyEntry entry, final PolicyActionCommand command) { - return resolveSubjectId(entry, command.getSubjectId()); + public Set resolveSubjectIds(final PolicyEntry entry, final PolicyActionCommand command) { + return resolveSubjectId(entry, command.getSubjectIds()); } - SubjectId resolveSubjectId(final PolicyEntry entry, final SubjectId subjectIdWithPlaceholder) { - return PlaceholderFactory.newExpressionResolver(PlaceholderFactory.newPlaceholderResolver( - PlaceholderFactory.newPolicyEntryPlaceholder(), - entry) - ) - .resolve(subjectIdWithPlaceholder.toString()) - .toOptional() - .map(SubjectId::newInstance) - .orElseThrow(() -> - UnresolvedPlaceholderException.newBuilder(subjectIdWithPlaceholder.toString()).build()); + Set resolveSubjectId(final PolicyEntry entry, final Collection subjectIdsWithPlaceholder) { + final ExpressionResolver expressionResolver = PlaceholderFactory.newExpressionResolver( + PlaceholderFactory.newPlaceholderResolver(PlaceholderFactory.newPolicyEntryPlaceholder(), entry) + ); + return subjectIdsWithPlaceholder.stream() + .map(subjectId -> expressionResolver + .resolve(subjectId.toString()) + .toOptional() + .map(SubjectId::newInstance) + .orElseThrow(() -> UnresolvedPlaceholderException.newBuilder(subjectId.toString()).build())) + .collect(Collectors.toCollection(LinkedHashSet::new)); } } diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/SubjectIdFromActionResolver.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/SubjectIdFromActionResolver.java index f67c75934b..ee978efbe3 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/SubjectIdFromActionResolver.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/SubjectIdFromActionResolver.java @@ -12,6 +12,8 @@ */ package org.eclipse.ditto.services.policies.persistence.actors.resolvers; +import java.util.Set; + import org.eclipse.ditto.model.policies.PolicyEntry; import org.eclipse.ditto.model.policies.SubjectId; import org.eclipse.ditto.signals.commands.policies.actions.PolicyActionCommand; @@ -22,13 +24,14 @@ public interface SubjectIdFromActionResolver { /** - * Resolves a subject ID. + * Resolves subject IDs. * * @param entry the policy entry. * @param command the policy action command. - * @return the subject ID after resolution. - * @throws org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException if the subject ID contains unsupported placeholders. - * @throws org.eclipse.ditto.model.policies.SubjectIdInvalidException if the resolved subject ID is invalid. + * @return the subject IDs after resolution. + * @throws org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException if one of the subject IDs contains + * unsupported placeholders. + * @throws org.eclipse.ditto.model.policies.SubjectIdInvalidException if one of the resolved subject IDs is invalid. */ - SubjectId resolveSubjectId(PolicyEntry entry, PolicyActionCommand command); + Set resolveSubjectIds(PolicyEntry entry, PolicyActionCommand command); } diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategy.java index 8b4bf80773..8c99a62335 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategy.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategy.java @@ -15,7 +15,12 @@ import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull; import java.text.MessageFormat; +import java.time.Instant; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -26,6 +31,7 @@ import org.eclipse.ditto.model.policies.Label; import org.eclipse.ditto.model.policies.PoliciesModelFactory; import org.eclipse.ditto.model.policies.Policy; +import org.eclipse.ditto.model.policies.PolicyBuilder; import org.eclipse.ditto.model.policies.PolicyEntry; import org.eclipse.ditto.model.policies.PolicyId; import org.eclipse.ditto.model.policies.Subject; @@ -41,6 +47,7 @@ import org.eclipse.ditto.signals.events.policies.PolicyActionEvent; import org.eclipse.ditto.signals.events.policies.SubjectCreated; import org.eclipse.ditto.signals.events.policies.SubjectModified; +import org.eclipse.ditto.signals.events.policies.SubjectsModifiedPartially; import akka.actor.ActorSystem; @@ -50,6 +57,8 @@ final class ActivateTokenIntegrationStrategy extends AbstractPolicyActionCommandStrategy { + private static final String MESSAGE_PATTERN_SUBJECT_TYPE = "added via action <{0}> at <{1}>"; + ActivateTokenIntegrationStrategy(final PolicyConfig policyConfig, final ActorSystem system) { super(ActivateTokenIntegration.class, policyConfig, system); } @@ -72,22 +81,27 @@ protected Result> doApply(final Context context, .filter(this::containsThingReadPermission); if (optionalEntry.isPresent()) { final PolicyEntry policyEntry = optionalEntry.get(); - final SubjectId subjectId; + final Set subjectIds; try { - subjectId = subjectIdFromActionResolver.resolveSubjectId(policyEntry, command); + subjectIds = subjectIdFromActionResolver.resolveSubjectIds(policyEntry, command); } catch (final DittoRuntimeException e) { return ResultFactory.newErrorResult(e, command); } final SubjectType subjectType = PoliciesModelFactory.newSubjectType( - MessageFormat.format("added via action <{0}>", command.getName())); - final Subject subject = Subject.newInstance(subjectId, subjectType, commandSubjectExpiry); - final Subject adjustedSubject = potentiallyAdjustSubject(subject); + MessageFormat.format(MESSAGE_PATTERN_SUBJECT_TYPE, command.getName(), Instant.now().toString())); + final SubjectExpiry adjustedSubjectExpiry = roundPolicySubjectExpiry(commandSubjectExpiry); final ActivateTokenIntegration adjustedCommand = ActivateTokenIntegration.of( - command.getEntityId(), command.getLabel(), adjustedSubject.getId(), - adjustedSubject.getExpiry().orElseThrow().getTimestamp(), dittoHeaders); + command.getEntityId(), command.getLabel(), subjectIds, adjustedSubjectExpiry.getTimestamp(), + dittoHeaders); + + final Set adjustedSubjects = subjectIds.stream() + .map(subjectId -> Subject.newInstance(subjectId, subjectType, adjustedSubjectExpiry)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + final PolicyBuilder policyBuilder = nonNullPolicy.toBuilder(); + adjustedSubjects.forEach(subject -> policyBuilder.setSubjectFor(label, subject)); // Validation is necessary because activation may add expiry to the policy admin subject. - final Policy newPolicy = nonNullPolicy.setSubjectFor(label, adjustedSubject); + final Policy newPolicy = policyBuilder.build(); final Optional>> alreadyExpiredSubject = checkForAlreadyExpiredSubject(newPolicy, dittoHeaders, command); @@ -99,15 +113,24 @@ protected Result> doApply(final Context context, if (validator.isValid()) { final PolicyActionEvent event; - if (policyEntry.getSubjects().getSubject(adjustedSubject.getId()).isPresent()) { - event = SubjectModified.of(policyId, label, adjustedSubject, nextRevision, getEventTimestamp(), - dittoHeaders); + final Instant eventTimestamp = getEventTimestamp(); + if (adjustedSubjects.size() == 1) { + final Subject adjustedSubject = adjustedSubjects.stream().findFirst().orElseThrow(); + final SubjectId subjectId = adjustedSubject.getId(); + + if (policyEntry.getSubjects().getSubject(subjectId).isPresent()) { + event = SubjectModified.of(policyId, label, adjustedSubject, nextRevision, eventTimestamp, + dittoHeaders); + } else { + event = SubjectCreated.of(policyId, label, adjustedSubject, nextRevision, eventTimestamp, + dittoHeaders); + } } else { - event = SubjectCreated.of(policyId, label, adjustedSubject, nextRevision, getEventTimestamp(), - dittoHeaders); + event = SubjectsModifiedPartially.of(policyId, Map.of(label, adjustedSubjects), nextRevision, + eventTimestamp, dittoHeaders); } final ActivateTokenIntegrationResponse rawResponse = - ActivateTokenIntegrationResponse.of(policyId, label, adjustedSubject.getId(), dittoHeaders); + ActivateTokenIntegrationResponse.of(policyId, label, subjectIds, dittoHeaders); // do not append ETag - activated subjects do not support ETags. return ResultFactory.newMutationResult(adjustedCommand, event, rawResponse); } else { diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategy.java index a852080c9d..0121920d66 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategy.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategy.java @@ -14,7 +14,10 @@ import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull; +import java.time.Instant; +import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nullable; @@ -34,6 +37,7 @@ import org.eclipse.ditto.signals.commands.policies.actions.DeactivateTokenIntegrationResponse; import org.eclipse.ditto.signals.events.policies.PolicyActionEvent; import org.eclipse.ditto.signals.events.policies.SubjectDeleted; +import org.eclipse.ditto.signals.events.policies.SubjectsDeletedPartially; import akka.actor.ActorSystem; @@ -62,19 +66,25 @@ protected Result> doApply(final Context context, final Optional optionalEntry = nonNullPolicy.getEntryFor(label) .filter(entry -> containsAuthenticatedSubject(entry, dittoHeaders.getAuthorizationContext())); if (optionalEntry.isPresent()) { - final SubjectId subjectId; + final Set subjectIds; final PolicyEntry policyEntry = optionalEntry.get(); try { - subjectId = subjectIdFromActionResolver.resolveSubjectId(policyEntry, command); + subjectIds = subjectIdFromActionResolver.resolveSubjectIds(policyEntry, command); } catch (final DittoRuntimeException e) { return ResultFactory.newErrorResult(e, command); } final DeactivateTokenIntegration adjustedCommand = - DeactivateTokenIntegration.of(command.getEntityId(), command.getLabel(), subjectId, dittoHeaders); + DeactivateTokenIntegration.of(command.getEntityId(), command.getLabel(), subjectIds, dittoHeaders); - final SubjectDeleted event = - SubjectDeleted.of(policyId, label, subjectId, nextRevision, getEventTimestamp(), - dittoHeaders); + final PolicyActionEvent event; + final Instant eventTimestamp = getEventTimestamp(); + if (subjectIds.size() == 1) { + final SubjectId subjectId = subjectIds.stream().findFirst().orElseThrow(); + event = SubjectDeleted.of(policyId, label, subjectId, nextRevision, eventTimestamp, dittoHeaders); + } else { + event = SubjectsDeletedPartially.of(policyId, Map.of(label, subjectIds), nextRevision, eventTimestamp, + dittoHeaders); + } final DeactivateTokenIntegrationResponse rawResponse = DeactivateTokenIntegrationResponse.of(policyId, label, dittoHeaders); return ResultFactory.newMutationResult(adjustedCommand, event, rawResponse); diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsDeletedPartiallyStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsDeletedPartiallyStrategy.java index 064affad86..f70068a7b6 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsDeletedPartiallyStrategy.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsDeletedPartiallyStrategy.java @@ -22,7 +22,11 @@ final class SubjectsDeletedPartiallyStrategy extends AbstractPolicyEventStrategy @Override protected PolicyBuilder applyEvent(final SubjectsDeletedPartially event, final PolicyBuilder policyBuilder) { - event.getDeletedSubjectIds().forEach(policyBuilder::removeSubjectFor); + event.getDeletedSubjectIds().forEach((label, subjects) -> + subjects.forEach(subject -> + policyBuilder.removeSubjectFor(label, subject) + ) + ); return policyBuilder; } } diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsModifiedPartiallyStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsModifiedPartiallyStrategy.java index 384b815f15..72e9c58016 100644 --- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsModifiedPartiallyStrategy.java +++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectsModifiedPartiallyStrategy.java @@ -25,14 +25,16 @@ final class SubjectsModifiedPartiallyStrategy extends AbstractPolicyEventStrateg @Override protected PolicyBuilder applyEvent(final SubjectsModifiedPartially event, final PolicyBuilder policyBuilder) { final Instant now = Instant.now(); - event.getModifiedSubjects().forEach((label, subject) -> { - final boolean isSubjectExpiryAfterNow = subject.getExpiry() - .map(expiry -> expiry.getTimestamp().isAfter(now)) - .orElse(false); - if (isSubjectExpiryAfterNow) { - policyBuilder.setSubjectFor(label, subject); - } - }); + event.getModifiedSubjects().forEach((label, subjects) -> + subjects.forEach(subject -> { + final boolean isSubjectExpiryAfterNow = subject.getExpiry() + .map(expiry -> expiry.getTimestamp().isAfter(now)) + .orElse(false); + if (isSubjectExpiryAfterNow) { + policyBuilder.setSubjectFor(label, subject); + } + }) + ); return policyBuilder; } } diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/PolicyPersistenceActorTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/PolicyPersistenceActorTest.java index 2b4a5e2700..a79cdd944c 100755 --- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/PolicyPersistenceActorTest.java +++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/PolicyPersistenceActorTest.java @@ -1214,7 +1214,7 @@ public void ensureExpiredSubjectIsRemovedDuringRecovery() throws InterruptedExce .build(); final long secondsToAdd = 10 - (expiryInstant.getEpochSecond() % 10); - final Instant expectedRoundedExpiryInstant = + final Instant expectedRoundedExpiryInstant = (secondsToAdd == 10) ? expiryInstant : expiryInstant.plusSeconds(secondsToAdd); // to next 10s rounded up final SubjectExpiry expectedSubjectExpiry = SubjectExpiry.newInstance(expectedRoundedExpiryInstant); final Subject expectedAdjustedSubjectToAdd = Subject.newInstance(expiringSubject.getId(), diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolverTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolverTest.java index 39de1ac62a..5b4c67a4cb 100644 --- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolverTest.java +++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/resolvers/DefaultSubjectIdFromActionResolverTest.java @@ -15,6 +15,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.util.Collections; + import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException; import org.eclipse.ditto.model.policies.Label; import org.eclipse.ditto.model.policies.PoliciesModelFactory; @@ -48,34 +50,35 @@ public final class DefaultSubjectIdFromActionResolverTest { @Test public void resolveSubjectWithoutPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("integration:hello"); - assertThat(sut.resolveSubjectId(ENTRY, subjectId)).isEqualTo(subjectId); + assertThat(sut.resolveSubjectId(ENTRY, Collections.singleton(subjectId))) + .isEqualTo(Collections.singleton(subjectId)); } @Test public void resolveSubjectWithPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("integration:{{policy-entry:label}}"); - assertThat(sut.resolveSubjectId(ENTRY, subjectId)) - .isEqualTo(SubjectId.newInstance("integration:label")); + assertThat(sut.resolveSubjectId(ENTRY, Collections.singleton(subjectId))) + .isEqualTo(Collections.singleton(SubjectId.newInstance("integration:label"))); } @Test public void doNotResolveSubjectWithSupportedAndUnresolvedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("integration:{{fn:delete()}}"); assertThatExceptionOfType(UnresolvedPlaceholderException.class) - .isThrownBy(() -> sut.resolveSubjectId(ENTRY, subjectId)); + .isThrownBy(() -> sut.resolveSubjectId(ENTRY, Collections.singleton(subjectId))); } @Test public void throwErrorOnUnsupportedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("integration:{{connection:id}}"); assertThatExceptionOfType(UnresolvedPlaceholderException.class) - .isThrownBy(() -> sut.resolveSubjectId(ENTRY, subjectId)); + .isThrownBy(() -> sut.resolveSubjectId(ENTRY, Collections.singleton(subjectId))); } @Test public void throwSubjectIdInvalidException() { final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}"); assertThatExceptionOfType(SubjectIdInvalidException.class) - .isThrownBy(() -> sut.resolveSubjectId(ENTRY, subjectId)); + .isThrownBy(() -> sut.resolveSubjectId(ENTRY, Collections.singleton(subjectId))); } } diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategyTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategyTest.java index 540e525b65..ee01443d74 100644 --- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategyTest.java +++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateTokenIntegrationStrategyTest.java @@ -18,6 +18,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.Collections; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException; @@ -70,10 +71,12 @@ public void activateTokenIntegration() { final SubjectId expectedSubjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, LABEL + ":this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final ActivateTokenIntegration command = - ActivateTokenIntegration.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders); + ActivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), expiry, + dittoHeaders); assertModificationResult(underTest, TestConstants.Policy.POLICY, command, SubjectCreated.class, - ActivateTokenIntegrationResponse.of(context.getState(), LABEL, expectedSubjectId, dittoHeaders)); + ActivateTokenIntegrationResponse.of(context.getState(), LABEL, Collections.singleton(expectedSubjectId), + dittoHeaders)); } @Test @@ -83,7 +86,9 @@ public void activateInvalidTokenIntegration() { final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final ActivateTokenIntegration - command = ActivateTokenIntegration.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders); + command = + ActivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), expiry, + dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, SubjectIdInvalidException.newBuilder(LABEL).build()); } @@ -95,7 +100,9 @@ public void activateUnresolvableTokenIntegration() { final SubjectId subjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{fn:delete()}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final ActivateTokenIntegration - command = ActivateTokenIntegration.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders); + command = + ActivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), expiry, + dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, UnresolvedPlaceholderException.newBuilder("integration:{{fn:delete()}}").build()); } @@ -107,7 +114,9 @@ public void activateTokenIntegrationWithUnsupportedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("{{request:subjectId}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final ActivateTokenIntegration - command = ActivateTokenIntegration.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders); + command = + ActivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), expiry, + dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, UnresolvedPlaceholderException.newBuilder("{{request:subjectId}}").build()); } @@ -119,7 +128,8 @@ public void rejectEntryWithoutAuthContextContainingPolicyEntrySubject() { final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me"); final DittoHeaders dittoHeaders = DittoHeaders.empty(); final ActivateTokenIntegration command = - ActivateTokenIntegration.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders); + ActivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), expiry, + dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, underTest.getNotApplicableException(dittoHeaders)); } @@ -132,7 +142,8 @@ public void rejectEntryWithoutThingReadPermission() { final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final ActivateTokenIntegration command = - ActivateTokenIntegration.of(context.getState(), label, subjectId, expiry, dittoHeaders); + ActivateTokenIntegration.of(context.getState(), label, Collections.singleton(subjectId), expiry, + dittoHeaders); final Policy policy = TestConstants.Policy.POLICY.toBuilder() .forLabel(label) .setSubject(TestConstants.Policy.SUPPORT_SUBJECT) diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategyTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategyTest.java index dce91a5526..0de51ff3b1 100644 --- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategyTest.java +++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/DeactivateTokenIntegrationStrategyTest.java @@ -16,6 +16,8 @@ import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; +import java.util.Collections; + import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException; import org.eclipse.ditto.model.policies.PolicyId; @@ -61,7 +63,7 @@ public void deactivateTokenIntegration() { SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{policy-entry:label}}:this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final DeactivateTokenIntegration command = - DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertModificationResult(underTest, TestConstants.Policy.POLICY, command, SubjectDeleted.class, DeactivateTokenIntegrationResponse.of(context.getState(), LABEL, dittoHeaders)); @@ -74,7 +76,7 @@ public void deactivatePermanentSubject() { SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{policy-entry:label}}:this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final DeactivateTokenIntegration command = - DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertModificationResult(underTest, TestConstants.Policy.POLICY, command, SubjectDeleted.class, DeactivateTokenIntegrationResponse.of(context.getState(), LABEL, dittoHeaders)); @@ -86,7 +88,7 @@ public void deactivateInvalidSubject() { final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final DeactivateTokenIntegration - command = DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + command = DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, SubjectIdInvalidException.newBuilder(LABEL).build()); } @@ -97,7 +99,7 @@ public void deactivateUnresolvableSubject() { final SubjectId subjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{fn:delete()}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final DeactivateTokenIntegration - command = DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + command = DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, UnresolvedPlaceholderException.newBuilder("integration:{{fn:delete()}}").build()); } @@ -108,7 +110,7 @@ public void deactivateTokenIntegrationWithUnsupportedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("{{request:subjectId}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final DeactivateTokenIntegration - command = DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + command = DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, UnresolvedPlaceholderException.newBuilder("{{request:subjectId}}").build()); } @@ -120,7 +122,7 @@ public void deactivateTokenIntegrationWithoutAuthContextContainingPolicyEntrySub SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{policy-entry:label}}:this-is-me"); final DittoHeaders dittoHeaders = DittoHeaders.empty(); final DeactivateTokenIntegration command = - DeactivateTokenIntegration.of(context.getState(), LABEL, subjectId, dittoHeaders); + DeactivateTokenIntegration.of(context.getState(), LABEL, Collections.singleton(subjectId), dittoHeaders); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, underTest.getNotApplicableException(dittoHeaders)); } diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/TopLevelPolicyActionCommandStrategyTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/TopLevelPolicyActionCommandStrategyTest.java index 5d421ef26e..ed2b3ab759 100644 --- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/TopLevelPolicyActionCommandStrategyTest.java +++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/TopLevelPolicyActionCommandStrategyTest.java @@ -20,6 +20,7 @@ import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -88,15 +89,17 @@ public void activateTokenIntegration() { SubjectId.newInstance(SubjectIssuer.INTEGRATION, LABEL + ":this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(LABEL) ); assertModificationResult(underTest, TestConstants.Policy.POLICY, command, SubjectsModifiedPartially.class, event -> { assertThat(event.getModifiedSubjects()).containsOnlyKeys(LABEL); - assertThat(event.getModifiedSubjects().get(LABEL).getId()).isEqualTo(expectedSubjectId); - assertThat(event.getModifiedSubjects().get(LABEL).getExpiry()).isNotEmpty(); + assertThat(event.getModifiedSubjects().get(LABEL).stream().findFirst().orElseThrow().getId()) + .isEqualTo(expectedSubjectId); + assertThat(event.getModifiedSubjects().get(LABEL).stream().findFirst().orElseThrow().getExpiry()) + .isNotEmpty(); }, TopLevelPolicyActionCommandResponse.class, response -> assertThat(response) @@ -110,7 +113,7 @@ public void activateInvalidSubject() { final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -124,7 +127,7 @@ public void activateUnresolvableSubject() { final SubjectId subjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{fn:delete()}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -138,7 +141,7 @@ public void activateTokenIntegrationWithUnsupportedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("{{request:subjectId}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -152,7 +155,7 @@ public void rejectEmptyLabels() { final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me"); final DittoHeaders dittoHeaders = DittoHeaders.empty(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of() ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -167,7 +170,7 @@ public void rejectNonexistentLabel() { final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me"); final DittoHeaders dittoHeaders = DittoHeaders.empty(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(nonexistentLabel) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -182,7 +185,7 @@ public void rejectEntryWithoutThingReadPermission() { final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me"); final DittoHeaders dittoHeaders = DittoHeaders.empty(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, expiry, dittoHeaders), + ActivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), expiry, dittoHeaders), List.of(label) ); final Policy policy = TestConstants.Policy.POLICY.toBuilder() @@ -203,7 +206,7 @@ public void deactivateTokenIntegration() { SubjectId.newInstance(SubjectIssuer.INTEGRATION, LABEL + ":this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, dittoHeaders), + DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), dittoHeaders), List.of(LABEL) ); final Policy policy = TestConstants.Policy.POLICY.toBuilder() @@ -216,7 +219,8 @@ public void deactivateTokenIntegration() { SubjectsDeletedPartially.class, event -> { assertThat(event.getDeletedSubjectIds()).containsOnlyKeys(LABEL); - assertThat(event.getDeletedSubjectIds().get(LABEL)).isEqualTo(expectedSubjectId); + assertThat(event.getDeletedSubjectIds().get(LABEL)) + .isEqualTo(Collections.singleton(expectedSubjectId)); }, TopLevelPolicyActionCommandResponse.class, response -> assertThat(response) @@ -230,14 +234,14 @@ public void deactivateNonexistentSubject() { SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{policy-entry:label}}:this-is-me"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, dittoHeaders), + DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), dittoHeaders), List.of(LABEL) ); assertModificationResult(underTest, TestConstants.Policy.POLICY, command, SubjectsDeletedPartially.class, // nonexistent subjects are present in the event. event -> assertThat(event.getDeletedSubjectIds()).containsExactly( - Map.entry(LABEL, SubjectId.newInstance("integration:" + LABEL + ":this-is-me"))), + Map.entry(LABEL, Collections.singleton(SubjectId.newInstance("integration:" + LABEL + ":this-is-me")))), TopLevelPolicyActionCommandResponse.class, response -> assertThat(response) .isEqualTo(TopLevelPolicyActionCommandResponse.of(context.getState(), dittoHeaders))); @@ -249,7 +253,7 @@ public void deactivateInvalidSubject() { final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, dittoHeaders), + DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -262,7 +266,7 @@ public void deactivateUnresolvableSubject() { final SubjectId subjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{fn:delete()}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, dittoHeaders), + DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, @@ -275,7 +279,7 @@ public void deactivateTokenIntegrationWithUnsupportedPlaceholder() { final SubjectId subjectId = SubjectId.newInstance("{{request:subjectId}}"); final DittoHeaders dittoHeaders = buildActivateTokenIntegrationHeaders(); final TopLevelPolicyActionCommand command = TopLevelPolicyActionCommand.of( - DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, subjectId, dittoHeaders), + DeactivateTokenIntegration.of(context.getState(), DUMMY_LABEL, Collections.singleton(subjectId), dittoHeaders), List.of(LABEL) ); assertErrorResult(underTest, TestConstants.Policy.POLICY, command, diff --git a/services/thingsearch/persistence/src/main/java/org/eclipse/ditto/services/thingsearch/persistence/write/model/Metadata.java b/services/thingsearch/persistence/src/main/java/org/eclipse/ditto/services/thingsearch/persistence/write/model/Metadata.java index 2d31a14652..b16092d797 100644 --- a/services/thingsearch/persistence/src/main/java/org/eclipse/ditto/services/thingsearch/persistence/write/model/Metadata.java +++ b/services/thingsearch/persistence/src/main/java/org/eclipse/ditto/services/thingsearch/persistence/write/model/Metadata.java @@ -23,7 +23,7 @@ import javax.annotation.concurrent.Immutable; import org.eclipse.ditto.model.base.acks.DittoAcknowledgementLabel; -import org.eclipse.ditto.model.base.common.HttpStatusCode; +import org.eclipse.ditto.model.base.common.HttpStatus; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.policies.PolicyId; import org.eclipse.ditto.model.things.ThingId; @@ -200,14 +200,14 @@ public Metadata prependSenders(final Metadata newMetadata) { */ public void sendNAck() { send(Acknowledgement.of(DittoAcknowledgementLabel.SEARCH_PERSISTED, thingId, - HttpStatusCode.INTERNAL_SERVER_ERROR, DittoHeaders.empty())); + HttpStatus.INTERNAL_SERVER_ERROR, DittoHeaders.empty())); } /** * Send positive acknowledgements to senders. */ public void sendAck() { - send(Acknowledgement.of(DittoAcknowledgementLabel.SEARCH_PERSISTED, thingId, HttpStatusCode.NO_CONTENT, + send(Acknowledgement.of(DittoAcknowledgementLabel.SEARCH_PERSISTED, thingId, HttpStatus.NO_CONTENT, DittoHeaders.empty())); } diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegration.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegration.java index 24c0dbfd82..311a9df716 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegration.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegration.java @@ -16,12 +16,19 @@ import java.time.Instant; import java.time.format.DateTimeParseException; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.eclipse.ditto.json.JsonArray; +import org.eclipse.ditto.json.JsonCollectors; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonField; import org.eclipse.ditto.json.JsonFieldDefinition; @@ -30,13 +37,13 @@ import org.eclipse.ditto.json.JsonObjectBuilder; import org.eclipse.ditto.json.JsonParseException; import org.eclipse.ditto.json.JsonPointer; +import org.eclipse.ditto.json.JsonValue; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.base.json.FieldType; import org.eclipse.ditto.model.base.json.JsonParsableCommand; import org.eclipse.ditto.model.base.json.JsonSchemaVersion; import org.eclipse.ditto.model.policies.EffectedPermissions; import org.eclipse.ditto.model.policies.Label; -import org.eclipse.ditto.model.policies.PoliciesModelFactory; import org.eclipse.ditto.model.policies.PoliciesResourceType; import org.eclipse.ditto.model.policies.Policy; import org.eclipse.ditto.model.policies.PolicyEntry; @@ -47,7 +54,8 @@ import org.eclipse.ditto.signals.commands.policies.PolicyCommand; /** - * This command activates a token subject in a policy entry. + * This command injects one or several subjects derived from a provided token including an "expiry" extracted from the + * token into an existing policy entry. * * @since 2.0.0 */ @@ -69,8 +77,8 @@ public final class ActivateTokenIntegration extends AbstractCommand JSON_LABEL = JsonFactory.newStringFieldDefinition("label", FieldType.REGULAR, JsonSchemaVersion.V_2); - static final JsonFieldDefinition JSON_SUBJECT_ID = - JsonFactory.newStringFieldDefinition("subjectId", FieldType.REGULAR, JsonSchemaVersion.V_2); + static final JsonFieldDefinition JSON_SUBJECT_IDS = + JsonFactory.newJsonArrayFieldDefinition("subjectIds", FieldType.REGULAR, JsonSchemaVersion.V_2); static final JsonFieldDefinition JSON_EXPIRY = JsonFactory.newStringFieldDefinition("expiry", FieldType.REGULAR, JsonSchemaVersion.V_2); @@ -79,15 +87,15 @@ public final class ActivateTokenIntegration extends AbstractCommand subjectIds; private final Instant expiry; - private ActivateTokenIntegration(final PolicyId policyId, final Label label, final SubjectId subjectId, + private ActivateTokenIntegration(final PolicyId policyId, final Label label, final Collection subjectIds, final Instant expiry, final DittoHeaders dittoHeaders) { super(TYPE, dittoHeaders); this.policyId = checkNotNull(policyId, "policyId"); this.label = checkNotNull(label, "label"); - this.subjectId = checkNotNull(subjectId, "subjectId"); + this.subjectIds = Collections.unmodifiableSet(new LinkedHashSet<>(checkNotNull(subjectIds, "subjectIds"))); this.expiry = checkNotNull(expiry, "expiry"); } @@ -96,16 +104,16 @@ private ActivateTokenIntegration(final PolicyId policyId, final Label label, fin * * @param policyId the identifier of the Policy. * @param label label of the policy entry where the subject should be activated. - * @param subjectId subject ID to activate. + * @param subjectIds subject IDs to activate. * @param expiry when the subject expires. * @param dittoHeaders the headers of the command. * @return the command. * @throws NullPointerException if any argument is {@code null}. */ - public static ActivateTokenIntegration of(final PolicyId policyId, final Label label, final SubjectId subjectId, - final Instant expiry, final DittoHeaders dittoHeaders) { + public static ActivateTokenIntegration of(final PolicyId policyId, final Label label, + final Collection subjectIds, final Instant expiry, final DittoHeaders dittoHeaders) { - return new ActivateTokenIntegration(policyId, label, subjectId, expiry, dittoHeaders); + return new ActivateTokenIntegration(policyId, label, subjectIds, expiry, dittoHeaders); } /** @@ -123,10 +131,13 @@ public static ActivateTokenIntegration fromJson(final JsonObject jsonObject, fin final String extractedPolicyId = jsonObject.getValueOrThrow(PolicyCommand.JsonFields.JSON_POLICY_ID); final PolicyId policyId = PolicyId.of(extractedPolicyId); final Label label = Label.of(jsonObject.getValueOrThrow(JSON_LABEL)); - final SubjectId subjectId = - PoliciesModelFactory.newSubjectId(jsonObject.getValueOrThrow(JSON_SUBJECT_ID)); + final Set subjectIds = jsonObject.getValueOrThrow(JSON_SUBJECT_IDS).stream() + .filter(JsonValue::isString) + .map(JsonValue::asString) + .map(SubjectId::newInstance) + .collect(Collectors.toCollection(LinkedHashSet::new)); final Instant expiry = parseExpiry(jsonObject.getValueOrThrow(JSON_EXPIRY)); - return new ActivateTokenIntegration(policyId, label, subjectId, expiry, dittoHeaders); + return new ActivateTokenIntegration(policyId, label, subjectIds, expiry, dittoHeaders); }); } @@ -140,13 +151,13 @@ public Label getLabel() { } @Override - public SubjectId getSubjectId() { - return subjectId; + public Set getSubjectIds() { + return subjectIds; } @Override public ActivateTokenIntegration setLabel(final Label label) { - return new ActivateTokenIntegration(policyId, label, subjectId, expiry, getDittoHeaders()); + return new ActivateTokenIntegration(policyId, label, subjectIds, expiry, getDittoHeaders()); } @Override @@ -192,13 +203,16 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js final Predicate predicate = schemaVersion.and(thePredicate); jsonObjectBuilder.set(PolicyCommand.JsonFields.JSON_POLICY_ID, String.valueOf(policyId), predicate); jsonObjectBuilder.set(JSON_LABEL, label.toString(), predicate); - jsonObjectBuilder.set(JSON_SUBJECT_ID, subjectId.toString(), predicate); + jsonObjectBuilder.set(JSON_SUBJECT_IDS, subjectIds.stream() + .map(SubjectId::toString) + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()), predicate); jsonObjectBuilder.set(JSON_EXPIRY, expiry.toString(), predicate); } @Override public ActivateTokenIntegration setDittoHeaders(final DittoHeaders dittoHeaders) { - return new ActivateTokenIntegration(policyId, label, subjectId, expiry, dittoHeaders); + return new ActivateTokenIntegration(policyId, label, subjectIds, expiry, dittoHeaders); } @Override @@ -218,14 +232,14 @@ public boolean equals(@Nullable final Object obj) { return that.canEqual(this) && Objects.equals(policyId, that.policyId) && Objects.equals(label, that.label) && - Objects.equals(subjectId, that.subjectId) && + Objects.equals(subjectIds, that.subjectIds) && Objects.equals(expiry, that.expiry) && super.equals(obj); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), policyId, label, subjectId, expiry); + return Objects.hash(super.hashCode(), policyId, label, subjectIds, expiry); } @Override @@ -233,7 +247,7 @@ public String toString() { return getClass().getSimpleName() + " [" + super.toString() + ", policyId=" + policyId + ", label=" + label + - ", subjectId=" + subjectId + + ", subjectIds=" + subjectIds + ", expiry=" + expiry + "]"; } diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponse.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponse.java index 490a9a1c28..06589994ff 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponse.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponse.java @@ -14,12 +14,19 @@ import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.eclipse.ditto.json.JsonArray; +import org.eclipse.ditto.json.JsonCollectors; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonField; import org.eclipse.ditto.json.JsonFieldDefinition; @@ -27,6 +34,7 @@ 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.model.base.common.HttpStatus; import org.eclipse.ditto.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.base.json.FieldType; @@ -63,20 +71,20 @@ public final class ActivateTokenIntegrationResponse static final JsonFieldDefinition JSON_LABEL = JsonFactory.newStringFieldDefinition("label", FieldType.REGULAR, JsonSchemaVersion.V_2); - static final JsonFieldDefinition JSON_SUBJECT_ID = - JsonFactory.newStringFieldDefinition("subjectId", FieldType.REGULAR, JsonSchemaVersion.V_2); + static final JsonFieldDefinition JSON_SUBJECT_IDS = + JsonFactory.newJsonArrayFieldDefinition("subjectIds", FieldType.REGULAR, JsonSchemaVersion.V_2); private final PolicyId policyId; private final Label label; - private final SubjectId subjectId; + private final Collection subjectIds; - private ActivateTokenIntegrationResponse(final PolicyId policyId, final Label label, final SubjectId subjectId, - final DittoHeaders dittoHeaders) { + private ActivateTokenIntegrationResponse(final PolicyId policyId, final Label label, + final Collection subjectIds, final DittoHeaders dittoHeaders) { super(TYPE, STATUS, dittoHeaders); this.policyId = checkNotNull(policyId, "policyId"); this.label = checkNotNull(label, "label"); - this.subjectId = checkNotNull(subjectId, "subjectId"); + this.subjectIds = Collections.unmodifiableSet(new LinkedHashSet<>(checkNotNull(subjectIds, "subjectIds"))); } /** @@ -84,14 +92,14 @@ private ActivateTokenIntegrationResponse(final PolicyId policyId, final Label la * * @param policyId the policy ID. * @param label the policy entry label. - * @param subjectId the added subject ID. + * @param subjectIds the added subject IDs. * @param dittoHeaders the headers of the preceding command. * @return the response. * @throws NullPointerException if any argument is {@code null}. */ - public static ActivateTokenIntegrationResponse of(final PolicyId policyId, final Label label, final SubjectId subjectId, - final DittoHeaders dittoHeaders) { - return new ActivateTokenIntegrationResponse(policyId, label, subjectId, dittoHeaders); + public static ActivateTokenIntegrationResponse of(final PolicyId policyId, final Label label, + final Collection subjectIds, final DittoHeaders dittoHeaders) { + return new ActivateTokenIntegrationResponse(policyId, label, subjectIds, dittoHeaders); } /** @@ -105,12 +113,17 @@ public static ActivateTokenIntegrationResponse of(final PolicyId policyId, final * format. */ @SuppressWarnings("unused") // called by reflection - public static ActivateTokenIntegrationResponse fromJson(final JsonObject jsonObject, final DittoHeaders dittoHeaders) { + public static ActivateTokenIntegrationResponse fromJson(final JsonObject jsonObject, + final DittoHeaders dittoHeaders) { final PolicyId policyId = PolicyId.of(jsonObject.getValueOrThrow(PolicyCommandResponse.JsonFields.JSON_POLICY_ID)); final Label label = Label.of(jsonObject.getValueOrThrow(JSON_LABEL)); - final SubjectId subjectId = SubjectId.newInstance(jsonObject.getValueOrThrow(JSON_SUBJECT_ID)); - return new ActivateTokenIntegrationResponse(policyId, label, subjectId, dittoHeaders); + final Set subjectIds = jsonObject.getValueOrThrow(JSON_SUBJECT_IDS).stream() + .filter(JsonValue::isString) + .map(JsonValue::asString) + .map(SubjectId::newInstance) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return new ActivateTokenIntegrationResponse(policyId, label, subjectIds, dittoHeaders); } @Override @@ -133,12 +146,15 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js final Predicate predicate = schemaVersion.and(thePredicate); jsonObjectBuilder.set(PolicyCommandResponse.JsonFields.JSON_POLICY_ID, policyId.toString(), predicate); jsonObjectBuilder.set(JSON_LABEL, label.toString(), predicate); - jsonObjectBuilder.set(JSON_SUBJECT_ID, subjectId.toString(), predicate); + jsonObjectBuilder.set(JSON_SUBJECT_IDS, subjectIds.stream() + .map(SubjectId::toString) + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()), predicate); } @Override public ActivateTokenIntegrationResponse setDittoHeaders(final DittoHeaders dittoHeaders) { - return new ActivateTokenIntegrationResponse(policyId, label, subjectId, dittoHeaders); + return new ActivateTokenIntegrationResponse(policyId, label, subjectIds, dittoHeaders); } @Override @@ -157,13 +173,13 @@ public boolean equals(@Nullable final Object o) { final ActivateTokenIntegrationResponse that = (ActivateTokenIntegrationResponse) o; return Objects.equals(policyId, that.policyId) && Objects.equals(label, that.label) && - Objects.equals(subjectId, that.subjectId) && + Objects.equals(subjectIds, that.subjectIds) && super.equals(o); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), policyId, label, subjectId); + return Objects.hash(super.hashCode(), policyId, label, subjectIds); } @Override @@ -172,7 +188,7 @@ public String toString() { " [" + super.toString() + ", policyId=" + policyId + ", label=" + label + - ", subjectId=" + subjectId + + ", subjectIds=" + subjectIds + "]"; } diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegration.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegration.java index 3db052b779..b9b4bae904 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegration.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegration.java @@ -14,12 +14,19 @@ import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.eclipse.ditto.json.JsonArray; +import org.eclipse.ditto.json.JsonCollectors; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonField; import org.eclipse.ditto.json.JsonFieldDefinition; @@ -27,12 +34,12 @@ 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.model.base.headers.DittoHeaders; import org.eclipse.ditto.model.base.json.FieldType; import org.eclipse.ditto.model.base.json.JsonParsableCommand; import org.eclipse.ditto.model.base.json.JsonSchemaVersion; import org.eclipse.ditto.model.policies.Label; -import org.eclipse.ditto.model.policies.PoliciesModelFactory; import org.eclipse.ditto.model.policies.Policy; import org.eclipse.ditto.model.policies.PolicyEntry; import org.eclipse.ditto.model.policies.PolicyId; @@ -42,7 +49,7 @@ import org.eclipse.ditto.signals.commands.policies.PolicyCommand; /** - * This command deactivates a token subject in a policy entry. + * This command deactivates one or several subjects derived from a provided token from an existing policy entry. * * @since 2.0.0 */ @@ -64,19 +71,19 @@ public final class DeactivateTokenIntegration extends AbstractCommand JSON_LABEL = JsonFactory.newStringFieldDefinition("label", FieldType.REGULAR, JsonSchemaVersion.V_2); - static final JsonFieldDefinition JSON_SUBJECT_ID = - JsonFactory.newStringFieldDefinition("subjectId", FieldType.REGULAR, JsonSchemaVersion.V_2); + static final JsonFieldDefinition JSON_SUBJECT_IDS = + JsonFactory.newJsonArrayFieldDefinition("subjectIds", FieldType.REGULAR, JsonSchemaVersion.V_2); private final PolicyId policyId; private final Label label; - private final SubjectId subjectId; + private final Set subjectIds; - private DeactivateTokenIntegration(final PolicyId policyId, final Label label, final SubjectId subjectId, - final DittoHeaders dittoHeaders) { + private DeactivateTokenIntegration(final PolicyId policyId, final Label label, + final Collection subjectIds, final DittoHeaders dittoHeaders) { super(TYPE, dittoHeaders); this.policyId = checkNotNull(policyId, "policyId"); this.label = checkNotNull(label, "label"); - this.subjectId = checkNotNull(subjectId, "subjectId"); + this.subjectIds = Collections.unmodifiableSet(new LinkedHashSet<>(checkNotNull(subjectIds, "subjectIds"))); } /** @@ -84,15 +91,15 @@ private DeactivateTokenIntegration(final PolicyId policyId, final Label label, f * * @param policyId the identifier of the Policy. * @param label label of the policy entry where the subject should be deactivated. - * @param subjectId subject ID to activate. + * @param subjectIds subject IDs to activate. * @param dittoHeaders the headers of the command. * @return the command. * @throws NullPointerException if any argument is {@code null}. */ - public static DeactivateTokenIntegration of(final PolicyId policyId, final Label label, final SubjectId subjectId, - final DittoHeaders dittoHeaders) { + public static DeactivateTokenIntegration of(final PolicyId policyId, final Label label, + final Collection subjectIds, final DittoHeaders dittoHeaders) { - return new DeactivateTokenIntegration(policyId, label, subjectId, dittoHeaders); + return new DeactivateTokenIntegration(policyId, label, subjectIds, dittoHeaders); } /** @@ -110,9 +117,12 @@ public static DeactivateTokenIntegration fromJson(final JsonObject jsonObject, f final String extractedPolicyId = jsonObject.getValueOrThrow(PolicyCommand.JsonFields.JSON_POLICY_ID); final PolicyId policyId = PolicyId.of(extractedPolicyId); final Label label = Label.of(jsonObject.getValueOrThrow(JSON_LABEL)); - final SubjectId subjectId = - PoliciesModelFactory.newSubjectId(jsonObject.getValueOrThrow(JSON_SUBJECT_ID)); - return new DeactivateTokenIntegration(policyId, label, subjectId, dittoHeaders); + final Set subjectIds = jsonObject.getValueOrThrow(JSON_SUBJECT_IDS).stream() + .filter(JsonValue::isString) + .map(JsonValue::asString) + .map(SubjectId::newInstance) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return new DeactivateTokenIntegration(policyId, label, subjectIds, dittoHeaders); }); } @@ -126,13 +136,13 @@ public Label getLabel() { } @Override - public SubjectId getSubjectId() { - return subjectId; + public Set getSubjectIds() { + return subjectIds; } @Override public DeactivateTokenIntegration setLabel(final Label label) { - return new DeactivateTokenIntegration(policyId, label, subjectId, getDittoHeaders()); + return new DeactivateTokenIntegration(policyId, label, subjectIds, getDittoHeaders()); } @Override @@ -160,12 +170,15 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js final Predicate predicate = schemaVersion.and(thePredicate); jsonObjectBuilder.set(PolicyCommand.JsonFields.JSON_POLICY_ID, String.valueOf(policyId), predicate); jsonObjectBuilder.set(JSON_LABEL, label.toString(), predicate); - jsonObjectBuilder.set(JSON_SUBJECT_ID, subjectId.toString(), predicate); + jsonObjectBuilder.set(JSON_SUBJECT_IDS, subjectIds.stream() + .map(SubjectId::toString) + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()), predicate); } @Override public DeactivateTokenIntegration setDittoHeaders(final DittoHeaders dittoHeaders) { - return new DeactivateTokenIntegration(policyId, label, subjectId, dittoHeaders); + return new DeactivateTokenIntegration(policyId, label, subjectIds, dittoHeaders); } @Override @@ -185,13 +198,13 @@ public boolean equals(@Nullable final Object obj) { return that.canEqual(this) && Objects.equals(policyId, that.policyId) && Objects.equals(label, that.label) && - Objects.equals(subjectId, that.subjectId) && + Objects.equals(subjectIds, that.subjectIds) && super.equals(obj); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), policyId, label, subjectId); + return Objects.hash(super.hashCode(), policyId, label, subjectIds); } @Override @@ -199,7 +212,7 @@ public String toString() { return getClass().getSimpleName() + " [" + super.toString() + ", policyId=" + policyId + ", label=" + label + - ", subjectId=" + subjectId + + ", subjectIds=" + subjectIds + "]"; } diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/PolicyActionCommand.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/PolicyActionCommand.java index d8233ab4dc..5d67bf4233 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/PolicyActionCommand.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/PolicyActionCommand.java @@ -12,6 +12,8 @@ */ package org.eclipse.ditto.signals.commands.policies.actions; +import java.util.Set; + import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.model.policies.Label; import org.eclipse.ditto.model.policies.PolicyEntry; @@ -33,11 +35,11 @@ public interface PolicyActionCommand> extends P JsonPointer RESOURCE_PATH_ACTIONS = JsonPointer.of("actions"); /** - * Get the subject ID of this command. + * Get the subject IDs of this command. * - * @return the subject ID. + * @return the subject IDs. */ - SubjectId getSubjectId(); + Set getSubjectIds(); /** * Set the label of the policy entry where this action is executed, if applicable. diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommand.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommand.java index 625b8f80bc..896461d6e6 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommand.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommand.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -139,8 +140,8 @@ public static TopLevelPolicyActionCommand fromJson(final JsonObject jsonObject, } @Override - public SubjectId getSubjectId() { - return policyActionCommand.getSubjectId(); + public Set getSubjectIds() { + return policyActionCommand.getSubjectIds(); } @Override diff --git a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/modify/DeleteSubjectResponse.java b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/modify/DeleteSubjectResponse.java index 0cf9c76a18..390b438a93 100755 --- a/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/modify/DeleteSubjectResponse.java +++ b/signals/commands/policies/src/main/java/org/eclipse/ditto/signals/commands/policies/modify/DeleteSubjectResponse.java @@ -69,9 +69,9 @@ private DeleteSubjectResponse(final PolicyId policyId, final DittoHeaders dittoHeaders) { super(TYPE, httpStatus, dittoHeaders); - this.policyId = checkNotNull(policyId, "Policy ID"); - this.label = checkNotNull(label, "Label"); - this.subjectId = checkNotNull(subjectId, "SubjectId"); + this.policyId = checkNotNull(policyId, "policyId"); + this.label = checkNotNull(label, "label"); + this.subjectId = checkNotNull(subjectId, "subjectId"); } /** diff --git a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponseTest.java b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponseTest.java index 2ebb367e7b..e0857645cc 100755 --- a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponseTest.java +++ b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationResponseTest.java @@ -17,6 +17,9 @@ import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; +import java.util.Collections; + +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.model.base.json.FieldType; @@ -39,7 +42,9 @@ public final class ActivateTokenIntegrationResponseTest { .set(PolicyCommandResponse.JsonFields.STATUS, ActivateTokenIntegrationResponse.STATUS.getCode()) .set(PolicyCommandResponse.JsonFields.JSON_POLICY_ID, TestConstants.Policy.POLICY_ID.toString()) .set(ActivateTokenIntegrationResponse.JSON_LABEL, TestConstants.Policy.LABEL.toString()) - .set(ActivateTokenIntegrationResponse.JSON_SUBJECT_ID, TestConstants.Policy.SUBJECT_ID.toString()) + .set(ActivateTokenIntegrationResponse.JSON_SUBJECT_IDS, JsonArray.newBuilder() + .add(TestConstants.Policy.SUBJECT_ID.toString()) + .build()) .build(); @Test @@ -59,13 +64,13 @@ public void testHashCodeAndEquals() { @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullPolicyId() { ActivateTokenIntegrationResponse.of(null, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); } @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullLabel() { ActivateTokenIntegrationResponse.of(TestConstants.Policy.POLICY_ID, null, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); } @@ -79,7 +84,7 @@ public void tryToCreateInstanceWithNullSubject() { public void toJsonReturnsExpected() { final ActivateTokenIntegrationResponse underTest = ActivateTokenIntegrationResponse.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); final JsonObject actualJson = underTest.toJson(FieldType.regularOrSpecial()); assertThat(actualJson).isEqualTo(KNOWN_JSON); @@ -92,7 +97,7 @@ public void createInstanceFromValidJson() { final ActivateTokenIntegrationResponse expectedCommand = ActivateTokenIntegrationResponse.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); assertThat(underTest).isEqualTo(expectedCommand); } diff --git a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationTest.java b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationTest.java index 8884986fcf..a0315cd4e6 100755 --- a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationTest.java +++ b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/ActivateTokenIntegrationTest.java @@ -18,7 +18,9 @@ import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; import java.time.Instant; +import java.util.Collections; +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonParseException; @@ -41,7 +43,9 @@ public final class ActivateTokenIntegrationTest { .set(PolicyCommand.JsonFields.TYPE, ActivateTokenIntegration.TYPE) .set(PolicyCommand.JsonFields.JSON_POLICY_ID, TestConstants.Policy.POLICY_ID.toString()) .set(ActivateTokenIntegration.JSON_LABEL, TestConstants.Policy.LABEL.toString()) - .set(ActivateTokenIntegration.JSON_SUBJECT_ID, TestConstants.Policy.SUBJECT_ID.toString()) + .set(ActivateTokenIntegration.JSON_SUBJECT_IDS, JsonArray.newBuilder() + .add(TestConstants.Policy.SUBJECT_ID.toString()) + .build()) .set(ActivateTokenIntegration.JSON_EXPIRY, Instant.EPOCH.toString()) .build(); @@ -62,13 +66,13 @@ public void testHashCodeAndEquals() { @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullPolicyId() { ActivateTokenIntegration.of(null, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); } @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullLabel() { ActivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, null, - TestConstants.Policy.SUBJECT_ID, Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); } @@ -81,14 +85,14 @@ public void tryToCreateInstanceWithNullSubject() { @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullExpiry() { ActivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, - TestConstants.Policy.LABEL, TestConstants.Policy.SUBJECT_ID, null, TestConstants.EMPTY_DITTO_HEADERS); + TestConstants.Policy.LABEL, Collections.singleton(TestConstants.Policy.SUBJECT_ID), null, TestConstants.EMPTY_DITTO_HEADERS); } @Test public void toJsonReturnsExpected() { final ActivateTokenIntegration underTest = ActivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); final JsonObject actualJson = underTest.toJson(FieldType.regularOrSpecial()); assertThat(actualJson).isEqualTo(KNOWN_JSON); @@ -101,7 +105,7 @@ public void createInstanceFromValidJson() { final ActivateTokenIntegration expectedCommand = ActivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); assertThat(underTest).isEqualTo(expectedCommand); } diff --git a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegrationTest.java b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegrationTest.java index 133cf80ef2..fe70ba9a10 100755 --- a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegrationTest.java +++ b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/DeactivateTokenIntegrationTest.java @@ -17,6 +17,9 @@ import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; +import java.util.Collections; + +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.model.base.json.FieldType; @@ -38,7 +41,9 @@ public final class DeactivateTokenIntegrationTest { .set(PolicyCommand.JsonFields.TYPE, DeactivateTokenIntegration.TYPE) .set(PolicyCommand.JsonFields.JSON_POLICY_ID, TestConstants.Policy.POLICY_ID.toString()) .set(DeactivateTokenIntegration.JSON_LABEL, TestConstants.Policy.LABEL.toString()) - .set(DeactivateTokenIntegration.JSON_SUBJECT_ID, TestConstants.Policy.SUBJECT_ID.toString()) + .set(DeactivateTokenIntegration.JSON_SUBJECT_IDS, JsonArray.newBuilder() + .add(TestConstants.Policy.SUBJECT_ID.toString()) + .build()) .build(); @Test @@ -58,13 +63,13 @@ public void testHashCodeAndEquals() { @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullPolicyId() { DeactivateTokenIntegration.of(null, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); } @Test(expected = NullPointerException.class) public void tryToCreateInstanceWithNullLabel() { DeactivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, null, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); } @@ -78,7 +83,7 @@ public void tryToCreateInstanceWithNullSubject() { public void toJsonReturnsExpected() { final DeactivateTokenIntegration underTest = DeactivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); final JsonObject actualJson = underTest.toJson(FieldType.regularOrSpecial()); assertThat(actualJson).isEqualTo(KNOWN_JSON); @@ -91,7 +96,7 @@ public void createInstanceFromValidJson() { final DeactivateTokenIntegration expectedCommand = DeactivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), TestConstants.EMPTY_DITTO_HEADERS); assertThat(underTest).isEqualTo(expectedCommand); } diff --git a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommandTest.java b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommandTest.java index 4b8cb9142e..8b59f4cee0 100755 --- a/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommandTest.java +++ b/signals/commands/policies/src/test/java/org/eclipse/ditto/signals/commands/policies/actions/TopLevelPolicyActionCommandTest.java @@ -43,7 +43,7 @@ public final class TopLevelPolicyActionCommandTest { private static final ActivateTokenIntegration POLICY_ACTION_COMMAND = ActivateTokenIntegration.of(TestConstants.Policy.POLICY_ID, TestConstants.Policy.LABEL, - TestConstants.Policy.SUBJECT_ID, Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); + Collections.singleton(TestConstants.Policy.SUBJECT_ID), Instant.EPOCH, TestConstants.EMPTY_DITTO_HEADERS); private static final JsonObject KNOWN_JSON = JsonFactory.newObjectBuilder() .set(PolicyCommand.JsonFields.TYPE, TopLevelPolicyActionCommand.TYPE) diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/AbstractPolicyActionEvent.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/AbstractPolicyActionEvent.java index 965a9a0ddc..c92bffdb34 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/AbstractPolicyActionEvent.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/AbstractPolicyActionEvent.java @@ -15,7 +15,9 @@ import java.time.Instant; import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; @@ -57,17 +59,33 @@ protected AbstractPolicyActionEvent(final String type, final PolicyId policyId, * @return the aggregated event. */ protected SubjectsModifiedPartially aggregateWithSubjectCreatedOrModified( - final Map initialModifiedSubjects, + final Map> initialModifiedSubjects, final Collection> otherEvents) { - final Map modifiedSubjects = new HashMap<>(initialModifiedSubjects); + final Map> modifiedSubjects = new HashMap<>(initialModifiedSubjects); for (final PolicyActionEvent event : otherEvents) { if (event instanceof SubjectCreated) { final SubjectCreated subjectCreated = (SubjectCreated) event; - modifiedSubjects.put(subjectCreated.getLabel(), subjectCreated.getSubject()); + final Collection existingSubjects = modifiedSubjects.get(subjectCreated.getLabel()); + final Set mergedSubjects; + if (null == existingSubjects) { + mergedSubjects = new LinkedHashSet<>(); + } else { + mergedSubjects = new LinkedHashSet<>(existingSubjects); + } + mergedSubjects.add(subjectCreated.getSubject()); + modifiedSubjects.put(subjectCreated.getLabel(), mergedSubjects); } else if (event instanceof SubjectModified) { final SubjectModified subjectModified = (SubjectModified) event; - modifiedSubjects.put(subjectModified.getLabel(), subjectModified.getSubject()); + final Collection existingSubjects = modifiedSubjects.get(subjectModified.getLabel()); + final Set mergedSubjects; + if (null == existingSubjects) { + mergedSubjects = new LinkedHashSet<>(); + } else { + mergedSubjects = new LinkedHashSet<>(existingSubjects); + } + mergedSubjects.add(subjectModified.getSubject()); + modifiedSubjects.put(subjectModified.getLabel(), mergedSubjects); } } return SubjectsModifiedPartially.of(getPolicyEntityId(), modifiedSubjects, getRevision(), @@ -81,14 +99,23 @@ protected SubjectsModifiedPartially aggregateWithSubjectCreatedOrModified( * @param otherEvents other subject deletion events. * @return the aggregated event. */ - protected SubjectsDeletedPartially aggregateWithSubjectDeleted(final Map initialDeletedSubjectIds, + protected SubjectsDeletedPartially aggregateWithSubjectDeleted( + final Map> initialDeletedSubjectIds, final Collection> otherEvents) { - final Map deletedSubjectIds = new HashMap<>(initialDeletedSubjectIds); + final Map> deletedSubjectIds = new HashMap<>(initialDeletedSubjectIds); for (final PolicyActionEvent event : otherEvents) { if (event instanceof SubjectDeleted) { final SubjectDeleted subjectDeleted = (SubjectDeleted) event; - deletedSubjectIds.put(subjectDeleted.getLabel(), subjectDeleted.getSubjectId()); + final Collection existingSubjectIds = deletedSubjectIds.get(subjectDeleted.getLabel()); + final Set mergedSubjectIds; + if (null == existingSubjectIds) { + mergedSubjectIds = new LinkedHashSet<>(); + } else { + mergedSubjectIds = new LinkedHashSet<>(existingSubjectIds); + } + mergedSubjectIds.add(subjectDeleted.getSubjectId()); + deletedSubjectIds.put(subjectDeleted.getLabel(), mergedSubjectIds); } } return SubjectsDeletedPartially.of(getPolicyEntityId(), deletedSubjectIds, getRevision(), diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectCreated.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectCreated.java index 172c9303fb..972ac561f4 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectCreated.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectCreated.java @@ -16,6 +16,7 @@ import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -263,8 +264,8 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js @Override public SubjectsModifiedPartially aggregateWith(final Collection> otherPolicyActionEvents) { - final Map initialCreatedSubjects = - Stream.of(0).collect(Collectors.toMap(i -> label, i -> subject)); + final Map> initialCreatedSubjects = + Stream.of(0).collect(Collectors.toMap(i -> label, i -> Collections.singleton(subject))); return aggregateWithSubjectCreatedOrModified(initialCreatedSubjects, otherPolicyActionEvents); } diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectDeleted.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectDeleted.java index dfd9a8d712..6828f1c4c6 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectDeleted.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectDeleted.java @@ -16,6 +16,7 @@ import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -249,8 +250,8 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js @Override public SubjectsDeletedPartially aggregateWith(final Collection> otherPolicyActionEvents) { - final Map initialDeletedSubjectId = - Stream.of(0).collect(Collectors.toMap(i -> label, i -> subjectId)); + final Map> initialDeletedSubjectId = + Stream.of(0).collect(Collectors.toMap(i -> label, i -> Collections.singleton(subjectId))); return aggregateWithSubjectDeleted(initialDeletedSubjectId, otherPolicyActionEvents); } diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectModified.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectModified.java index 7f29411e60..ecf495fd50 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectModified.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectModified.java @@ -16,6 +16,7 @@ import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -263,8 +264,8 @@ protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final Js @Override public SubjectsModifiedPartially aggregateWith(final Collection> otherPolicyActionEvents) { - final Map initialModifiedSubjects = - Stream.of(0).collect(Collectors.toMap(i -> label, i -> subject)); + final Map> initialModifiedSubjects = + Stream.of(0).collect(Collectors.toMap(i -> label, i -> Collections.singleton(subject))); return aggregateWithSubjectCreatedOrModified(initialModifiedSubjects, otherPolicyActionEvents); } diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartially.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartially.java index 5c5c57c61b..7a8720a608 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartially.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartially.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -68,11 +69,11 @@ public final class SubjectsDeletedPartially extends AbstractPolicyActionEvent deletedSubjectIds; + private final Map> deletedSubjectIds; private SubjectsDeletedPartially( final PolicyId policyId, - final Map deletedSubjectIds, + final Map> deletedSubjectIds, final long revision, @Nullable final Instant timestamp, final DittoHeaders dittoHeaders) { @@ -106,7 +107,7 @@ private SubjectsDeletedPartially( * @throws NullPointerException if any argument is {@code null}. */ public static SubjectsDeletedPartially of(final PolicyId policyId, - final Map deletedSubjectIds, + final Map> deletedSubjectIds, final long revision, final DittoHeaders dittoHeaders) { @@ -125,7 +126,7 @@ public static SubjectsDeletedPartially of(final PolicyId policyId, * @throws NullPointerException if any argument but {@code timestamp} is {@code null}. */ public static SubjectsDeletedPartially of(final PolicyId policyId, - final Map deletedSubjectIds, + final Map> deletedSubjectIds, final long revision, @Nullable final Instant timestamp, final DittoHeaders dittoHeaders) { @@ -161,7 +162,7 @@ public static SubjectsDeletedPartially fromJson(final JsonObject jsonObject, fin * * @return the deleted subject IDs. */ - public Map getDeletedSubjectIds() { + public Map> getDeletedSubjectIds() { return deletedSubjectIds; } @@ -230,17 +231,25 @@ public String toString() { "]"; } - private static JsonObject deletedSubjectsToJson(final Map deletedSubjects) { + private static JsonObject deletedSubjectsToJson(final Map> deletedSubjects) { return deletedSubjects.entrySet() .stream() - .map(entry -> JsonField.newInstance(entry.getKey(), JsonValue.of(entry.getValue()))) + .map(entry -> JsonField.newInstance(entry.getKey(), entry.getValue().stream() + .map(SubjectId::toString) + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()))) .collect(JsonCollectors.fieldsToObject()); } - private static Map deletedSubjectsFromJson(final JsonObject jsonObject) { - final Map map = jsonObject.stream() + private static Map> deletedSubjectsFromJson(final JsonObject jsonObject) { + final Map> map = jsonObject.stream() .collect(Collectors.toMap(field -> Label.of(field.getKeyName()), - field -> SubjectId.newInstance(field.getValue().asString()))); + field -> field.getValue().asArray().stream() + .map(JsonValue::asString) + .map(SubjectId::newInstance) + .collect(Collectors.toCollection(LinkedHashSet::new)) + )); return Collections.unmodifiableMap(map); } + } diff --git a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartially.java b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartially.java index 00cba2f884..7de76939a8 100755 --- a/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartially.java +++ b/signals/events/policies/src/main/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartially.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -47,8 +48,7 @@ import org.eclipse.ditto.signals.events.base.EventJsonDeserializer; /** - * This event is emitted after several {@link org.eclipse.ditto.model.policies.Subject}s of a Policy - * in multiple policy entries were modified/created. + * This event is emitted after several {@link Subject}s of a Policy in multiple policy entries were modified/created. * * @since 2.0.0 */ @@ -69,11 +69,11 @@ public final class SubjectsModifiedPartially extends AbstractPolicyActionEvent JSON_MODIFIED_SUBJECTS = JsonFactory.newJsonObjectFieldDefinition("modifiedSubjects", FieldType.REGULAR, JsonSchemaVersion.V_2); - private final Map modifiedSubjects; + private final Map> modifiedSubjects; private SubjectsModifiedPartially( final PolicyId policyId, - final Map modifiedSubjects, + final Map> modifiedSubjects, final long revision, @Nullable final Instant timestamp, final DittoHeaders dittoHeaders) { @@ -107,7 +107,7 @@ private SubjectsModifiedPartially( * @throws NullPointerException if any argument is {@code null}. */ public static SubjectsModifiedPartially of(final PolicyId policyId, - final Map activatedSubjects, + final Map> activatedSubjects, final long revision, final DittoHeaders dittoHeaders) { @@ -126,7 +126,7 @@ public static SubjectsModifiedPartially of(final PolicyId policyId, * @throws NullPointerException if any argument but {@code timestamp} is {@code null}. */ public static SubjectsModifiedPartially of(final PolicyId policyId, - final Map activatedSubjects, + final Map> activatedSubjects, final long revision, @Nullable final Instant timestamp, final DittoHeaders dittoHeaders) { @@ -162,7 +162,7 @@ public static SubjectsModifiedPartially fromJson(final JsonObject jsonObject, fi * * @return the modified subjects. */ - public Map getModifiedSubjects() { + public Map> getModifiedSubjects() { return modifiedSubjects; } @@ -230,33 +230,35 @@ public String toString() { "]"; } - private static JsonObject subjectToJsonWithId(final Subject subject) { - return JsonObject.newBuilder() - .set(subject.getId(), subject.toJson()) - .build(); + private static JsonObject subjectsToJsonWithId(final Collection subjects) { + return subjects.stream() + .map(subject -> JsonField.newInstance(subject.getId(), subject.toJson())) + .collect(JsonCollectors.fieldsToObject()); } - private static Subject subjectFromJsonWithId(final JsonObject jsonObject) { + private static Collection subjectsFromJsonWithId(final JsonObject jsonObject) { if (jsonObject.getSize() != 1) { throw JsonParseException.newBuilder() .message("Unexpected subject with ID format") .build(); } - final JsonField jsonField = jsonObject.iterator().next(); - return PoliciesModelFactory.newSubject(jsonField.getKeyName(), jsonField.getValue().asObject()); + return jsonObject.stream() + .map(jsonField -> PoliciesModelFactory.newSubject( + jsonField.getKeyName(), jsonField.getValue().asObject())) + .collect(Collectors.toCollection(LinkedHashSet::new)); } - private static JsonObject modifiedSubjectsToJson(final Map modifiedSubjects) { + private static JsonObject modifiedSubjectsToJson(final Map> modifiedSubjects) { return modifiedSubjects.entrySet() .stream() - .map(entry -> JsonField.newInstance(entry.getKey(), subjectToJsonWithId(entry.getValue()))) + .map(entry -> JsonField.newInstance(entry.getKey(), subjectsToJsonWithId(entry.getValue()))) .collect(JsonCollectors.fieldsToObject()); } - private static Map modifiedSubjectsFromJson(final JsonObject jsonObject) { - final Map map = jsonObject.stream() + private static Map> modifiedSubjectsFromJson(final JsonObject jsonObject) { + final Map> map = jsonObject.stream() .collect(Collectors.toMap(field -> Label.of(field.getKeyName()), - field -> subjectFromJsonWithId(field.getValue().asObject()))); + field -> subjectsFromJsonWithId(field.getValue().asObject()))); return Collections.unmodifiableMap(map); } } diff --git a/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartiallyTest.java b/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartiallyTest.java index 2069ce4f72..cbf71a12fe 100755 --- a/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartiallyTest.java +++ b/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsDeletedPartiallyTest.java @@ -18,13 +18,15 @@ import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonFactory; import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.json.JsonValue; import org.eclipse.ditto.model.base.json.FieldType; import org.eclipse.ditto.model.policies.Label; import org.eclipse.ditto.model.policies.Subject; @@ -39,11 +41,15 @@ */ public final class SubjectsDeletedPartiallyTest { - private static final Map DELETED_SUBJECT_IDS = IntStream.of(0).boxed() - .collect(Collectors.toMap(i -> TestConstants.Policy.LABEL, i -> TestConstants.Policy.SUBJECT_ID)); + private static final Map> DELETED_SUBJECT_IDS = IntStream.of(0).boxed() + .collect(Collectors.toMap( + i -> TestConstants.Policy.LABEL, + i -> Collections.singleton(TestConstants.Policy.SUBJECT_ID))); private static final JsonObject DELETED_SUBJECT_IDS_JSON = JsonObject.newBuilder() - .set(TestConstants.Policy.LABEL, JsonValue.of(TestConstants.Policy.SUBJECT_ID)) + .set(TestConstants.Policy.LABEL, JsonArray.newBuilder() + .add(TestConstants.Policy.SUBJECT_ID.toString()) + .build()) .build(); private static final JsonObject KNOWN_JSON = JsonFactory.newObjectBuilder() diff --git a/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartiallyTest.java b/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartiallyTest.java index 43657f4628..12ea157fc0 100755 --- a/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartiallyTest.java +++ b/signals/events/policies/src/test/java/org/eclipse/ditto/signals/events/policies/SubjectsModifiedPartiallyTest.java @@ -18,6 +18,8 @@ import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -38,8 +40,10 @@ */ public final class SubjectsModifiedPartiallyTest { - private static final Map MODIFIED_SUBJECTS = IntStream.of(0).boxed() - .collect(Collectors.toMap(i -> TestConstants.Policy.LABEL, i -> TestConstants.Policy.SUBJECT)); + private static final Map> MODIFIED_SUBJECTS = IntStream.of(0).boxed() + .collect(Collectors.toMap( + i -> TestConstants.Policy.LABEL, + i -> Collections.singleton(TestConstants.Policy.SUBJECT))); private static final JsonObject MODIFIED_SUBJECTS_JSON = JsonObject.newBuilder() .set(TestConstants.Policy.LABEL, JsonObject.newBuilder()