Skip to content

Commit

Permalink
[eclipse-ditto#926] add command and event strategies for ActivateSubj…
Browse files Browse the repository at this point in the history
…ect.

Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Dec 24, 2020
1 parent f595551 commit c9d1b91
Show file tree
Hide file tree
Showing 12 changed files with 492 additions and 9 deletions.
4 changes: 4 additions & 0 deletions services/policies/persistence/pom.xml
Expand Up @@ -56,6 +56,10 @@
<groupId>org.eclipse.ditto</groupId>
<artifactId>ditto-services-models-policies</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.ditto</groupId>
<artifactId>ditto-model-placeholders</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.ditto</groupId>
Expand Down
@@ -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<PolicyEntry> {

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<String> 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<String> getSupportedNames() {
return List.of(LABEL);
}

@Override
public boolean supports(final String name) {
return LABEL.equals(name);
}
}
@@ -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;
Expand Up @@ -29,13 +29,15 @@
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;
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.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;
Expand All @@ -60,6 +62,8 @@
abstract class AbstractPolicyCommandStrategy<C extends Command<C>>
extends AbstractConditionHeaderCheckingCommandStrategy<C, Policy, PolicyId, PolicyEvent> {

static SubjectType TOKEN_INTEGRATION = PoliciesModelFactory.newSubjectType("actions/activateTokenIntegration");

private final PolicyExpiryGranularity policyExpiryGranularity;

AbstractPolicyCommandStrategy(final Class<C> theMatchingClass, final PolicyConfig policyConfig) {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
@@ -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<ActivateSubject> {

ActivateSubjectStrategy(final PolicyConfig policyConfig) {
super(ActivateSubject.class, policyConfig);
}

@Override
protected Result<PolicyEvent> doApply(final Context<PolicyId> 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<PolicyEntry> 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<EntityTag> previousEntityTag(final ActivateSubject command, @Nullable final Policy previousEntity) {
// activated subjects do not support entity tag
return Optional.empty();
}

@Override
public Optional<EntityTag> nextEntityTag(final ActivateSubject command, @Nullable final Policy newEntity) {
// activated subjects do not support entity tag
return Optional.empty();
}
}
Expand Up @@ -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));
Expand Down
Expand Up @@ -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;
Expand All @@ -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());
Expand Down
@@ -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<SubjectActivated> {

@Override
protected PolicyBuilder applyEvent(final SubjectActivated sm, final PolicyBuilder policyBuilder) {
return policyBuilder.setSubjectFor(sm.getLabel(), sm.getSubject());
}
}
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Expand All @@ -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"))
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit c9d1b91

Please sign in to comment.