From a70b3aa1e970b6cdb08faa80b2f5dec7a7be70cd Mon Sep 17 00:00:00 2001 From: Dominik Guggemos Date: Mon, 26 Sep 2022 09:31:15 +0200 Subject: [PATCH] implement pre-enforcer to enforce read access on imported policies Co-authored-by: Kalin Kostashki Signed-off-by: Dominik Guggemos --- .../pre/PolicyImportsPreEnforcer.java | 135 ++++++++++ .../pre/PolicyImportsPreEnforcerTest.java | 235 ++++++++++++++++++ .../commands/actions/PolicyActionCommand.java | 2 +- ...olicyCommandToAccessExceptionRegistry.java | 11 + ...olicyCommandToModifyExceptionRegistry.java | 11 + .../PolicyImportNotAccessibleException.java | 9 +- .../PolicyImportNotModifiableException.java | 137 ++++++++++ .../PolicyImportsNotModifiableException.java | 136 ++++++++++ .../service/src/main/resources/policies.conf | 3 +- 9 files changed, 673 insertions(+), 6 deletions(-) create mode 100644 policies/enforcement/src/main/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcer.java create mode 100644 policies/enforcement/src/test/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcerTest.java create mode 100755 policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotModifiableException.java create mode 100755 policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportsNotModifiableException.java diff --git a/policies/enforcement/src/main/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcer.java b/policies/enforcement/src/main/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcer.java new file mode 100644 index 0000000000..4e634c2b58 --- /dev/null +++ b/policies/enforcement/src/main/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcer.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2022 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.policies.enforcement.pre; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.ditto.base.model.auth.AuthorizationContext; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.signals.Signal; +import org.eclipse.ditto.policies.api.Permission; +import org.eclipse.ditto.policies.enforcement.PolicyEnforcer; +import org.eclipse.ditto.policies.enforcement.PolicyEnforcerProvider; +import org.eclipse.ditto.policies.model.PoliciesModelFactory; +import org.eclipse.ditto.policies.model.PolicyId; +import org.eclipse.ditto.policies.model.PolicyImport; +import org.eclipse.ditto.policies.model.ResourceKey; +import org.eclipse.ditto.policies.model.enforcers.Enforcer; +import org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyImportNotAccessibleException; +import org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyNotAccessibleException; +import org.eclipse.ditto.policies.model.signals.commands.modify.CreatePolicy; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicy; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImport; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImports; +import org.eclipse.ditto.policies.model.signals.commands.modify.PolicyModifyCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.typesafe.config.Config; + +import akka.actor.ActorSystem; + +/** + * Pre-Enforcer for authorizing modifications to policy imports. + */ +public class PolicyImportsPreEnforcer implements PreEnforcer { + + private static final String POLICY_RESOURCE = "policy"; + public static final String ENTRIES_PREFIX = "/entries/"; + private final PolicyEnforcerProvider policyEnforcerProvider; + + // TOOO DG logging + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyImportsPreEnforcer.class); + + /** + * Constructs a new instance of PolicyImportsPreEnforcer extension. + * + * @param actorSystem the actor system in which to load the extension. + * @param config the configuration for this extension. + */ + @SuppressWarnings("unused") + public PolicyImportsPreEnforcer(final ActorSystem actorSystem, final Config config) { + // TODO DG make PolicyEnforcerProvider an extension + policyEnforcerProvider = PolicyEnforcerProvider.getInstance(actorSystem); + } + + PolicyImportsPreEnforcer(final PolicyEnforcerProvider policyEnforcerProvider) { + this.policyEnforcerProvider = policyEnforcerProvider; + } + + @Override + public CompletionStage> apply(final Signal signal) { + if (signal instanceof ModifyPolicy modifyPolicy) { + return doApply(modifyPolicy.getPolicy().getPolicyImports().stream(), modifyPolicy); + } else if (signal instanceof CreatePolicy createPolicy) { + return doApply(createPolicy.getPolicy().getPolicyImports().stream(), createPolicy); + } else if (signal instanceof ModifyPolicyImports modifyPolicyImports) { + return doApply(modifyPolicyImports.getPolicyImports().stream(), modifyPolicyImports); + } else if (signal instanceof ModifyPolicyImport modifyPolicyImport) { + return doApply(Stream.of(modifyPolicyImport.getPolicyImport()), modifyPolicyImport); + } else { + return CompletableFuture.completedStage(signal); + } + } + + private CompletionStage> doApply(final Stream policyImportStream, + final PolicyModifyCommand command) { + final DittoHeaders dittoHeaders = command.getDittoHeaders(); + return policyImportStream.map( + policyImport -> getPolicyEnforcer(policyImport.getImportedPolicyId(), dittoHeaders).thenApply( + importedPolicyEnforcer -> authorize(command, importedPolicyEnforcer.getEnforcer(), + policyImport))) + .reduce(CompletableFuture.completedStage(true), (s1, s2) -> s1.thenCombine(s2, (b1, b2) -> b1 && b2)) + .thenApply(ignored -> command); + } + + private CompletionStage getPolicyEnforcer(final PolicyId policyId, + final DittoHeaders dittoHeaders) { + return policyEnforcerProvider.getPolicyEnforcer(policyId) + .thenApply(policyEnforcerOpt -> policyEnforcerOpt.orElseThrow( + () -> PolicyNotAccessibleException.newBuilder(policyId).dittoHeaders(dittoHeaders).build())); + } + + private static Set getResourceKeys(final PolicyImport policyImport) { + return policyImport.getEffectedImports().orElse(PoliciesModelFactory.emptyEffectedImportedEntries()) + .getImportedLabels() + .stream() + .map(l -> ENTRIES_PREFIX + l) + .map(path -> ResourceKey.newInstance(POLICY_RESOURCE, path)) + .collect(Collectors.toSet()); + } + + private boolean authorize(final PolicyModifyCommand command, final Enforcer enforcer, + final PolicyImport policyImport) { + final Set resourceKeys = getResourceKeys(policyImport); + final AuthorizationContext authorizationContext = command.getDittoHeaders().getAuthorizationContext(); + final boolean hasAccess = + enforcer.hasUnrestrictedPermissions(resourceKeys, authorizationContext, Permission.READ); + if (!hasAccess) { + throw errorForPolicyModifyCommand(command, policyImport); + } else { + return true; + } + } + + private static DittoRuntimeException errorForPolicyModifyCommand(final PolicyModifyCommand policyModifyCommand, + final PolicyImport policyImport) { + return PolicyImportNotAccessibleException.newBuilder(policyModifyCommand.getEntityId(), + policyImport.getImportedPolicyId()).build(); + } +} diff --git a/policies/enforcement/src/test/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcerTest.java b/policies/enforcement/src/test/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcerTest.java new file mode 100644 index 0000000000..6765769b2a --- /dev/null +++ b/policies/enforcement/src/test/java/org/eclipse/ditto/policies/enforcement/pre/PolicyImportsPreEnforcerTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2022 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.policies.enforcement.pre; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORTED; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORTED_POLICY_ID; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORTING; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORTING_POLICY_ID; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORT_NOT_FOUND; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.IMPORT_NOT_FOUND_POLICY_ID; +import static org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcerTest.Policies.KNOWN_IDS; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.stream.Stream; + +import org.eclipse.ditto.base.model.auth.AuthorizationContext; +import org.eclipse.ditto.base.model.auth.AuthorizationModelFactory; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.signals.Signal; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.policies.enforcement.PolicyEnforcer; +import org.eclipse.ditto.policies.enforcement.PolicyEnforcerProvider; +import org.eclipse.ditto.policies.model.PoliciesModelFactory; +import org.eclipse.ditto.policies.model.Policy; +import org.eclipse.ditto.policies.model.PolicyId; +import org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyImportNotAccessibleException; +import org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyNotAccessibleException; +import org.eclipse.ditto.policies.model.signals.commands.modify.CreatePolicy; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicy; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImport; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImports; +import org.eclipse.ditto.policies.model.signals.commands.modify.PolicyModifyCommand; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.Mockito; + +/** + * Tests {@link org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcer}. + */ +class PolicyImportsPreEnforcerTest { + + private static final AuthorizationContext AUTH_CONTEXT_SUBJECT_ALLOWED = AuthorizationModelFactory.newAuthContext( + JsonObject.of(""" + { + "type" : "unspecified", + "subjects" : ["ditto:subject1"] + } + """)); + private static final AuthorizationContext AUTH_CONTEXT_SUBJECT_FORBIDDEN = AuthorizationModelFactory.newAuthContext( + JsonObject.of(""" + { + "type" : "unspecified", + "subjects" : ["ditto:subject2"] + } + """)); + private PolicyImportsPreEnforcer policyImportsPreEnforcer; + + @BeforeEach + void setUp() { + PolicyEnforcerProvider policyEnforcerProvider = Mockito.mock(PolicyEnforcerProvider.class); + + when(policyEnforcerProvider.getPolicyEnforcer(IMPORTED_POLICY_ID)) + .thenReturn(CompletableFuture.completedStage(Optional.of(PolicyEnforcer.of(IMPORTED)))); + when(policyEnforcerProvider.getPolicyEnforcer(IMPORTING_POLICY_ID)) + .thenReturn(CompletableFuture.completedStage(Optional.of(PolicyEnforcer.of(IMPORTING)))); + when(policyEnforcerProvider.getPolicyEnforcer(IMPORT_NOT_FOUND_POLICY_ID)) + .thenReturn(CompletableFuture.completedStage(Optional.of(PolicyEnforcer.of(IMPORT_NOT_FOUND)))); + when(policyEnforcerProvider.getPolicyEnforcer(argThat(id -> !KNOWN_IDS.contains(id)))) + .thenReturn(CompletableFuture.completedStage(Optional.empty())); + + policyImportsPreEnforcer = new PolicyImportsPreEnforcer(policyEnforcerProvider); + } + + @ParameterizedTest + @ArgumentsSource(PolicyModifyCommandsProvider.class) + void testSubjectAllowedToReadImportedPolicy(final PolicyModifyCommandsProvider.Outcome outcome, + final PolicyModifyCommand command) { + final CompletableFuture> applyFuture = policyImportsPreEnforcer.apply(command).toCompletableFuture(); + switch (outcome) { + case SUCCESS -> { + final Signal signal = applyFuture.join(); + assertThat(signal).isSameAs(command); + } + case ERROR -> { + assertThatExceptionOfType(CompletionException.class) + .isThrownBy(applyFuture::join) + .withCauseInstanceOf(PolicyImportNotAccessibleException.class); + } + } + } + + @Test + void testEnforcerOfImportedPolicyNotFound() { + final DittoHeaders dittoHeaders = + DittoHeaders.newBuilder().authorizationContext(AUTH_CONTEXT_SUBJECT_FORBIDDEN).build(); + + final ModifyPolicy modifyPolicy = ModifyPolicy.of(IMPORT_NOT_FOUND_POLICY_ID, IMPORT_NOT_FOUND, dittoHeaders); + + final CompletableFuture> signalFuture = + policyImportsPreEnforcer.apply(modifyPolicy).toCompletableFuture(); + + assertThatExceptionOfType(CompletionException.class) + .isThrownBy(signalFuture::join) + .withCauseInstanceOf(PolicyNotAccessibleException.class); + } + + private static class PolicyModifyCommandsProvider implements ArgumentsProvider { + + private PolicyModifyCommandsProvider() { + } + + @Override + public Stream provideArguments(ExtensionContext context) { + + return Arrays.stream(Outcome.values()) + .flatMap(outcome -> Stream.of( + Arguments.of(outcome, CreatePolicy.of(IMPORTING, getDittoHeaders(outcome))), + Arguments.of(outcome, + ModifyPolicy.of(IMPORTING_POLICY_ID, IMPORTING, getDittoHeaders(outcome))), + Arguments.of(outcome, + ModifyPolicyImports.of(IMPORTING_POLICY_ID, IMPORTING.getPolicyImports(), + getDittoHeaders(outcome))), + Arguments.of(outcome, ModifyPolicyImport.of(IMPORTING_POLICY_ID, + IMPORTING.getPolicyImports().getPolicyImport(IMPORTED_POLICY_ID).orElseThrow(), + getDittoHeaders(outcome))) + )); + } + + private static DittoHeaders getDittoHeaders(final Outcome outcome) { + return switch (outcome) { + case SUCCESS -> DittoHeaders.newBuilder().authorizationContext(AUTH_CONTEXT_SUBJECT_ALLOWED).build(); + case ERROR -> DittoHeaders.newBuilder().authorizationContext(AUTH_CONTEXT_SUBJECT_FORBIDDEN).build(); + }; + } + + enum Outcome { + SUCCESS, + ERROR + } + } + + static class Policies { + static final Policy IMPORTING = PoliciesModelFactory.newPolicy(""" + { + "policyId": "test:importing", + "entries" : { + "DEFAULT" : { + "subjects": { + "ditto:subject1" : { "type": "test" } + }, + "resources": { + "thing:/attributes": { "grant": [ "READ", "WRITE" ], "revoke": [] } + } + } + }, + "imports": { + "test:imported": {"entries":["IMPORT"]} + } + } + """); + static final Policy IMPORTED = PoliciesModelFactory.newPolicy(""" + { + "policyId": "test:imported", + "entries" : { + "DEFAULT" : { + "subjects": { + "ditto:subject2" : { "type": "test" } + }, + "resources": { + "thing:/": { "grant": [ "READ", "WRITE" ], "revoke": [] } + }, + "importable":"never" + }, + "IMPORT" : { + "subjects": { + "ditto:subject1" : { "type": "test" } + }, + "resources": { + "policy:/entries/IMPORT": { "grant": [ "READ" ], "revoke": [] } + }, + "importable":"explicit" + } + } + } + """); + static final Policy IMPORT_NOT_FOUND = PoliciesModelFactory.newPolicy(""" + { + "policyId": "test:import.not.found", + "entries" : { + "DEFAULT" : { + "subjects": { + "ditto:subject1" : { "type": "test" } + }, + "resources": { + "policy:/": { "grant": [ "READ", "WRITE" ], "revoke": [] } + } + } + }, + "imports": { + "test:notfound": {"entries":["IMPORT"]} + } + } + """); + static final PolicyId IMPORTING_POLICY_ID = IMPORTING.getEntityId().orElseThrow(); + static final PolicyId IMPORTED_POLICY_ID = IMPORTED.getEntityId().orElseThrow(); + static final PolicyId IMPORT_NOT_FOUND_POLICY_ID = IMPORT_NOT_FOUND.getEntityId().orElseThrow(); + + static final Collection KNOWN_IDS = + List.of(IMPORTED_POLICY_ID, IMPORTING_POLICY_ID, IMPORT_NOT_FOUND_POLICY_ID); + } +} \ No newline at end of file diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/actions/PolicyActionCommand.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/actions/PolicyActionCommand.java index f316df7548..ca89fc7252 100755 --- a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/actions/PolicyActionCommand.java +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/actions/PolicyActionCommand.java @@ -33,7 +33,7 @@ public interface PolicyActionCommand> extends PolicyCommand, WithOptionalEntity { /** - * Path of Policy actions as part of the the {@link #getResourcePath()}. + * Path of Policy actions as part of the {@link #getResourcePath()}. */ JsonPointer RESOURCE_PATH_ACTIONS = JsonPointer.of("actions"); diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToAccessExceptionRegistry.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToAccessExceptionRegistry.java index 1e065af64f..34b70ebfd8 100755 --- a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToAccessExceptionRegistry.java +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToAccessExceptionRegistry.java @@ -22,6 +22,8 @@ import org.eclipse.ditto.policies.model.signals.commands.query.RetrievePolicy; import org.eclipse.ditto.policies.model.signals.commands.query.RetrievePolicyEntries; import org.eclipse.ditto.policies.model.signals.commands.query.RetrievePolicyEntry; +import org.eclipse.ditto.policies.model.signals.commands.query.RetrievePolicyImport; +import org.eclipse.ditto.policies.model.signals.commands.query.RetrievePolicyImports; import org.eclipse.ditto.policies.model.signals.commands.query.RetrieveResource; import org.eclipse.ditto.policies.model.signals.commands.query.RetrieveResources; import org.eclipse.ditto.policies.model.signals.commands.query.RetrieveSubject; @@ -87,6 +89,15 @@ private static PolicyCommandToAccessExceptionRegistry createInstance() { ((RetrieveSubjects) command).getLabel()) .dittoHeaders(command.getDittoHeaders()) .build()); + mappingStrategies.put(RetrievePolicyImports.TYPE, + command -> PolicyImportsNotAccessibleException.newBuilder(command.getEntityId()) + .dittoHeaders(command.getDittoHeaders()) + .build()); + mappingStrategies.put(RetrievePolicyImport.TYPE, + command -> PolicyImportNotAccessibleException.newBuilder(command.getEntityId(), + ((RetrievePolicyImport) command).getImportedPolicyId()) + .dittoHeaders(command.getDittoHeaders()) + .build()); return new PolicyCommandToAccessExceptionRegistry(mappingStrategies); } diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToModifyExceptionRegistry.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToModifyExceptionRegistry.java index 2c6bd65f22..e71d21532f 100755 --- a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToModifyExceptionRegistry.java +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyCommandToModifyExceptionRegistry.java @@ -27,6 +27,8 @@ import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicy; import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyEntries; import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyEntry; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImport; +import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyPolicyImports; import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyResource; import org.eclipse.ditto.policies.model.signals.commands.modify.ModifyResources; import org.eclipse.ditto.policies.model.signals.commands.modify.ModifySubject; @@ -114,6 +116,15 @@ private static PolicyCommandToModifyExceptionRegistry createInstance() { ((ModifySubjects) command).getLabel()) .dittoHeaders(command.getDittoHeaders()) .build()); + mappingStrategies.put(ModifyPolicyImports.TYPE, + command -> PolicyImportsNotModifiableException.newBuilder(command.getEntityId()) + .dittoHeaders(command.getDittoHeaders()) + .build()); + mappingStrategies.put(ModifyPolicyImport.TYPE, + command -> PolicyImportNotModifiableException.newBuilder(command.getEntityId(), + ((ModifyPolicyImport) command).getPolicyImport().getImportedPolicyId()) + .dittoHeaders(command.getDittoHeaders()) + .build()); return new PolicyCommandToModifyExceptionRegistry(mappingStrategies); } diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotAccessibleException.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotAccessibleException.java index c7bf0b58ef..331fd1b449 100644 --- a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotAccessibleException.java +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotAccessibleException.java @@ -43,11 +43,12 @@ public final class PolicyImportNotAccessibleException extends DittoRuntimeExcept */ public static final String ERROR_CODE = ERROR_CODE_PREFIX + "import.notfound"; - private static final String MESSAGE_TEMPLATE = "The import with PolicyId ''{0}'' on the Policy with ID ''{1}''" + - " could not be found or requester had insufficient permissions to access it."; + private static final String MESSAGE_TEMPLATE = + "The import of the Policy with ID ''{0}'' on the Policy with ID ''{1}''" + + " could not be found or requester had insufficient permissions to access it."; - private static final String DEFAULT_DESCRIPTION = "Check if the ID of the Policy and the PolicyId of your requested" + - " PolicyImport was correct and you have sufficient permissions."; + private static final String DEFAULT_DESCRIPTION = + "Check if the ID of the Policy and the imported Policy was correct and you have sufficient permissions."; private static final long serialVersionUID = 3798954052492368034L; diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotModifiableException.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotModifiableException.java new file mode 100755 index 0000000000..82f1a15b42 --- /dev/null +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportNotModifiableException.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 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.policies.model.signals.commands.exceptions; + +import java.net.URI; +import java.text.MessageFormat; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.NotThreadSafe; + +import org.eclipse.ditto.base.model.common.HttpStatus; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.json.JsonParsableException; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.policies.model.PolicyException; +import org.eclipse.ditto.policies.model.PolicyId; + +/** + * Thrown if a {@link org.eclipse.ditto.policies.model.PolicyImport} of a {@link org.eclipse.ditto.policies.model.Policy} + * could not be modified because the requester had insufficient permissions. + */ +@Immutable +@JsonParsableException(errorCode = PolicyImportNotModifiableException.ERROR_CODE) +public final class PolicyImportNotModifiableException extends DittoRuntimeException implements PolicyException { + + /** + * Error code of this exception. + */ + public static final String ERROR_CODE = ERROR_CODE_PREFIX + "import.notmodifiable"; + + private static final String MESSAGE_TEMPLATE = + "The Import of the Policy with ID ''{1}'' in Policy with ID ''{0}'' could not be modified as the requester had insufficient permissions."; + + private static final String DEFAULT_DESCRIPTION = + "Check if the ID of your requested Policy and imported Policy was correct and you have sufficient permissions."; + + private static final long serialVersionUID = -6563690825509942869L; + + private PolicyImportNotModifiableException(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href) { + super(ERROR_CODE, HttpStatus.FORBIDDEN, dittoHeaders, message, description, cause, href); + } + + /** + * A mutable builder for a {@code PolicyImportNotModifiableException}. + * + * @param policyId the identifier of the Policy. + * @param importedPolicyId the identifier of the imported Policy. + * @return the builder. + */ + public static Builder newBuilder(final PolicyId policyId, final PolicyId importedPolicyId) { + return new Builder(policyId, importedPolicyId); + } + + /** + * Constructs a new {@code PolicyImportNotModifiableException} object with given message. + * + * @param message detail message. This message can be later retrieved by the {@link #getMessage()} method. + * @param dittoHeaders the headers of the command which resulted in this exception. + * @return the new PolicyImportNotModifiableException. + * @throws NullPointerException if {@code dittoHeaders} is {@code null}. + */ + public static PolicyImportNotModifiableException fromMessage(@Nullable final String message, + final DittoHeaders dittoHeaders) { + return DittoRuntimeException.fromMessage(message, dittoHeaders, new Builder()); + } + + /** + * Constructs a new {@code PolicyImportNotModifiableException} object with the exception message extracted from the + * given JSON object. + * + * @param jsonObject the JSON to read the {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException.JsonFields#MESSAGE} field from. + * @param dittoHeaders the headers of the command which resulted in this exception. + * @return the new PolicyImportNotModifiableException. + * @throws NullPointerException if any argument is {@code null}. + * @throws org.eclipse.ditto.json.JsonMissingFieldException if this JsonObject did not contain an error message. + * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected + * format. + */ + public static PolicyImportNotModifiableException fromJson(final JsonObject jsonObject, + final DittoHeaders dittoHeaders) { + return DittoRuntimeException.fromJson(jsonObject, dittoHeaders, new Builder()); + } + + @Override + public DittoRuntimeException setDittoHeaders(final DittoHeaders dittoHeaders) { + return new Builder() + .message(getMessage()) + .description(getDescription().orElse(null)) + .cause(getCause()) + .href(getHref().orElse(null)) + .dittoHeaders(dittoHeaders) + .build(); + } + + /** + * A mutable builder with a fluent API for a {@link org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyImportNotModifiableException}. + */ + @NotThreadSafe + public static final class Builder extends DittoRuntimeExceptionBuilder { + + private Builder() { + description(DEFAULT_DESCRIPTION); + } + + private Builder(final PolicyId policyId, final PolicyId importedPolicyId) { + this(); + message(MessageFormat.format(MESSAGE_TEMPLATE, String.valueOf(policyId), String.valueOf(importedPolicyId))); + } + + @Override + protected PolicyImportNotModifiableException doBuild(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href) { + return new PolicyImportNotModifiableException(dittoHeaders, message, description, cause, href); + } + } + +} diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportsNotModifiableException.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportsNotModifiableException.java new file mode 100755 index 0000000000..be9957fab5 --- /dev/null +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyImportsNotModifiableException.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 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.policies.model.signals.commands.exceptions; + +import java.net.URI; +import java.text.MessageFormat; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.NotThreadSafe; + +import org.eclipse.ditto.base.model.common.HttpStatus; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.json.JsonParsableException; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.policies.model.PolicyException; +import org.eclipse.ditto.policies.model.PolicyId; + +/** + * Thrown if the {@link org.eclipse.ditto.policies.model.PolicyImports} of a {@link org.eclipse.ditto.policies.model.Policy} could not be modified because the requester had + * insufficient permissions. + */ +@Immutable +@JsonParsableException(errorCode = PolicyImportsNotModifiableException.ERROR_CODE) +public final class PolicyImportsNotModifiableException extends DittoRuntimeException implements PolicyException { + + /** + * Error code of this exception. + */ + public static final String ERROR_CODE = ERROR_CODE_PREFIX + "imports.notmodifiable"; + + private static final String MESSAGE_TEMPLATE = + "The Imports of the Policy with ID ''{0}'' could not be modified as the requester had insufficient permissions."; + + private static final String DEFAULT_DESCRIPTION = + "Check if the ID of your requested Policy was correct and you have sufficient permissions."; + + private static final long serialVersionUID = -4656006844212207608L; + + private PolicyImportsNotModifiableException(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href) { + super(ERROR_CODE, HttpStatus.FORBIDDEN, dittoHeaders, message, description, cause, href); + } + + /** + * A mutable builder for a {@code PolicyImportsNotModifiableException}. + * + * @param policyId the identifier of the Policy. + * @return the builder. + */ + public static Builder newBuilder(final PolicyId policyId) { + return new Builder(policyId); + } + + /** + * Constructs a new {@code PolicyImportsNotModifiableException} object with given message. + * + * @param message detail message. This message can be later retrieved by the {@link #getMessage()} method. + * @param dittoHeaders the headers of the command which resulted in this exception. + * @return the new PolicyImportsNotModifiableException. + * @throws NullPointerException if {@code dittoHeaders} is {@code null}. + */ + public static PolicyImportsNotModifiableException fromMessage(@Nullable final String message, + final DittoHeaders dittoHeaders) { + return DittoRuntimeException.fromMessage(message, dittoHeaders, new Builder()); + } + + /** + * Constructs a new {@code PolicyImportsNotModifiableException} object with the exception message extracted from the + * given JSON object. + * + * @param jsonObject the JSON to read the {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException.JsonFields#MESSAGE} field from. + * @param dittoHeaders the headers of the command which resulted in this exception. + * @return the new PolicyImportsNotModifiableException. + * @throws NullPointerException if any argument is {@code null}. + * @throws org.eclipse.ditto.json.JsonMissingFieldException if this JsonObject did not contain an error message. + * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected + * format. + */ + public static PolicyImportsNotModifiableException fromJson(final JsonObject jsonObject, + final DittoHeaders dittoHeaders) { + return DittoRuntimeException.fromJson(jsonObject, dittoHeaders, new Builder()); + } + + @Override + public DittoRuntimeException setDittoHeaders(final DittoHeaders dittoHeaders) { + return new Builder() + .message(getMessage()) + .description(getDescription().orElse(null)) + .cause(getCause()) + .href(getHref().orElse(null)) + .dittoHeaders(dittoHeaders) + .build(); + } + + /** + * A mutable builder with a fluent API for a {@link org.eclipse.ditto.policies.model.signals.commands.exceptions.PolicyImportsNotModifiableException}. + */ + @NotThreadSafe + public static final class Builder extends DittoRuntimeExceptionBuilder { + + private Builder() { + description(DEFAULT_DESCRIPTION); + } + + private Builder(final PolicyId policyId) { + this(); + message(MessageFormat.format(MESSAGE_TEMPLATE, String.valueOf(policyId))); + } + + @Override + protected PolicyImportsNotModifiableException doBuild(final DittoHeaders dittoHeaders, + @Nullable final String message, + @Nullable final String description, + @Nullable final Throwable cause, + @Nullable final URI href) { + return new PolicyImportsNotModifiableException(dittoHeaders, message, description, cause, href); + } + } + +} diff --git a/policies/service/src/main/resources/policies.conf b/policies/service/src/main/resources/policies.conf index 573b52207c..59b229dc06 100755 --- a/policies/service/src/main/resources/policies.conf +++ b/policies/service/src/main/resources/policies.conf @@ -5,7 +5,8 @@ ditto { pre-enforcer-provider.extension-config.pre-enforcers = [ "org.eclipse.ditto.policies.enforcement.pre.BlockedNamespacePreEnforcer", "org.eclipse.ditto.policies.enforcement.pre.CommandWithOptionalEntityPreEnforcer", - "org.eclipse.ditto.policies.enforcement.pre.CreationRestrictionPreEnforcer" + "org.eclipse.ditto.policies.enforcement.pre.CreationRestrictionPreEnforcer", + "org.eclipse.ditto.policies.enforcement.pre.PolicyImportsPreEnforcer" ] signal-transformers-provider.extension-config.signal-transformers = [ "org.eclipse.ditto.policies.service.enforcement.pre.ModifyToCreatePolicyTransformer", // always keep this as first transformer in order to guarantee that all following transformers know that the command is creating a policy instead of modifying it