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