diff --git a/services/policies/persistence/pom.xml b/services/policies/persistence/pom.xml
index 0237127e0f..ab8e664c17 100755
--- a/services/policies/persistence/pom.xml
+++ b/services/policies/persistence/pom.xml
@@ -56,6 +56,10 @@
org.eclipse.ditto
ditto-services-models-policies
+
+ org.eclipse.ditto
+ ditto-model-placeholders
+
org.eclipse.ditto
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholder.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholder.java
new file mode 100644
index 0000000000..f89d582811
--- /dev/null
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholder.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.placeholders;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.ditto.model.placeholders.Placeholder;
+import org.eclipse.ditto.model.placeholders.PlaceholderFactory;
+import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException;
+import org.eclipse.ditto.model.policies.PolicyEntry;
+import org.eclipse.ditto.model.policies.SubjectId;
+
+/**
+ * Placeholder with prefix policy-entry.
+ */
+public final class PolicyEntryPlaceholder implements Placeholder {
+
+ private static PolicyEntryPlaceholder INSTANCE = new PolicyEntryPlaceholder();
+
+ private static final String PREFIX = "policy-entry";
+ private static final String LABEL = "label";
+
+ /**
+ * Resolve a subject ID containing policy-entry placeholders.
+ *
+ * @param entry the policy entry.
+ * @param subjectIdWithPlaceholder the subject ID containing placeholders.
+ * @return the subject ID after resolution, or an empty optional if it contains an unresolvable placeholder.
+ * @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.
+ */
+ public static SubjectId resolveSubjectId(final PolicyEntry entry,
+ final SubjectId subjectIdWithPlaceholder) {
+ return PlaceholderFactory.newExpressionResolver(PlaceholderFactory.newPlaceholderResolver(INSTANCE, entry))
+ .resolve(subjectIdWithPlaceholder.toString())
+ .toOptional()
+ .map(SubjectId::newInstance)
+ .orElseThrow(() ->
+ UnresolvedPlaceholderException.newBuilder(subjectIdWithPlaceholder.toString()).build());
+ }
+
+ @Override
+ public Optional resolve(final PolicyEntry policyEntry, final String name) {
+ return supports(name) ? Optional.of(policyEntry.getLabel().toString()) : Optional.empty();
+ }
+
+ @Override
+ public String getPrefix() {
+ return PREFIX;
+ }
+
+ @Override
+ public List getSupportedNames() {
+ return List.of(LABEL);
+ }
+
+ @Override
+ public boolean supports(final String name) {
+ return LABEL.equals(name);
+ }
+}
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/package-info.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/package-info.java
new file mode 100644
index 0000000000..54021da981
--- /dev/null
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/package-info.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+@org.eclipse.ditto.utils.jsr305.annotations.AllParametersAndReturnValuesAreNonnullByDefault
+package org.eclipse.ditto.services.policies.persistence.actors.placeholders;
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/AbstractPolicyCommandStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/AbstractPolicyCommandStrategy.java
index 1e818cef15..d925bafb32 100644
--- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/AbstractPolicyCommandStrategy.java
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/AbstractPolicyCommandStrategy.java
@@ -29,6 +29,7 @@
import org.eclipse.ditto.model.base.exceptions.DittoRuntimeException;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
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;
@@ -36,6 +37,7 @@
import org.eclipse.ditto.model.policies.Subject;
import org.eclipse.ditto.model.policies.SubjectExpiry;
import org.eclipse.ditto.model.policies.SubjectExpiryInvalidException;
+import org.eclipse.ditto.model.policies.SubjectType;
import org.eclipse.ditto.model.policies.Subjects;
import org.eclipse.ditto.services.policies.common.config.PolicyConfig;
import org.eclipse.ditto.services.utils.headers.conditional.ConditionalHeadersValidator;
@@ -60,6 +62,8 @@
abstract class AbstractPolicyCommandStrategy>
extends AbstractConditionHeaderCheckingCommandStrategy {
+ static SubjectType TOKEN_INTEGRATION = PoliciesModelFactory.newSubjectType("actions/activateTokenIntegration");
+
private final PolicyExpiryGranularity policyExpiryGranularity;
AbstractPolicyCommandStrategy(final Class theMatchingClass, final PolicyConfig policyConfig) {
@@ -86,7 +90,7 @@ static PolicyExpiryGranularity calculateTemporalUnitAndAmount(final PolicyConfig
final Duration minutes = granularity.truncatedTo(ChronoUnit.MINUTES);
if (!minutes.isZero()) {
amount = minutes.dividedBy(ChronoUnit.MINUTES.getDuration());
- return new PolicyExpiryGranularity(ChronoUnit.MINUTES, amount, ChronoUnit.HOURS);
+ return new PolicyExpiryGranularity(ChronoUnit.MINUTES, amount, ChronoUnit.HOURS);
}
final Duration seconds = granularity.truncatedTo(ChronoUnit.SECONDS);
@@ -295,6 +299,7 @@ static DittoRuntimeException policyEntryInvalid(final PolicyId policyId, final L
}
private static class PolicyExpiryGranularity {
+
private final ChronoUnit temporalUnit;
private final long amount;
private final ChronoUnit parentTemporalUnit;
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategy.java
new file mode 100644
index 0000000000..21cb2c4198
--- /dev/null
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategy.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2019 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.strategies.commands;
+
+import static org.eclipse.ditto.model.base.common.ConditionChecker.checkNotNull;
+
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import org.eclipse.ditto.model.base.entity.metadata.Metadata;
+import org.eclipse.ditto.model.base.exceptions.DittoRuntimeException;
+import org.eclipse.ditto.model.base.headers.DittoHeaders;
+import org.eclipse.ditto.model.base.headers.entitytag.EntityTag;
+import org.eclipse.ditto.model.policies.Label;
+import org.eclipse.ditto.model.policies.Policy;
+import org.eclipse.ditto.model.policies.PolicyEntry;
+import org.eclipse.ditto.model.policies.PolicyId;
+import org.eclipse.ditto.model.policies.Subject;
+import org.eclipse.ditto.model.policies.SubjectExpiry;
+import org.eclipse.ditto.model.policies.SubjectId;
+import org.eclipse.ditto.services.models.policies.PoliciesValidator;
+import org.eclipse.ditto.services.policies.common.config.PolicyConfig;
+import org.eclipse.ditto.services.policies.persistence.actors.placeholders.PolicyEntryPlaceholder;
+import org.eclipse.ditto.services.utils.persistentactors.results.Result;
+import org.eclipse.ditto.services.utils.persistentactors.results.ResultFactory;
+import org.eclipse.ditto.signals.commands.policies.modify.ActivateSubject;
+import org.eclipse.ditto.signals.commands.policies.modify.ActivateSubjectResponse;
+import org.eclipse.ditto.signals.events.policies.PolicyEvent;
+import org.eclipse.ditto.signals.events.policies.SubjectActivated;
+
+/**
+ * This strategy handles the {@link org.eclipse.ditto.signals.commands.policies.modify.ActivateSubject} command.
+ */
+final class ActivateSubjectStrategy extends AbstractPolicyCommandStrategy {
+
+ ActivateSubjectStrategy(final PolicyConfig policyConfig) {
+ super(ActivateSubject.class, policyConfig);
+ }
+
+ @Override
+ protected Result doApply(final Context context,
+ @Nullable final Policy policy,
+ final long nextRevision,
+ final ActivateSubject command,
+ @Nullable final Metadata metadata) {
+
+ final Policy nonNullPolicy = checkNotNull(policy, "policy");
+ final PolicyId policyId = context.getState();
+ final Label label = command.getLabel();
+ final SubjectExpiry commandSubjectExpiry = SubjectExpiry.newInstance(command.getExpiry());
+ final DittoHeaders dittoHeaders = command.getDittoHeaders();
+
+ final Optional optionalEntry = nonNullPolicy.getEntryFor(label);
+ if (optionalEntry.isPresent()) {
+ final SubjectId subjectId;
+ try {
+ subjectId = PolicyEntryPlaceholder.resolveSubjectId(optionalEntry.get(), command.getSubjectId());
+ } catch (final DittoRuntimeException e) {
+ return ResultFactory.newErrorResult(e, command);
+ }
+ final Subject subject = Subject.newInstance(subjectId, TOKEN_INTEGRATION, commandSubjectExpiry);
+ final Subject adjustedSubject = potentiallyAdjustSubject(subject);
+ final ActivateSubject adjustedCommand = ActivateSubject.of(
+ command.getEntityId(), command.getLabel(), adjustedSubject.getId(),
+ adjustedSubject.getExpiry().orElseThrow().getTimestamp(), dittoHeaders);
+
+ // Validation is necessary because activation may add expiry to the policy admin subject.
+ final Policy newPolicy = nonNullPolicy.setSubjectFor(label, adjustedSubject);
+ final PoliciesValidator validator = PoliciesValidator.newInstance(newPolicy);
+ if (validator.isValid()) {
+ final PolicyEvent> event =
+ SubjectActivated.of(policyId, label, adjustedSubject, nextRevision, getEventTimestamp(),
+ dittoHeaders);
+ final ActivateSubjectResponse rawResponse =
+ ActivateSubjectResponse.of(policyId, label, adjustedSubject.getId(), dittoHeaders);
+ // do not append ETag - activated subjects do not support ETags.
+ return ResultFactory.newMutationResult(adjustedCommand, event, rawResponse);
+ } else {
+ return ResultFactory.newErrorResult(
+ policyEntryInvalid(policyId, label, validator.getReason().orElse(null), dittoHeaders),
+ command);
+ }
+ } else {
+ return ResultFactory.newErrorResult(policyEntryNotFound(policyId, label, dittoHeaders), command);
+ }
+ }
+
+ @Override
+ public Optional previousEntityTag(final ActivateSubject command, @Nullable final Policy previousEntity) {
+ // activated subjects do not support entity tag
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional nextEntityTag(final ActivateSubject command, @Nullable final Policy newEntity) {
+ // activated subjects do not support entity tag
+ return Optional.empty();
+ }
+}
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/PolicyCommandStrategies.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/PolicyCommandStrategies.java
index 62a58ef9fc..907878d038 100755
--- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/PolicyCommandStrategies.java
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/PolicyCommandStrategies.java
@@ -51,6 +51,7 @@ private PolicyCommandStrategies(final PolicyConfig policyConfig) {
addStrategy(new ModifyPolicyEntryStrategy(policyConfig));
addStrategy(new RetrievePolicyEntryStrategy(policyConfig));
addStrategy(new DeletePolicyEntryStrategy(policyConfig));
+ addStrategy(new ActivateSubjectStrategy(policyConfig));
// Subjects
addStrategy(new ModifySubjectsStrategy(policyConfig));
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/PolicyEventStrategies.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/PolicyEventStrategies.java
index fa230c40a3..ac95461fe8 100755
--- a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/PolicyEventStrategies.java
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/PolicyEventStrategies.java
@@ -26,6 +26,7 @@
import org.eclipse.ditto.signals.events.policies.ResourceDeleted;
import org.eclipse.ditto.signals.events.policies.ResourceModified;
import org.eclipse.ditto.signals.events.policies.ResourcesModified;
+import org.eclipse.ditto.signals.events.policies.SubjectActivated;
import org.eclipse.ditto.signals.events.policies.SubjectCreated;
import org.eclipse.ditto.signals.events.policies.SubjectDeleted;
import org.eclipse.ditto.signals.events.policies.SubjectModified;
@@ -50,6 +51,7 @@ private PolicyEventStrategies() {
addStrategy(SubjectCreated.class, new SubjectCreatedStrategy());
addStrategy(SubjectModified.class, new SubjectModifiedStrategy());
addStrategy(SubjectDeleted.class, new SubjectDeletedStrategy());
+ addStrategy(SubjectActivated.class, new SubjectActivatedStrategy());
addStrategy(ResourcesModified.class, new ResourcesModifiedStrategy());
addStrategy(ResourceCreated.class, new ResourceCreatedStrategy());
addStrategy(ResourceModified.class, new ResourceModifiedStrategy());
diff --git a/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategy.java b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategy.java
new file mode 100644
index 0000000000..e0266de73f
--- /dev/null
+++ b/services/policies/persistence/src/main/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.strategies.events;
+
+import org.eclipse.ditto.model.policies.PolicyBuilder;
+import org.eclipse.ditto.signals.events.policies.SubjectActivated;
+
+/**
+ * This strategy handles {@link org.eclipse.ditto.signals.events.policies.SubjectActivated} events.
+ */
+final class SubjectActivatedStrategy extends AbstractPolicyEventStrategy {
+
+ @Override
+ protected PolicyBuilder applyEvent(final SubjectActivated sm, final PolicyBuilder policyBuilder) {
+ return policyBuilder.setSubjectFor(sm.getLabel(), sm.getSubject());
+ }
+}
diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/TestConstants.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/TestConstants.java
index 4cbe0212ee..33a4a5a735 100755
--- a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/TestConstants.java
+++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/TestConstants.java
@@ -12,6 +12,7 @@
*/
package org.eclipse.ditto.services.policies.persistence;
+import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
@@ -25,6 +26,7 @@
import org.eclipse.ditto.model.policies.Resource;
import org.eclipse.ditto.model.policies.ResourceKey;
import org.eclipse.ditto.model.policies.Subject;
+import org.eclipse.ditto.model.policies.SubjectExpiry;
import org.eclipse.ditto.model.policies.SubjectId;
import org.eclipse.ditto.model.policies.SubjectIssuer;
import org.eclipse.ditto.model.policies.SubjectType;
@@ -67,13 +69,15 @@ public static final class Policy {
public static final EffectedPermissions READ_GRANTED =
EffectedPermissions.newInstance(Collections.singleton(PERMISSION_READ), Collections.emptySet());
public static final EffectedPermissions READ_WRITE_REVOKED =
- EffectedPermissions.newInstance( Collections.emptySet(), Arrays.asList(PERMISSION_READ, PERMISSION_WRITE));
+ EffectedPermissions.newInstance(Collections.emptySet(),
+ Arrays.asList(PERMISSION_READ, PERMISSION_WRITE));
public static final ResourceKey FEATURES_RESOURCE_KEY = ResourceKey.newInstance("thing", "/features");
public static final ResourceKey NEW_ATTRIBUTE_RESOURCE_KEY = ResourceKey.newInstance("thing",
"/attribute/new");
public static final Resource NEW_ATTRIBUTE_RESOURCE = Resource.newInstance(NEW_ATTRIBUTE_RESOURCE_KEY,
READ_GRANTED);
- public static final Resource FEATURES_RESOURCE = Resource.newInstance(FEATURES_RESOURCE_KEY, READ_WRITE_REVOKED);
+ public static final Resource FEATURES_RESOURCE =
+ Resource.newInstance(FEATURES_RESOURCE_KEY, READ_WRITE_REVOKED);
public static final Resource
MODIFIED_FEATURES_RESOURCE = Resource.newInstance(FEATURES_RESOURCE_KEY, READ_GRANTED);
public static final Label SUPPORT_LABEL = Label.of("Support");
@@ -84,6 +88,11 @@ public static final class Policy {
public static final SubjectId ADDITIONAL_SUPPORT_SUBJECT_ID = SubjectId.newInstance(SubjectIssuer.GOOGLE,
UUID.randomUUID().toString());
public static final Subject ADDITIONAL_SUPPORT_SUBJECT = Subject.newInstance(ADDITIONAL_SUPPORT_SUBJECT_ID);
+ public static final Subject SUPPORT_SUBJECT_WITH_EXPIRY = PoliciesModelFactory.newSubject(
+ SUPPORT_SUBJECT_ID,
+ SubjectType.UNKNOWN,
+ SubjectExpiry.newInstance(Instant.now().plus(Duration.ofDays(1L)))
+ );
public static org.eclipse.ditto.model.policies.Policy policyWithRandomName() {
return PoliciesModelFactory.newPolicyBuilder(PolicyId.inNamespaceWithRandomName("test"))
@@ -125,12 +134,13 @@ public static org.eclipse.ditto.model.policies.PolicyEntry policyEntryWithLabel(
/**
* A Policy to be used in persistence tests.
*/
- public static final org.eclipse.ditto.model.policies.Policy POLICY = PoliciesModelFactory.newPolicyBuilder(POLICY_ID)
- .setSubjectFor(LABEL, SUPPORT_SUBJECT_ID, SUBJECT_TYPE)
- .setGrantedPermissionsFor(LABEL, RESOURCE_TYPE_POLICY, "/", PERMISSION_READ, PERMISSION_WRITE)
- .setGrantedPermissionsFor(LABEL, RESOURCE_TYPE_THING, "/", PERMISSION_READ, PERMISSION_WRITE)
- .setRevokedPermissionsFor(LABEL, RESOURCE_TYPE_THING, RESOURCE_PATH, PERMISSION_WRITE)
- .build();
+ public static final org.eclipse.ditto.model.policies.Policy POLICY =
+ PoliciesModelFactory.newPolicyBuilder(POLICY_ID)
+ .setSubjectFor(LABEL, SUPPORT_SUBJECT_ID, SUBJECT_TYPE)
+ .setGrantedPermissionsFor(LABEL, RESOURCE_TYPE_POLICY, "/", PERMISSION_READ, PERMISSION_WRITE)
+ .setGrantedPermissionsFor(LABEL, RESOURCE_TYPE_THING, "/", PERMISSION_READ, PERMISSION_WRITE)
+ .setRevokedPermissionsFor(LABEL, RESOURCE_TYPE_THING, RESOURCE_PATH, PERMISSION_WRITE)
+ .build();
private Policy() {
throw new AssertionError();
diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholderTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholderTest.java
new file mode 100644
index 0000000000..cbf78b1401
--- /dev/null
+++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/placeholders/PolicyEntryPlaceholderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.placeholders;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException;
+import org.eclipse.ditto.model.policies.Label;
+import org.eclipse.ditto.model.policies.PoliciesModelFactory;
+import org.eclipse.ditto.model.policies.PolicyEntry;
+import org.eclipse.ditto.model.policies.SubjectId;
+import org.eclipse.ditto.model.policies.SubjectIdInvalidException;
+import org.junit.Test;
+
+/**
+ * Tests {@link PolicyEntryPlaceholder}.
+ */
+public final class PolicyEntryPlaceholderTest {
+
+ private static final Label LABEL = Label.of("label");
+ private static final PolicyEntry ENTRY = PoliciesModelFactory.newPolicyEntry(LABEL, "{\n" +
+ " \"subjects\": {\n" +
+ " \"abc:def\": {\n" +
+ " \"type\": \"def\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"resources\": {\n" +
+ " \"policy:/\": {\n" +
+ " \"grant\": [\"READ\"],\n" +
+ " \"revoke\": [\"WRITE\"]\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+
+ @Test
+ public void resolveSubjectWithoutPlaceholder() {
+ final SubjectId subjectId = SubjectId.newInstance("integration:hello");
+ assertThat(PolicyEntryPlaceholder.resolveSubjectId(ENTRY, subjectId)).isEqualTo(subjectId);
+ }
+
+ @Test
+ public void resolveSubjectWithPlaceholder() {
+ final SubjectId subjectId = SubjectId.newInstance("integration:{{policy-entry:label}}");
+ assertThat(PolicyEntryPlaceholder.resolveSubjectId(ENTRY, subjectId))
+ .isEqualTo(SubjectId.newInstance("integration:label"));
+ }
+
+ @Test
+ public void doNotResolveSubjectWithSupportedAndUnresolvedPlaceholder() {
+ final SubjectId subjectId = SubjectId.newInstance("integration:{{fn:delete()}}");
+ assertThatExceptionOfType(UnresolvedPlaceholderException.class)
+ .isThrownBy(() -> PolicyEntryPlaceholder.resolveSubjectId(ENTRY, subjectId));
+ }
+
+ @Test
+ public void throwErrorOnUnsupportedPlaceholder() {
+ final SubjectId subjectId = SubjectId.newInstance("integration:{{connection:id}}");
+ assertThatExceptionOfType(UnresolvedPlaceholderException.class)
+ .isThrownBy(() -> PolicyEntryPlaceholder.resolveSubjectId(ENTRY, subjectId));
+ }
+
+ @Test
+ public void throwSubjectIdInvalidException() {
+ final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}");
+ assertThatExceptionOfType(SubjectIdInvalidException.class)
+ .isThrownBy(() -> PolicyEntryPlaceholder.resolveSubjectId(ENTRY, subjectId));
+ }
+}
diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategyTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategyTest.java
new file mode 100644
index 0000000000..a29cd20e23
--- /dev/null
+++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/commands/ActivateSubjectStrategyTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.strategies.commands;
+
+import static org.eclipse.ditto.services.policies.persistence.TestConstants.Policy.LABEL;
+import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
+import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import org.eclipse.ditto.model.base.headers.DittoHeaders;
+import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException;
+import org.eclipse.ditto.model.policies.PolicyId;
+import org.eclipse.ditto.model.policies.SubjectId;
+import org.eclipse.ditto.model.policies.SubjectIdInvalidException;
+import org.eclipse.ditto.model.policies.SubjectIssuer;
+import org.eclipse.ditto.services.policies.common.config.DefaultPolicyConfig;
+import org.eclipse.ditto.services.policies.persistence.TestConstants;
+import org.eclipse.ditto.services.utils.persistentactors.commands.CommandStrategy;
+import org.eclipse.ditto.signals.commands.policies.modify.ActivateSubject;
+import org.eclipse.ditto.signals.commands.policies.modify.ActivateSubjectResponse;
+import org.eclipse.ditto.signals.events.policies.SubjectActivated;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.typesafe.config.ConfigFactory;
+
+/**
+ * Unit test for {@link ActivateSubjectStrategy}.
+ */
+public final class ActivateSubjectStrategyTest extends AbstractPolicyCommandStrategyTest {
+
+ private ActivateSubjectStrategy underTest;
+
+ @Before
+ public void setUp() {
+ underTest = new ActivateSubjectStrategy(DefaultPolicyConfig.of(ConfigFactory.load("policy-test")));
+ }
+
+ @Test
+ public void assertImmutability() {
+ assertInstancesOf(ActivateSubjectStrategy.class, areImmutable());
+ }
+
+ @Test
+ public void activateSubject() {
+ final CommandStrategy.Context context = getDefaultContext();
+ final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
+ final SubjectId subjectId =
+ SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{policy-entry:label}}:this-is-me");
+ final SubjectId expectedSubjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, LABEL + ":this-is-me");
+ final DittoHeaders dittoHeaders = DittoHeaders.empty();
+ final ActivateSubject command =
+ ActivateSubject.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders);
+ assertModificationResult(underTest, TestConstants.Policy.POLICY, command,
+ SubjectActivated.class,
+ ActivateSubjectResponse.of(context.getState(), LABEL, expectedSubjectId, dittoHeaders));
+ }
+
+ @Test
+ public void activateInvalidSubject() {
+ final CommandStrategy.Context context = getDefaultContext();
+ final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
+ final SubjectId subjectId = SubjectId.newInstance("{{policy-entry:label}}");
+ final DittoHeaders dittoHeaders = DittoHeaders.empty();
+ final ActivateSubject command = ActivateSubject.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders);
+ assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
+ SubjectIdInvalidException.newBuilder(LABEL).build());
+ }
+
+ @Test
+ public void activateUnresolvableSubject() {
+ final CommandStrategy.Context context = getDefaultContext();
+ final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
+ final SubjectId subjectId = SubjectId.newInstance(SubjectIssuer.INTEGRATION, "{{fn:delete()}}");
+ final DittoHeaders dittoHeaders = DittoHeaders.empty();
+ final ActivateSubject command = ActivateSubject.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders);
+ assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
+ UnresolvedPlaceholderException.newBuilder("integration:{{fn:delete()}}").build());
+ }
+
+ @Test
+ public void activateSubjectWithUnsupportedPlaceholder() {
+ final CommandStrategy.Context context = getDefaultContext();
+ final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
+ final SubjectId subjectId = SubjectId.newInstance("{{request:subjectId}}");
+ final DittoHeaders dittoHeaders = DittoHeaders.empty();
+ final ActivateSubject command = ActivateSubject.of(context.getState(), LABEL, subjectId, expiry, dittoHeaders);
+ assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
+ UnresolvedPlaceholderException.newBuilder("{{request:subjectId}}").build());
+ }
+}
diff --git a/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategyTest.java b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategyTest.java
new file mode 100644
index 0000000000..0659485d62
--- /dev/null
+++ b/services/policies/persistence/src/test/java/org/eclipse/ditto/services/policies/persistence/actors/strategies/events/SubjectActivatedStrategyTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.ditto.services.policies.persistence.actors.strategies.events;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.ditto.services.policies.persistence.TestConstants.Policy.SUPPORT_LABEL;
+import static org.eclipse.ditto.services.policies.persistence.TestConstants.Policy.SUPPORT_SUBJECT_ID;
+import static org.eclipse.ditto.services.policies.persistence.TestConstants.Policy.SUPPORT_SUBJECT_WITH_EXPIRY;
+
+import java.time.Instant;
+
+import org.eclipse.ditto.model.base.headers.DittoHeaders;
+import org.eclipse.ditto.model.policies.Policy;
+import org.eclipse.ditto.model.policies.PolicyEntry;
+import org.eclipse.ditto.model.policies.PolicyId;
+import org.eclipse.ditto.model.policies.Subject;
+import org.eclipse.ditto.signals.events.policies.SubjectActivated;
+
+/**
+ * Tests {@link SubjectActivatedStrategy}.
+ */
+public class SubjectActivatedStrategyTest extends AbstractPolicyEventStrategyTest {
+
+ @Override
+ SubjectActivatedStrategy getStrategyUnderTest() {
+ return new SubjectActivatedStrategy();
+ }
+
+ @Override
+ SubjectActivated getPolicyEvent(final Instant instant, final Policy policy) {
+ final PolicyId policyId = policy.getEntityId().orElseThrow();
+ return SubjectActivated.of(policyId, SUPPORT_LABEL, SUPPORT_SUBJECT_WITH_EXPIRY, 10L, instant,
+ DittoHeaders.empty());
+ }
+
+ @Override
+ protected void additionalAssertions(final Policy policyWithEventApplied) {
+ final Subject activatedSubject = policyWithEventApplied.getEntryFor(SUPPORT_LABEL)
+ .map(PolicyEntry::getSubjects)
+ .flatMap(subjects -> subjects.getSubject(SUPPORT_SUBJECT_ID))
+ .orElseThrow(() -> new AssertionError("Expected subject " + SUPPORT_SUBJECT_ID +
+ " not found in entry " + SUPPORT_LABEL + " in policy " + policyWithEventApplied));
+
+ assertThat(activatedSubject.getExpiry()).isNotEmpty();
+ }
+}
\ No newline at end of file