Skip to content

Commit

Permalink
[eclipse-ditto#926] Reject activateTokenIntegration actions on entrie…
Browse files Browse the repository at this point in the history
…s without READ permission for things.

Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Jan 13, 2021
1 parent b7ce02b commit 850f995
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 17 deletions.
Expand Up @@ -12,10 +12,16 @@
*/
package org.eclipse.ditto.services.policies.persistence.actors.strategies.commands;

import org.eclipse.ditto.model.base.common.HttpStatusCode;
import org.eclipse.ditto.model.policies.EffectedPermissions;
import org.eclipse.ditto.model.policies.PoliciesResourceType;
import org.eclipse.ditto.model.policies.PolicyEntry;
import org.eclipse.ditto.services.models.policies.Permission;
import org.eclipse.ditto.services.policies.common.config.PolicyConfig;
import org.eclipse.ditto.services.policies.persistence.actors.resolvers.SubjectIdFromActionResolver;
import org.eclipse.ditto.services.utils.akka.AkkaClassLoader;
import org.eclipse.ditto.signals.commands.policies.actions.PolicyActionCommand;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;

import akka.actor.ActorSystem;

Expand All @@ -37,4 +43,29 @@ abstract class AbstractPolicyActionCommandStrategy<C extends PolicyActionCommand
policyConfig.getSubjectIdResolver());
}

/**
* Check whether a policy entry contains a READ permission for things.
*
* @param policyEntry the policy entry to check.
* @return whether the entry contains a READ permission for things.
*/
boolean containsThingReadPermission(final PolicyEntry policyEntry) {
return policyEntry.getResources()
.stream()
.anyMatch(resource -> {
final String resourceType = resource.getResourceKey().getResourceType();
final EffectedPermissions permissions = resource.getEffectedPermissions();
return PoliciesResourceType.THING.equals(resourceType) &&
permissions.getGrantedPermissions().contains(Permission.READ) &&
!permissions.getRevokedPermissions().contains(Permission.READ);
});
}

PolicyActionFailedException getExceptionForNoEntryWithThingReadPermission() {
return PolicyActionFailedException.newBuilderForActivateTokenIntegration()
.status(HttpStatusCode.NOT_FOUND)
.description("No policy entry found with READ permission for things.")
.build();
}

}
Expand Up @@ -43,7 +43,6 @@
import org.eclipse.ditto.services.utils.persistentactors.results.ResultFactory;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegrationResponse;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;
import org.eclipse.ditto.signals.events.policies.PolicyEvent;
import org.eclipse.ditto.signals.events.policies.SubjectsModifiedPartially;

Expand Down Expand Up @@ -74,11 +73,10 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
.stream()
.map(nonNullPolicy::getEntryFor)
.flatMap(Optional::stream)
.filter(this::containsThingReadPermission)
.collect(Collectors.toList());
if (entries.isEmpty() || entries.size() != command.getLabels().size()) {
// Command is constructed incorrectly. This is a bug.
return ResultFactory.newErrorResult(
PolicyActionFailedException.newBuilderForActivateTokenIntegration().build(), command);
if (entries.isEmpty()) {
return ResultFactory.newErrorResult(getExceptionForNoEntryWithThingReadPermission(), command);
}
final PolicyBuilder policyBuilder = nonNullPolicy.toBuilder();
final Map<Label, Subject> activatedSubjects = new HashMap<>();
Expand Down
Expand Up @@ -38,7 +38,6 @@
import org.eclipse.ditto.services.utils.persistentactors.results.ResultFactory;
import org.eclipse.ditto.signals.commands.policies.actions.ActivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.ActivateTokenIntegrationResponse;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyEntryNotAccessibleException;
import org.eclipse.ditto.signals.events.policies.PolicyEvent;
import org.eclipse.ditto.signals.events.policies.SubjectCreated;
import org.eclipse.ditto.signals.events.policies.SubjectModified;
Expand Down Expand Up @@ -67,7 +66,8 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
final SubjectExpiry commandSubjectExpiry = SubjectExpiry.newInstance(command.getExpiry());
final DittoHeaders dittoHeaders = command.getDittoHeaders();

final Optional<PolicyEntry> optionalEntry = nonNullPolicy.getEntryFor(label);
final Optional<PolicyEntry> optionalEntry = nonNullPolicy.getEntryFor(label)
.filter(this::containsThingReadPermission);
if (optionalEntry.isPresent()) {
final PolicyEntry policyEntry = optionalEntry.get();
final SubjectId subjectId;
Expand Down Expand Up @@ -99,7 +99,7 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
final PolicyEvent<?> event;
if (policyEntry.getSubjects().getSubject(adjustedSubject.getId()).isPresent()) {
event = SubjectModified.of(policyId, label, adjustedSubject, nextRevision, getEventTimestamp(),
dittoHeaders);
dittoHeaders);
} else {
event = SubjectCreated.of(policyId, label, adjustedSubject, nextRevision, getEventTimestamp(),
dittoHeaders);
Expand All @@ -114,15 +114,13 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
command);
}
} else {
// Policy is configured incorrectly
return ResultFactory.newErrorResult(
PolicyEntryNotAccessibleException.newBuilder(policyId, label).dittoHeaders(dittoHeaders).build(),
command);
return ResultFactory.newErrorResult(getExceptionForNoEntryWithThingReadPermission(), command);
}
}

@Override
public Optional<EntityTag> previousEntityTag(final ActivateTokenIntegration command, @Nullable final Policy previousEntity) {
public Optional<EntityTag> previousEntityTag(final ActivateTokenIntegration command,
@Nullable final Policy previousEntity) {
// activated subjects do not support entity tag
return Optional.empty();
}
Expand Down
Expand Up @@ -24,16 +24,18 @@
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException;
import org.eclipse.ditto.model.policies.Label;
import org.eclipse.ditto.model.policies.Policy;
import org.eclipse.ditto.model.policies.PolicyId;
import org.eclipse.ditto.model.policies.ResourceKey;
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.models.policies.Permission;
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.actions.ActivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegrationResponse;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;
import org.eclipse.ditto.signals.events.policies.SubjectsModifiedPartially;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -130,7 +132,7 @@ public void rejectEmptyLabels() {
final ActivatePolicyTokenIntegration command =
ActivatePolicyTokenIntegration.of(context.getState(), subjectId, expiry, List.of(), dittoHeaders);
assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
PolicyActionFailedException.newBuilderForActivateTokenIntegration().build());
underTest.getExceptionForNoEntryWithThingReadPermission());
}

@Test
Expand All @@ -141,8 +143,26 @@ public void rejectNonexistentLabel() {
final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me");
final DittoHeaders dittoHeaders = DittoHeaders.empty();
final ActivatePolicyTokenIntegration command =
ActivatePolicyTokenIntegration.of(context.getState(), subjectId, expiry, List.of(nonexistentLabel), dittoHeaders);
ActivatePolicyTokenIntegration.of(context.getState(), subjectId, expiry, List.of(nonexistentLabel),
dittoHeaders);
assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
PolicyActionFailedException.newBuilderForActivateTokenIntegration().build());
underTest.getExceptionForNoEntryWithThingReadPermission());
}

@Test
public void rejectEntryWithoutThingReadPermission() {
final CommandStrategy.Context<PolicyId> context = getDefaultContext();
final Label label = Label.of("empty-entry");
final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
final SubjectId subjectId = SubjectId.newInstance("integration:this-is-me");
final DittoHeaders dittoHeaders = DittoHeaders.empty();
final ActivatePolicyTokenIntegration command =
ActivatePolicyTokenIntegration.of(context.getState(), subjectId, expiry, List.of(label), dittoHeaders);
final Policy policy = TestConstants.Policy.POLICY.toBuilder()
.forLabel(label)
.setSubject(TestConstants.Policy.SUPPORT_SUBJECT)
.setGrantedPermissions(ResourceKey.newInstance("policy:/"), Permission.READ)
.build();
assertErrorResult(underTest, policy, command, underTest.getExceptionForNoEntryWithThingReadPermission());
}
}
Expand Up @@ -21,10 +21,14 @@

import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.placeholders.UnresolvedPlaceholderException;
import org.eclipse.ditto.model.policies.Label;
import org.eclipse.ditto.model.policies.Policy;
import org.eclipse.ditto.model.policies.PolicyId;
import org.eclipse.ditto.model.policies.ResourceKey;
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.models.policies.Permission;
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;
Expand Down Expand Up @@ -107,4 +111,21 @@ public void activateTokenIntegrationWithUnsupportedPlaceholder() {
assertErrorResult(underTest, TestConstants.Policy.POLICY, command,
UnresolvedPlaceholderException.newBuilder("{{request:subjectId}}").build());
}

@Test
public void rejectEntryWithoutThingReadPermission() {
final CommandStrategy.Context<PolicyId> context = getDefaultContext();
final Label label = Label.of("empty-entry");
final Instant expiry = Instant.now().plus(Duration.ofDays(1L));
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);
final Policy policy = TestConstants.Policy.POLICY.toBuilder()
.forLabel(label)
.setSubject(TestConstants.Policy.SUPPORT_SUBJECT)
.setGrantedPermissions(ResourceKey.newInstance("policy:/"), Permission.READ)
.build();
assertErrorResult(underTest, policy, command, underTest.getExceptionForNoEntryWithThingReadPermission());
}
}
10 changes: 10 additions & 0 deletions services/policies/persistence/src/test/resources/test.conf
Expand Up @@ -49,6 +49,16 @@ akka {
}
}

akka {
management.http.port = 0
remote {
artery {
canonical.hostname = "127.0.0.1"
canonical.port = 0
}
}
}

akka.contrib.persistence.mongodb.mongo {
driver = "akka.contrib.persistence.mongodb.ScalaDriverPersistenceExtension"
}
Expand Down

0 comments on commit 850f995

Please sign in to comment.