Skip to content

Commit

Permalink
[eclipse-ditto#926] Add generic TopLevelActionCommand for policies.
Browse files Browse the repository at this point in the history
Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Jan 14, 2021
1 parent 90614b7 commit 7004aa6
Show file tree
Hide file tree
Showing 50 changed files with 1,525 additions and 170 deletions.
Expand Up @@ -50,9 +50,8 @@
import org.eclipse.ditto.services.utils.cluster.DistPubSubAccess;
import org.eclipse.ditto.signals.commands.base.CommandToExceptionRegistry;
import org.eclipse.ditto.signals.commands.policies.PolicyCommand;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.PolicyActionCommand;
import org.eclipse.ditto.signals.commands.policies.actions.TopLevelActionCommand;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyCommandToAccessExceptionRegistry;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyCommandToActionsExceptionRegistry;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyCommandToModifyExceptionRegistry;
Expand Down Expand Up @@ -131,11 +130,13 @@ public static <T extends PolicyCommand<?>> Optional<T> authorizePolicyCommand(fi
return authorizedCommand;
}

@SuppressWarnings("unchecked")
private static <T extends PolicyCommand<?>> Optional<T> authorizeActionCommand(final PolicyEnforcer enforcer,
final T command, final ResourceKey resourceKey, final AuthorizationContext authorizationContext) {

if (isTopLevelActionCommand(command)) {
return authorizeTopLevelAction(enforcer, command, authorizationContext);
if (command instanceof TopLevelActionCommand) {
final TopLevelActionCommand topLevelActionCommand = (TopLevelActionCommand) command;
return (Optional<T>) authorizeTopLevelAction(enforcer, topLevelActionCommand, authorizationContext);
} else {
return authorizeEntryLevelAction(enforcer.getEnforcer(), command, resourceKey, authorizationContext);
}
Expand All @@ -152,9 +153,8 @@ private static <T extends PolicyCommand<?>> Optional<T> authorizeEntryLevelActio
: Optional.empty();
}

@SuppressWarnings("unchecked")
private static <T extends PolicyCommand<?>> Optional<T> authorizeTopLevelAction(final PolicyEnforcer policyEnforcer,
final T command, final AuthorizationContext authorizationContext) {
private static Optional<TopLevelActionCommand> authorizeTopLevelAction(final PolicyEnforcer policyEnforcer,
final TopLevelActionCommand command, final AuthorizationContext authorizationContext) {
final Enforcer enforcer = policyEnforcer.getEnforcer();
final List<Label> authorizedLabels = policyEnforcer.getPolicy()
.map(policy -> policy.getEntriesSet().stream()
Expand All @@ -165,21 +165,10 @@ private static <T extends PolicyCommand<?>> Optional<T> authorizeTopLevelAction(
.orElse(List.of());
if (authorizedLabels.isEmpty()) {
return Optional.empty();
} else if (command instanceof ActivatePolicyTokenIntegration) {
final ActivatePolicyTokenIntegration c = (ActivatePolicyTokenIntegration) command;
final T adjustedCommand =
(T) ActivatePolicyTokenIntegration.of(c.getEntityId(), c.getSubjectId(), c.getExpiry(),
authorizedLabels,
c.getDittoHeaders());
return Optional.of(adjustedCommand);
} else if (command instanceof DeactivatePolicyTokenIntegration) {
final DeactivatePolicyTokenIntegration c = (DeactivatePolicyTokenIntegration) command;
final T adjustedCommand =
(T) DeactivatePolicyTokenIntegration.of(c.getEntityId(), c.getSubjectId(), authorizedLabels,
c.getDittoHeaders());
return Optional.of(adjustedCommand);
} else {
return Optional.empty();
final TopLevelActionCommand adjustedCommand =
TopLevelActionCommand.of(command.getPolicyActionCommand(), authorizedLabels);
return Optional.of(adjustedCommand);
}
}

Expand Down
Expand Up @@ -62,6 +62,7 @@
import org.eclipse.ditto.signals.commands.policies.actions.ActivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.TopLevelActionCommand;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyNotAccessibleException;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyNotModifiableException;
Expand Down Expand Up @@ -625,39 +626,45 @@ public void activatePolicyTokenIntegration() {
new TestKit(system) {{
final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject");
final Instant expiry = Instant.now();
final ActivatePolicyTokenIntegration activatePolicyTokenIntegration =
ActivatePolicyTokenIntegration.of(POLICY_ID, subjectId, expiry, List.of(), DITTO_HEADERS);
final TopLevelActionCommand command = TopLevelActionCommand.of(
ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, expiry, DITTO_HEADERS),
List.of()
);

enforcer.tell(activatePolicyTokenIntegration, getRef());
enforcer.tell(command, getRef());

policiesShardRegionProbe.expectMsgClass(SudoRetrievePolicy.class);
policiesShardRegionProbe.reply(createPolicyResponseForActions());

final ActivatePolicyTokenIntegration
forwarded = policiesShardRegionProbe.expectMsgClass(ActivatePolicyTokenIntegration.class);
assertThat(forwarded).isEqualTo(
ActivatePolicyTokenIntegration.of(POLICY_ID, subjectId, expiry, List.of(Label.of("allowed")),
DITTO_HEADERS));
final TopLevelActionCommand
forwarded = policiesShardRegionProbe.expectMsgClass(TopLevelActionCommand.class);
assertThat(forwarded).isEqualTo(TopLevelActionCommand.of(
ActivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, expiry, DITTO_HEADERS),
List.of(Label.of("allowed"))
));
}};
}

@Test
public void deactivatePolicyTokenIntegration() {
new TestKit(system) {{
final SubjectId subjectId = SubjectId.newInstance("issuer:{{policy-entry:label}}:subject");
final DeactivatePolicyTokenIntegration deactivatePolicyTokenIntegration =
DeactivatePolicyTokenIntegration.of(POLICY_ID, subjectId, List.of(), DITTO_HEADERS);
final TopLevelActionCommand command = TopLevelActionCommand.of(
DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, DITTO_HEADERS),
List.of()
);

enforcer.tell(deactivatePolicyTokenIntegration, getRef());
enforcer.tell(command, getRef());

policiesShardRegionProbe.expectMsgClass(SudoRetrievePolicy.class);
policiesShardRegionProbe.reply(createPolicyResponseForActions());

final DeactivatePolicyTokenIntegration
forwarded = policiesShardRegionProbe.expectMsgClass(DeactivatePolicyTokenIntegration.class);
assertThat(forwarded).isEqualTo(
DeactivatePolicyTokenIntegration.of(POLICY_ID, subjectId, List.of(Label.of("allowed")),
DITTO_HEADERS));
final TopLevelActionCommand
forwarded = policiesShardRegionProbe.expectMsgClass(TopLevelActionCommand.class);
assertThat(forwarded).isEqualTo(TopLevelActionCommand.of(
DeactivateTokenIntegration.of(POLICY_ID, Label.of("-"), subjectId, DITTO_HEADERS),
List.of(Label.of("allowed"))
));
}};
}

Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.model.base.headers.DittoHeaders;
import org.eclipse.ditto.model.jwt.JsonWebToken;
import org.eclipse.ditto.model.policies.Label;
import org.eclipse.ditto.model.policies.PoliciesModelFactory;
import org.eclipse.ditto.model.policies.Policy;
import org.eclipse.ditto.model.policies.PolicyId;
Expand All @@ -34,10 +35,9 @@
import org.eclipse.ditto.services.gateway.security.authentication.jwt.JwtAuthenticationResult;
import org.eclipse.ditto.services.gateway.util.config.endpoints.CommandConfig;
import org.eclipse.ditto.services.gateway.util.config.endpoints.HttpConfig;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.ActivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.TopLevelActionCommand;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyIdNotExplicitlySettableException;
import org.eclipse.ditto.signals.commands.policies.modify.DeletePolicy;
Expand All @@ -62,6 +62,8 @@ public final class PoliciesRoute extends AbstractRoute {
private static final String PATH_POLICIES = "policies";
private static final String PATH_ENTRIES = "entries";

private static final Label DUMMY_LABEL = Label.of("-");

private final PolicyEntriesRoute policyEntriesRoute;
private final TokenIntegrationSubjectIdFactory tokenIntegrationSubjectIdFactory;

Expand Down Expand Up @@ -187,19 +189,23 @@ private Route policyActions(final RequestContext ctx, final DittoHeaders dittoHe
));
}

private ActivatePolicyTokenIntegration activatePolicyTokenIntegration(final DittoHeaders dittoHeaders,
private TopLevelActionCommand activatePolicyTokenIntegration(final DittoHeaders dittoHeaders,
final PolicyId policyId, final JsonWebToken jwt) {

final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt);
final Instant expiry = jwt.getExpirationTime();
return ActivatePolicyTokenIntegration.of(policyId, subjectId, expiry, List.of(), dittoHeaders);
final ActivateTokenIntegration activateTokenIntegration =
ActivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectId, expiry, dittoHeaders);
return TopLevelActionCommand.of(activateTokenIntegration, List.of());
}

private DeactivatePolicyTokenIntegration deactivatePolicyTokenIntegration(final DittoHeaders dittoHeaders,
private TopLevelActionCommand deactivatePolicyTokenIntegration(final DittoHeaders dittoHeaders,
final PolicyId policyId, final JsonWebToken jwt) {

final SubjectId subjectId = tokenIntegrationSubjectIdFactory.getSubjectId(dittoHeaders, jwt);
return DeactivatePolicyTokenIntegration.of(policyId, subjectId, List.of(), dittoHeaders);
final DeactivateTokenIntegration deactivateTokenIntegration =
DeactivateTokenIntegration.of(policyId, DUMMY_LABEL, subjectId, dittoHeaders);
return TopLevelActionCommand.of(deactivateTokenIntegration, List.of());
}

static Route extractJwt(final DittoHeaders dittoHeaders,
Expand Down
Expand Up @@ -28,10 +28,9 @@
import org.eclipse.ditto.services.gateway.security.authentication.DefaultAuthenticationResult;
import org.eclipse.ditto.services.gateway.security.authentication.jwt.JwtAuthenticationResult;
import org.eclipse.ditto.services.utils.protocol.ProtocolAdapterProvider;
import org.eclipse.ditto.signals.commands.policies.actions.ActivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.ActivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivatePolicyTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.DeactivateTokenIntegration;
import org.eclipse.ditto.signals.commands.policies.actions.TopLevelActionCommand;
import org.eclipse.ditto.signals.commands.policies.exceptions.PolicyActionFailedException;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -150,22 +149,26 @@ public void nonexistentPolicyAction() {
public void activateTokenIntegration() {
getRoute(getTokenAuthResult()).run(HttpRequest.POST("/policies/ns%3An/actions/activateTokenIntegration/"))
.assertStatusCode(StatusCodes.OK)
.assertEntity(ActivatePolicyTokenIntegration.of(PolicyId.of("ns:n"),
SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"),
DummyJwt.EXPIRY,
List.of(),
DittoHeaders.empty()
.assertEntity(TopLevelActionCommand.of(
ActivateTokenIntegration.of(PolicyId.of("ns:n"),
Label.of("-"),
SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"),
DummyJwt.EXPIRY,
DittoHeaders.empty()),
List.of()
).toJsonString());
}

@Test
public void deactivateTokenIntegration() {
getRoute(getTokenAuthResult()).run(HttpRequest.POST("/policies/ns%3An/actions/deactivateTokenIntegration"))
.assertStatusCode(StatusCodes.OK)
.assertEntity(DeactivatePolicyTokenIntegration.of(PolicyId.of("ns:n"),
SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"),
List.of(),
DittoHeaders.empty()
.assertEntity(TopLevelActionCommand.of(
DeactivateTokenIntegration.of(PolicyId.of("ns:n"),
Label.of("-"),
SubjectId.newInstance("dummy-issuer:{{policy-entry:label}}:dummy-subject"),
DittoHeaders.empty()),
List.of()
).toJsonString());
}

Expand Down
Expand Up @@ -22,6 +22,7 @@
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 org.eclipse.ditto.signals.events.policies.PolicyActionEvent;

import akka.actor.ActorSystem;

Expand All @@ -31,7 +32,7 @@
* @param <C> the type of the handled command
*/
abstract class AbstractPolicyActionCommandStrategy<C extends PolicyActionCommand<C>>
extends AbstractPolicyCommandStrategy<C> {
extends AbstractPolicyCommandStrategy<C, PolicyActionEvent<?>> {

protected final SubjectIdFromActionResolver subjectIdFromActionResolver;

Expand Down Expand Up @@ -61,7 +62,14 @@ boolean containsThingReadPermission(final PolicyEntry policyEntry) {
});
}

PolicyActionFailedException getExceptionForNoEntryWithThingReadPermission() {
/**
* Get the exception for when a policy action command is not applicable to the designated policy entry.
* For now there is 1 not-applicable exception defined here.
* New policy action command strategies may override this method to provide their own exceptions.
*
* @return the exception for when a policy action command is not applicable.
*/
PolicyActionFailedException getNotApplicableException() {
return PolicyActionFailedException.newBuilderForActivateTokenIntegration()
.status(HttpStatusCode.NOT_FOUND)
.description("No policy entry found with READ permission for things.")
Expand Down
Expand Up @@ -57,8 +57,8 @@
* @param <C> the type of the handled command - of type {@code Command} as also
* {@link org.eclipse.ditto.services.models.policies.commands.sudo.SudoCommand} are handled which are no PolicyCommands.
*/
abstract class AbstractPolicyCommandStrategy<C extends Command<C>>
extends AbstractConditionHeaderCheckingCommandStrategy<C, Policy, PolicyId, PolicyEvent<?>> {
abstract class AbstractPolicyCommandStrategy<C extends Command<C>, E extends PolicyEvent<?>>
extends AbstractConditionHeaderCheckingCommandStrategy<C, Policy, PolicyId, E> {

private final PolicyExpiryGranularity policyExpiryGranularity;

Expand Down Expand Up @@ -235,7 +235,8 @@ protected SubjectExpiry roundPolicySubjectExpiry(final SubjectExpiry expiry) {
* @param command the command which caused the change of the policy entries.
* @return an Optional with ErrorResponse if a subject was invalid, an empty Optional if everything was valid.
*/
protected static Optional<Result<PolicyEvent<?>>> checkForAlreadyExpiredSubject(final Iterable<PolicyEntry> entries,
protected static <T extends PolicyEvent<?>> Optional<Result<T>> checkForAlreadyExpiredSubject(
final Iterable<PolicyEntry> entries,
final DittoHeaders dittoHeaders, final Command<?> command) {

return StreamSupport.stream(entries.spliterator(), false)
Expand Down
Expand Up @@ -20,14 +20,15 @@
import org.eclipse.ditto.model.policies.Policy;
import org.eclipse.ditto.services.policies.common.config.PolicyConfig;
import org.eclipse.ditto.signals.commands.base.Command;
import org.eclipse.ditto.signals.events.policies.PolicyEvent;

/**
* Abstract base class for
* @param <C> the type of the handled command - of type {@code Command} as also
* {@link org.eclipse.ditto.services.models.policies.commands.sudo.SudoCommand} are handled which are no
* PolicyQueryCommands.
*/
abstract class AbstractPolicyQueryCommandStrategy<C extends Command<C>> extends AbstractPolicyCommandStrategy<C> {
abstract class AbstractPolicyQueryCommandStrategy<C extends Command<C>> extends AbstractPolicyCommandStrategy<C, PolicyEvent<?>> {

AbstractPolicyQueryCommandStrategy(final Class<C> theMatchingClass, final PolicyConfig policyConfig) {
super(theMatchingClass, policyConfig);
Expand Down
Expand Up @@ -43,7 +43,7 @@
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.events.policies.PolicyEvent;
import org.eclipse.ditto.signals.events.policies.PolicyActionEvent;
import org.eclipse.ditto.signals.events.policies.SubjectsModifiedPartially;

import akka.actor.ActorSystem;
Expand All @@ -59,7 +59,7 @@ final class ActivatePolicyTokenIntegrationStrategy
}

@Override
protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
protected Result<PolicyActionEvent<?>> doApply(final Context<PolicyId> context,
@Nullable final Policy policy,
final long nextRevision,
final ActivatePolicyTokenIntegration command,
Expand All @@ -76,7 +76,7 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
.filter(this::containsThingReadPermission)
.collect(Collectors.toList());
if (entries.isEmpty()) {
return ResultFactory.newErrorResult(getExceptionForNoEntryWithThingReadPermission(), command);
return ResultFactory.newErrorResult(getNotApplicableException(), command);
}
final PolicyBuilder policyBuilder = nonNullPolicy.toBuilder();
final Map<Label, Subject> activatedSubjects = new HashMap<>();
Expand All @@ -99,7 +99,7 @@ protected Result<PolicyEvent<?>> doApply(final Context<PolicyId> context,
final Policy newPolicy = policyBuilder.build();
final PoliciesValidator validator = PoliciesValidator.newInstance(newPolicy);
if (validator.isValid()) {
final PolicyEvent<?> event =
final SubjectsModifiedPartially event =
SubjectsModifiedPartially.of(policyId, activatedSubjects, nextRevision, getEventTimestamp(),
dittoHeaders);
final ActivatePolicyTokenIntegrationResponse rawResponse =
Expand Down

0 comments on commit 7004aa6

Please sign in to comment.