This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch CAUSEWAY-3524 in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 3f660e3c77cfb38dcdf228307e1048d367acd7e2 Author: danhaywood <[email protected]> AuthorDate: Sat Jul 8 17:51:44 2023 +0100 CAUSEWAY-3524: adds tests for PermissionsEvaluationServiceForSecman with regular nd with v1 compatible impls of ApplicationFeatureIdTransformer --- .../services/appfeat/ApplicationFeatureId.java | 46 ++++++- .../permission/dom/ApplicationPermissionValue.java | 1 - .../PermissionsEvaluationServiceForSecman.java | 19 +++ ...icationFeatureIdTransformerV1Compatibility.java | 34 +++--- ...onFeatureIdTransformerV1Compatibility_Test.java | 40 +++++- ...EvaluationServiceForSecmanV1_evaluate_Test.java | 136 +++++++++++++++++++++ ...nsEvaluationServiceForSecman_evaluate_Test.java | 125 +++++++++++++++++++ 7 files changed, 380 insertions(+), 21 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java index b1432099c0..b80aaf2b5c 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeat/ApplicationFeatureId.java @@ -243,18 +243,29 @@ implements @Programmatic @Getter private String namespace; + /** + * The type name, eg "Customer". + * + * <p> + * Will be {@code null} if this feature is a {@link ApplicationFeatureSort#NAMESPACE namespace}. + * </p> + */ @Programmatic @Getter private String typeSimpleName; /** * Logical (simple) name of the member (in case of actions not including the parameter list). - * Consequently method overloading is not supported. + * (This works because method overloading of actions is not supported by the framework). + * * <p> * If there is a member name clash involving an <i>action</i> and an <i>association</i>, * then consequently any permissions defined automatically apply to both and one cannot separate * these. + * </p> + * * <p> - * {@code null} if not a member + * Will be {@code null} if this feature is not a {@link ApplicationFeatureSort#MEMBER member}. + * </p> */ @Programmatic @Getter private String logicalMemberName; @@ -450,4 +461,35 @@ implements return newFeature(namespace, this.getTypeSimpleName(), this.getLogicalMemberName()); } + /** + * Returns a new instance that is a clone of this feature (expected to be a + * {@link ApplicationFeatureSort#MEMBER member} or {@link ApplicationFeatureSort#TYPE type}), but with the + * logicalTypeName taken from the argument. + * + * <p> + * If this feature is instead a {@link ApplicationFeatureSort#NAMESPACE namespace} then the feature is + * returned unchanged. + * </p> + * + * <p> + * Note: if the feature is a {@link ApplicationFeatureSort#TYPE type}, then this method will be the same as + * if {@link ApplicationFeatureId#newType(String)} had been used to create a new instance. The method exists + * though to provide a standardized way of transforming {@link ApplicationFeatureId}s where possible, without + * the caller having to worry as to what {@link #getSort() sort} of feature it is dealing with. + * </p> + * + * @param logicalTypeName + */ + public ApplicationFeatureId withLogicalTypeName(final @NonNull String logicalTypeName) { + switch (getSort()) { + case MEMBER: + return newMember(logicalTypeName, this.getLogicalMemberName()); + case TYPE: + return newType(logicalTypeName); + case NAMESPACE: + default: + return this; + } + } + } diff --git a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/permission/dom/ApplicationPermissionValue.java b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/permission/dom/ApplicationPermissionValue.java index 77ed07c631..54a3b2a514 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/permission/dom/ApplicationPermissionValue.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/permission/dom/ApplicationPermissionValue.java @@ -51,7 +51,6 @@ public class ApplicationPermissionValue implements Comparable<ApplicationPermiss this.mode = mode; } - // -- featureId private final ApplicationFeatureId featureId; @Programmatic diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman.java index b0a6adb8bb..35c56b43d6 100644 --- a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman.java +++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman.java @@ -26,6 +26,9 @@ import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; +import lombok.Builder; +import lombok.NoArgsConstructor; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -62,6 +65,22 @@ implements PermissionsEvaluationService { private final @NonNull PermissionsEvaluationPolicy policy; // serializable + /** + * for testing only + * + * @param policy + * @param applicationFeatureIdTransformer + */ + @Builder + private PermissionsEvaluationServiceForSecman( + PermissionsEvaluationPolicy policy, + ApplicationFeatureIdTransformer applicationFeatureIdTransformer + ) { + this.policy = policy; + this.applicationFeatureIdTransformer = applicationFeatureIdTransformer; + } + + @Inject public PermissionsEvaluationServiceForSecman(final CausewayConfiguration causewayConfiguration) { this.policy = Optional.ofNullable( diff --git a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility.java b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility.java index debde0dad2..403607ef8b 100644 --- a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility.java +++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility.java @@ -18,23 +18,18 @@ */ package org.apache.causeway.extensions.secman.integration.permissions; -import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; -import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; +import lombok.val; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.AutoConfigureOrder; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.apache.causeway.core.metamodel.spec.ObjectSpecification; +import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; -import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.annotation.Programmatic; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; -import org.apache.causeway.extensions.secman.applib.CausewayModuleExtSecmanApplib; -import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionValue; import javax.inject.Inject; @@ -44,16 +39,25 @@ public class ApplicationFeatureIdTransformerV1Compatibility implements Applicati private final SpecificationLoader specificationLoader; + /** + * local cache. + */ + private final Map<ApplicationFeatureId, ApplicationFeatureId> transformedByOriginal = new HashMap<>(); + @Programmatic @Override public ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId) { - return applicationFeatureId; + return transformedByOriginal.computeIfAbsent(applicationFeatureId, this::doTransform); } - @Programmatic - @Override - public Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) { - return permissionValues; + private ApplicationFeatureId doTransform(ApplicationFeatureId applicationFeatureId) { + val logicalTypeName = applicationFeatureId.getLogicalTypeName(); + val logicalTypeNameBasedOnPhysicalName = + specificationLoader.specForLogicalTypeName(logicalTypeName) + .map(ObjectSpecification::getCorrespondingClass) + .map(Class::getName) + .orElse(logicalTypeName); + return applicationFeatureId.withLogicalTypeName(logicalTypeNameBasedOnPhysicalName); } } diff --git a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility_Test.java b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility_Test.java index eae7ce4454..fb15280fd8 100644 --- a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility_Test.java +++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility_Test.java @@ -2,6 +2,7 @@ package org.apache.causeway.extensions.secman.integration.permissions; import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId; +import org.apache.causeway.applib.services.appfeat.ApplicationFeatureSort; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; @@ -37,11 +38,44 @@ class ApplicationFeatureIdTransformerV1Compatibility_Test { } @Test - void happy_case() { + void member_when_its_owning_type_exists() { ApplicationFeatureId input = ApplicationFeatureId.newMember("customer.Customer", "lastName"); - ApplicationFeatureId transform = transformer.transform(input); + ApplicationFeatureId transformed = transformer.transform(input); - assertThat(transform.getLogicalTypeName()).isEqualTo(Customer.class.getName()); + assertThat(transformed.getSort()).isEqualTo(ApplicationFeatureSort.MEMBER); + assertThat(transformed.getLogicalTypeName()).isEqualTo(Customer.class.getName()); + assertThat(transformed.getLogicalMemberName()).isEqualTo("lastName"); + } + + @Test + void member_when_its_owning_type_does_not_exist() { + ApplicationFeatureId input = ApplicationFeatureId.newMember("order.Order", "placedOn"); + ApplicationFeatureId transformed = transformer.transform(input); + + assertThat(transformed.getSort()).isEqualTo(ApplicationFeatureSort.MEMBER); + assertThat(transformed.getLogicalTypeName()).isEqualTo("order.Order"); + assertThat(transformed.getLogicalMemberName()).isEqualTo("placedOn"); + } + + @Test + void type_when_exists() { + ApplicationFeatureId input = ApplicationFeatureId.newType("customer.Customer"); + ApplicationFeatureId transformed = transformer.transform(input); + + assertThat(transformed.getSort()).isEqualTo(ApplicationFeatureSort.TYPE); + assertThat(transformed.getLogicalTypeName()).isEqualTo(Customer.class.getName()); + assertThat(transformed.getLogicalMemberName()).isNull(); + } + + + @Test + void type_when_does_not_exist() { + ApplicationFeatureId input = ApplicationFeatureId.newType("order.Order"); + ApplicationFeatureId transformed = transformer.transform(input); + + assertThat(transformed.getSort()).isEqualTo(ApplicationFeatureSort.TYPE); + assertThat(transformed.getLogicalTypeName()).isEqualTo("order.Order"); + assertThat(transformed.getLogicalMemberName()).isNull(); } } \ No newline at end of file diff --git a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecmanV1_evaluate_Test.java b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecmanV1_evaluate_Test.java new file mode 100644 index 0000000000..f2e311fda9 --- /dev/null +++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecmanV1_evaluate_Test.java @@ -0,0 +1,136 @@ +package org.apache.causeway.extensions.secman.integration.permissions; + +import org.apache.causeway.core.metamodel.spec.ObjectSpecification; +import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; +import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionValue; +import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionValueSet; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.apache.causeway.applib.services.appfeat.ApplicationFeatureId.*; +import static org.apache.causeway.core.config.CausewayConfiguration.Extensions.Secman.PermissionsEvaluationPolicy.ALLOW_BEATS_VETO; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode.CHANGING; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode.VIEWING; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule.ALLOW; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule.VETO; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; + +@ExtendWith(MockitoExtension.class) +class PermissionsEvaluationServiceForSecmanV1_evaluate_Test { + + static class Customer {} + + @Mock SpecificationLoader mockSpecificationLoader; + @Mock ObjectSpecification mockSpecificationForCustomerClass; + + PermissionsEvaluationServiceForSecman evaluator; + ApplicationFeatureIdTransformer applicationFeatureIdTransformer; + + @BeforeEach + void setup() { + applicationFeatureIdTransformer = new ApplicationFeatureIdTransformerV1Compatibility(mockSpecificationLoader); + + lenient().when(mockSpecificationLoader.specForLogicalTypeName("customer.Customer")).thenReturn(Optional.of(mockSpecificationForCustomerClass)); + lenient().when(mockSpecificationForCustomerClass.getCorrespondingClass()).then(__ -> ApplicationFeatureIdTransformerV1Compatibility_Test.Customer.class); + + evaluator = PermissionsEvaluationServiceForSecman.builder() + .applicationFeatureIdTransformer(applicationFeatureIdTransformer) + .policy(ALLOW_BEATS_VETO) + .build(); + } + + @Test + void granted_viewing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void granted_viewing_via_namespace_changing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, CHANGING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void no_opinion_changing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + CHANGING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate).isNull(); + } + + @Test + void granted_changing_via_namespace_changing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + CHANGING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, CHANGING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void granted_viewing_via_type() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newType("customer.Customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void veto_viewing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), VETO, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isFalse(); + } + + @Test + void granted_viewing_when_namespace_veto_viewing_overridden_by_type_allowing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), VETO, VIEWING), + new ApplicationPermissionValue(newNamespace("customer.Customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isFalse(); + } + +} \ No newline at end of file diff --git a/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman_evaluate_Test.java b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman_evaluate_Test.java new file mode 100644 index 0000000000..a5b00e4c15 --- /dev/null +++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/PermissionsEvaluationServiceForSecman_evaluate_Test.java @@ -0,0 +1,125 @@ +package org.apache.causeway.extensions.secman.integration.permissions; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionValue; +import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionValueSet; + +import static org.apache.causeway.applib.services.appfeat.ApplicationFeatureId.newMember; +import static org.apache.causeway.applib.services.appfeat.ApplicationFeatureId.newNamespace; +import static org.apache.causeway.applib.services.appfeat.ApplicationFeatureId.newType; +import static org.apache.causeway.core.config.CausewayConfiguration.Extensions.Secman.PermissionsEvaluationPolicy.ALLOW_BEATS_VETO; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode.CHANGING; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode.VIEWING; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule.ALLOW; +import static org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule.VETO; + +@ExtendWith(MockitoExtension.class) +class PermissionsEvaluationServiceForSecman_evaluate_Test { + + PermissionsEvaluationServiceForSecman evaluator; + ApplicationFeatureIdTransformer applicationFeatureIdTransformer; + + @BeforeEach + void setup() { + applicationFeatureIdTransformer = new ApplicationFeatureIdTransformerIdentity(); + + evaluator = PermissionsEvaluationServiceForSecman.builder() + .applicationFeatureIdTransformer(applicationFeatureIdTransformer) + .policy(ALLOW_BEATS_VETO) + .build(); + } + + @Test + void granted_viewing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void granted_viewing_via_namespace_changing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, CHANGING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void no_opinion_changing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + CHANGING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate).isNull(); + } + + @Test + void granted_changing_via_namespace_changing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + CHANGING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), ALLOW, CHANGING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void granted_viewing_via_type() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newType("customer.Customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isTrue(); + } + + @Test + void veto_viewing_via_namespace_viewing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), VETO, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isFalse(); + } + + @Test + void granted_viewing_when_namespace_veto_viewing_overridden_by_type_allowing() { + ApplicationPermissionValueSet.Evaluation evaluate = evaluator.evaluate( + newMember("customer.Customer#lastName"), + VIEWING, + List.of( + new ApplicationPermissionValue(newNamespace("customer"), VETO, VIEWING), + new ApplicationPermissionValue(newNamespace("customer.Customer"), ALLOW, VIEWING) + ) + ); + assertThat(evaluate.isGranted()).isFalse(); + } + +} \ No newline at end of file
