You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2023/07/08 16:53:55 UTC

[causeway] branch CAUSEWAY-3524 updated (81780e268c -> bd0a48ca66)

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a change to branch CAUSEWAY-3524
in repository https://gitbox.apache.org/repos/asf/causeway.git


 discard 81780e268c CAUSEWAY-3524: adds tests for PermissionsEvaluationServiceForSecman
 discard 462d40087a hacking
    omit b7ff7fe70c introduce ApplicationFeatureIdTransformer for secman
     new d38390b113 CAUSEWAY-3524: introduce ApplicationFeatureIdTransformer for secman
     new 2fd27a5000 CAUSEWAY-3524
     new bd0a48ca66 CAUSEWAY-3524: adds tests for PermissionsEvaluationServiceForSecman

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (81780e268c)
            \
             N -- N -- N   refs/heads/CAUSEWAY-3524 (bd0a48ca66)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:


[causeway] 02/03: CAUSEWAY-3524

Posted by da...@apache.org.
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 2fd27a500041265272a14152539f17ce0b070f35
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Sat Jul 8 12:32:33 2023 +0100

    CAUSEWAY-3524
---
 extensions/security/secman/integration/pom.xml     |  7 +++
 .../CausewayModuleExtSecmanIntegration.java        |  4 +-
 .../ApplicationFeatureIdTransformer.java           | 16 ------
 .../ApplicationFeatureIdTransformerIdentity.java   | 66 ++++++++++++++++++++++
 .../PermissionsEvaluationServiceForSecman.java     | 14 +----
 .../src/main/resources/META-INF/spring.factories   |  3 +-
 ...icationFeatureIdTransformerV1Compatibility.java | 59 +++++++++++++++++++
 ...onFeatureIdTransformerV1Compatibility_Test.java | 47 +++++++++++++++
 8 files changed, 186 insertions(+), 30 deletions(-)

diff --git a/extensions/security/secman/integration/pom.xml b/extensions/security/secman/integration/pom.xml
index 6977e5f0dd..82668509be 100644
--- a/extensions/security/secman/integration/pom.xml
+++ b/extensions/security/secman/integration/pom.xml
@@ -48,6 +48,13 @@
 			<scope>provided</scope>
 		</dependency>
 
+        <dependency>
+            <groupId>org.apache.causeway.testing</groupId>
+            <artifactId>causeway-testing-unittestsupport-applib</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
     </dependencies>
 
 </project>
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
index b8c8bb0ef3..25ff15a256 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
@@ -18,7 +18,7 @@
  */
 package org.apache.causeway.extensions.secman.integration;
 
-import org.apache.causeway.extensions.secman.integration.permissions.ApplicationFeatureIdTransformer;
+import org.apache.causeway.extensions.secman.integration.permissions.ApplicationFeatureIdTransformerIdentity;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
@@ -46,7 +46,7 @@ import org.apache.causeway.extensions.secman.integration.userreg.UserRegistratio
         TableColumnVisibilityServiceForSecman.class,
         ImpersonateMenuAdvisorForSecman.class, //not activated by default yet
         PermissionsEvaluationServiceForSecman.class,
-        ApplicationFeatureIdTransformer.Identity.class,
+        ApplicationFeatureIdTransformerIdentity.class,
         UserRegistrationServiceForSecman.class,
 
         UserMementoRefinerFromApplicationUser.class,
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java
index a843a19710..ffbd3544bb 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java
@@ -53,20 +53,4 @@ public interface ApplicationFeatureIdTransformer {
                 .collect(Collectors.toList());
     }
 
-    @Service
-    @Priority(PriorityPrecedence.LATE)
-    class Identity implements ApplicationFeatureIdTransformer {
-
-        @Programmatic
-        @Override
-        public ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId) {
-            return applicationFeatureId;
-        }
-
-        @Programmatic
-        @Override
-        public Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) {
-            return permissionValues;
-        }
-    }
 }
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerIdentity.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerIdentity.java
new file mode 100644
index 0000000000..4a4810a07b
--- /dev/null
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerIdentity.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.extensions.secman.integration.permissions;
+
+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 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 java.util.Collection;
+
+/**
+ * Created as a service, if required, by {@link AutoConfiguration}.
+ *
+ * @since 2.0 {@index}
+ */
+public class ApplicationFeatureIdTransformerIdentity implements ApplicationFeatureIdTransformer {
+
+    @Programmatic
+    @Override
+    public ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId) {
+        return applicationFeatureId;
+    }
+
+    @Programmatic
+    @Override
+    public Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) {
+        return permissionValues;
+    }
+
+    @AutoConfigureOrder(PriorityPrecedence.LATE)
+    @Configuration
+    public static class AutoConfiguration {
+
+        @Bean(CausewayModuleExtSecmanApplib.NAMESPACE + ".ApplicationFeatureIdTransformerIdentity")
+        @ConditionalOnMissingBean(ApplicationFeatureIdTransformer.class)
+        @Qualifier("Identity")
+        public ApplicationFeatureIdTransformer ApplicationFeatureIdTransformer() {
+            return new ApplicationFeatureIdTransformerIdentity();
+        }
+
+    }
+}
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 b0991ec9fc..b0a6adb8bb 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
@@ -76,8 +76,8 @@ implements PermissionsEvaluationService {
             final ApplicationPermissionMode mode,
             final Collection<ApplicationPermissionValue> permissionValuesInput) {
 
-        final ApplicationFeatureId targetMemberId = applicationFeatureIdTransformer().transform(targetMemberIdInput);
-        final Collection<ApplicationPermissionValue> permissionValues = applicationFeatureIdTransformer().transform(permissionValuesInput);
+        final ApplicationFeatureId targetMemberId = applicationFeatureIdTransformer.transform(targetMemberIdInput);
+        final Collection<ApplicationPermissionValue> permissionValues = applicationFeatureIdTransformer.transform(permissionValuesInput);
 
         if(_NullSafe.isEmpty(permissionValues)) {
             return null;
@@ -116,14 +116,6 @@ implements PermissionsEvaluationService {
         throw _Exceptions.illegalArgument("PermissionsEvaluationPolicy '%s' not recognised", policy);
     }
 
-    private ApplicationFeatureIdTransformer applicationFeatureIdTransformerCached;
-    ApplicationFeatureIdTransformer applicationFeatureIdTransformer() {
-        if (applicationFeatureIdTransformerCached == null) {
-            applicationFeatureIdTransformerCached = applicationFeatureIdTransformers == null || applicationFeatureIdTransformers.isEmpty() ? new ApplicationFeatureIdTransformer.Identity() : applicationFeatureIdTransformers.get(0);
-        }
-        return applicationFeatureIdTransformerCached;
-    };
-
-    @Inject List<ApplicationFeatureIdTransformer> applicationFeatureIdTransformers;
+    @Inject ApplicationFeatureIdTransformer applicationFeatureIdTransformer;
 
 }
diff --git a/extensions/security/secman/integration/src/main/resources/META-INF/spring.factories b/extensions/security/secman/integration/src/main/resources/META-INF/spring.factories
index 34738a98d8..30d7a4af8a 100644
--- a/extensions/security/secman/integration/src/main/resources/META-INF/spring.factories
+++ b/extensions/security/secman/integration/src/main/resources/META-INF/spring.factories
@@ -1,2 +1,3 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  org.apache.causeway.extensions.secman.integration.authenticator.AuthenticatorSecmanAutoConfiguration
+  org.apache.causeway.extensions.secman.integration.authenticator.AuthenticatorSecmanAutoConfiguration,
+  org.apache.causeway.extensions.secman.integration.permissions.ApplicationFeatureIdTransformerIdentity.AutoConfiguration
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
new file mode 100644
index 0000000000..debde0dad2
--- /dev/null
+++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.extensions.secman.integration.permissions;
+
+import java.util.Collection;
+
+import lombok.RequiredArgsConstructor;
+
+import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
+
+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.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;
+
+
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class ApplicationFeatureIdTransformerV1Compatibility implements ApplicationFeatureIdTransformer {
+
+    private final SpecificationLoader specificationLoader;
+
+    @Programmatic
+    @Override
+    public ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId) {
+        return applicationFeatureId;
+    }
+
+    @Programmatic
+    @Override
+    public Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) {
+        return permissionValues;
+    }
+
+}
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
new file mode 100644
index 0000000000..eae7ce4454
--- /dev/null
+++ b/extensions/security/secman/integration/src/test/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformerV1Compatibility_Test.java
@@ -0,0 +1,47 @@
+package org.apache.causeway.extensions.secman.integration.permissions;
+
+import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId;
+
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
+
+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.invocation.InvocationOnMock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.stubbing.Answer;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.lenient;
+
+@ExtendWith(MockitoExtension.class)
+class ApplicationFeatureIdTransformerV1Compatibility_Test {
+
+    public static class Customer {}
+
+    @Mock SpecificationLoader mockSpecificationLoader;
+    @Mock ObjectSpecification mockSpecificationForCustomerClass;
+
+    ApplicationFeatureIdTransformer transformer;
+
+    @BeforeEach
+    void setup() {
+        transformer = new ApplicationFeatureIdTransformerV1Compatibility(mockSpecificationLoader);
+
+        lenient().when(mockSpecificationLoader.specForLogicalTypeName("customer.Customer")).thenReturn(Optional.of(mockSpecificationForCustomerClass));
+        lenient().when(mockSpecificationForCustomerClass.getCorrespondingClass()).then(__ -> Customer.class);
+    }
+
+    @Test
+    void happy_case() {
+        ApplicationFeatureId input = ApplicationFeatureId.newMember("customer.Customer", "lastName");
+        ApplicationFeatureId transform = transformer.transform(input);
+
+        assertThat(transform.getLogicalTypeName()).isEqualTo(Customer.class.getName());
+    }
+
+}
\ No newline at end of file


[causeway] 03/03: CAUSEWAY-3524: adds tests for PermissionsEvaluationServiceForSecman

Posted by da...@apache.org.
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 bd0a48ca66646b3973112bbaa0df4c323c5693a2
Author: danhaywood <da...@haywood-associates.co.uk>
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 &quot;Customer&quot;.
+     *
+     * <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


[causeway] 01/03: CAUSEWAY-3524: introduce ApplicationFeatureIdTransformer for secman

Posted by da...@apache.org.
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 d38390b113fa3bbad91ae80205d32b77766aa611
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jul 7 19:02:13 2023 +0200

    CAUSEWAY-3524: introduce ApplicationFeatureIdTransformer for secman
    
    to provide an SPI allowing backward compatibility of security perms
---
 antora/supplemental-ui/index.html                  | 25 ++++++++
 .../permission/dom/ApplicationPermissionValue.java |  4 ++
 .../CausewayModuleExtSecmanIntegration.java        |  3 +
 .../ApplicationFeatureIdTransformer.java           | 72 ++++++++++++++++++++++
 .../PermissionsEvaluationServiceForSecman.java     | 18 +++++-
 5 files changed, 120 insertions(+), 2 deletions(-)

diff --git a/antora/supplemental-ui/index.html b/antora/supplemental-ui/index.html
index 293c38ea48..08ef4a2001 100644
--- a/antora/supplemental-ui/index.html
+++ b/antora/supplemental-ui/index.html
@@ -791,5 +791,30 @@ docker run -p 8080:8080 apache/causeway-app-demo-jdo:latest</pre>
 <!-- Template Main JavaScript File -->
 <script src="_/js/home/main.js"></script>
 
+<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
+<script>
+  function focusSearchInput () { document.querySelector('#algolia-search-input').focus() }
+  var search = docsearch({
+    appId: '5ISP5TFAEN',
+    apiKey: '0fc51c28b4ad46e7318e96d4e97fab7c',
+    indexName: 'causeway-apache-org',
+    inputSelector: '#algolia-search-input',
+    autocompleteOptions: { hint: false, keyboardShortcuts: ['s'] },
+    debug: false,
+  }).autocomplete
+  search.on('autocomplete:closed', function () { search.autocomplete.setVal() })
+  focusSearchInput()
+  window.addEventListener('load', focusSearchInput);
+</script>
+
+<!--
+  docsearch options:
+  https://docsearch.algolia.com/docs/behavior/
+-->
+<!--
+  https://www.algolia.com/doc/api-reference/api-parameters/
+  algoliaOptions: { hitsPerPage: 6 },
+-->
+
 </body>
 </html>
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 3aff46724d..77ed07c631 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
@@ -117,6 +117,10 @@ public class ApplicationPermissionValue implements Comparable<ApplicationPermiss
         return false;
     }
 
+    @Programmatic
+    public ApplicationPermissionValue withFeatureId(ApplicationFeatureId applicationFeatureId) {
+        return new ApplicationPermissionValue(applicationFeatureId, this.rule, this.mode);
+    }
 
 
     // -- Comparators
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
index 6e45015dcd..b8c8bb0ef3 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/CausewayModuleExtSecmanIntegration.java
@@ -18,6 +18,8 @@
  */
 package org.apache.causeway.extensions.secman.integration;
 
+import org.apache.causeway.extensions.secman.integration.permissions.ApplicationFeatureIdTransformer;
+
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
@@ -44,6 +46,7 @@ import org.apache.causeway.extensions.secman.integration.userreg.UserRegistratio
         TableColumnVisibilityServiceForSecman.class,
         ImpersonateMenuAdvisorForSecman.class, //not activated by default yet
         PermissionsEvaluationServiceForSecman.class,
+        ApplicationFeatureIdTransformer.Identity.class,
         UserRegistrationServiceForSecman.class,
 
         UserMementoRefinerFromApplicationUser.class,
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java
new file mode 100644
index 0000000000..a843a19710
--- /dev/null
+++ b/extensions/security/secman/integration/src/main/java/org/apache/causeway/extensions/secman/integration/permissions/ApplicationFeatureIdTransformer.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.extensions.secman.integration.permissions;
+
+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.permission.dom.ApplicationPermissionValue;
+
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Priority;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * Provides an SPI used by {@link PermissionsEvaluationServiceForSecman} that
+ * pre-processes {@link ApplicationFeatureId}s before evaluating them.
+ *
+ * <p>
+ *     The primary use case is to enable backward compatibility; rather than evaluating using the
+ *     logicalTypeName#memberId, an alternative transformer could use the packageName#memberId (as was done in v1).
+ * </p>
+ *
+ */
+public interface ApplicationFeatureIdTransformer {
+
+    @Programmatic
+    ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId);
+
+    @Programmatic
+    default Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) {
+        return permissionValues.stream()
+                .map(apv -> apv.withFeatureId(transform(apv.getFeatureId())))
+                .collect(Collectors.toList());
+    }
+
+    @Service
+    @Priority(PriorityPrecedence.LATE)
+    class Identity implements ApplicationFeatureIdTransformer {
+
+        @Programmatic
+        @Override
+        public ApplicationFeatureId transform(ApplicationFeatureId applicationFeatureId) {
+            return applicationFeatureId;
+        }
+
+        @Programmatic
+        @Override
+        public Collection<ApplicationPermissionValue> transform(Collection<ApplicationPermissionValue> permissionValues) {
+            return permissionValues;
+        }
+    }
+}
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 3496bb1b6a..b0991ec9fc 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
@@ -20,6 +20,7 @@ package org.apache.causeway.extensions.secman.integration.permissions;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -71,9 +72,12 @@ implements PermissionsEvaluationService {
 
     @Override
     public ApplicationPermissionValueSet.Evaluation evaluate(
-            final ApplicationFeatureId targetMemberId,
+            final ApplicationFeatureId targetMemberIdInput,
             final ApplicationPermissionMode mode,
-            final Collection<ApplicationPermissionValue> permissionValues) {
+            final Collection<ApplicationPermissionValue> permissionValuesInput) {
+
+        final ApplicationFeatureId targetMemberId = applicationFeatureIdTransformer().transform(targetMemberIdInput);
+        final Collection<ApplicationPermissionValue> permissionValues = applicationFeatureIdTransformer().transform(permissionValuesInput);
 
         if(_NullSafe.isEmpty(permissionValues)) {
             return null;
@@ -112,4 +116,14 @@ implements PermissionsEvaluationService {
         throw _Exceptions.illegalArgument("PermissionsEvaluationPolicy '%s' not recognised", policy);
     }
 
+    private ApplicationFeatureIdTransformer applicationFeatureIdTransformerCached;
+    ApplicationFeatureIdTransformer applicationFeatureIdTransformer() {
+        if (applicationFeatureIdTransformerCached == null) {
+            applicationFeatureIdTransformerCached = applicationFeatureIdTransformers == null || applicationFeatureIdTransformers.isEmpty() ? new ApplicationFeatureIdTransformer.Identity() : applicationFeatureIdTransformers.get(0);
+        }
+        return applicationFeatureIdTransformerCached;
+    };
+
+    @Inject List<ApplicationFeatureIdTransformer> applicationFeatureIdTransformers;
+
 }