[java-idp-plugin-webauthn] 10/11: JWEBAUTHN-27 - Add basic authenticator policy

Phil Smart philip.smart at jisc.ac.uk
Fri Oct 18 17:13:35 UTC 2024


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=d7b322eecdbc0eab1ae2e79573bfeff2b4a8aeb7

commit d7b322eecdbc0eab1ae2e79573bfeff2b4a8aeb7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Oct 14 15:40:59 2024 +0100

    JWEBAUTHN-27 - Add basic authenticator policy
    
     - Add authentication credential chaining rule.
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-27
---
 .../policy/impl/ChainingCredentialPolicyRule.java  | 102 +++++++++++++++
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  11 +-
 .../authn/webauthn/conf/authn/webauthn.properties  |   8 +-
 .../impl/ChainingCredentialPolicyRuleTest.java     | 143 +++++++++++++++++++++
 4 files changed, 258 insertions(+), 6 deletions(-)

diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java
new file mode 100644
index 0000000..faf6e32
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed 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 net.shibboleth.idp.plugin.authn.webauthn.policy.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A {@link CredentialPolicy} implementation that verifies an authentication credential is acceptable based on a 
+ * chain of configured rules.
+ * 
+ * <p>Verification ends if any of the chained rules signals the authentication credential should be rejected.</p>
+ */
+public class ChainingCredentialPolicyRule extends AbstractCredentialPolicyRule {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ChainingCredentialPolicyRule.class);
+    
+    /** An ordered chain of authentication credentials policies.*/
+    private List<CredentialPolicy> credentialPolicyChain;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (credentialPolicyChain == null) {
+            throw new ComponentInitializationException("List of authentication credential policies can not be null");
+        } 
+    }
+    
+    /**
+     * Set the chain of policies that should be applied to the authentication credential.
+     * 
+     * @param chain the authentication credential policy chain to set.
+     */
+    public void setCredentialPolicyChain(@Nullable final List<CredentialPolicy> chain) {
+        checkSetterPreconditions();
+        if (chain != null) {
+            credentialPolicyChain = chain;
+        } else {
+            credentialPolicyChain = CollectionSupport.emptyList();
+        }
+    }    
+    /** {@inheritDoc} */
+    @Override
+    protected CredentialPolicyOutcome doEvaluate(
+            @Nonnull final CredentialRegistration credential, @Nonnull final ProfileRequestContext prc,
+            @Nonnull final WebAuthnAuthenticationContext webAuthnContext) {
+        
+        for (final CredentialPolicy policy : credentialPolicyChain) {
+            if (log.isTraceEnabled()) {
+                log.trace("Trying CredentialPolicy rule '{}' for credential '{}'", policy.getId(),
+                        credential.getCredentialIdBase64Url());
+            }
+            final CredentialPolicyOutcome outcome = policy.evaluate(credential, prc);
+            if (outcome  == CredentialPolicyOutcome.REJECT) {
+                if (log.isDebugEnabled()) {
+                    log.debug("CredentialPolicy rule '{}' rejected credential '{}'", policy.getId(), 
+                            credential.getCredentialIdBase64Url());
+                }
+                return CredentialPolicyOutcome.REJECT;
+            } else if (outcome == CredentialPolicyOutcome.IGNORE){
+                if (log.isDebugEnabled()) {
+                    log.debug("CredentialPolicy rule '{}' was ignored for credential '{}'", policy.getId(), 
+                            credential.getCredentialIdBase64Url());
+                }
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace("CredentialPolicy rule '{}' accepted credential '{}'", policy.getId(),  
+                            credential.getCredentialIdBase64Url());
+                }
+            }
+        }
+        return CredentialPolicyOutcome.ACCEPT;
+    }
+
+}
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
index b674f5d..6538206 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
@@ -143,14 +143,19 @@
     <bean id="CheckCredentialPolicy" parent="AbstractWebAuthnAuthenticationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.CheckCredentialPolicy"
         p:credentialRepository="#{getObject('shibboleth.authn.webauthn.CredentialRepositoryy') ?: getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"
-        p:credentialPolicy="#{getObject('%{idp.authn.webauthn.credential.policy:shibboleth.authn.webauthn.ChainedCredentialPolicies}')}"
+        p:credentialPolicy="#{getObject('%{idp.authn.webauthn.credential.policy:shibboleth.authn.webauthn.ChainedCredentialPolicy}')}"
         p:activationCondition="%{idp.authn.webauthn.credential.policy.enabled:false}"/>
+
+    <bean id="shibboleth.authn.webauthn.ChainedCredentialPolicy" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.ChainingCredentialPolicyRule"
+        p:credentialPolicyChain="#{getObject('%{idp.authn.webauthn.credential.policy.chainedlist:shibboleth.authn.webauthn.ChainedCredentialPolicyList}')}"/>
+    
         
-    <util:list id="shibboleth.authn.webauthn.ChainedCredentialPolicies">  
+    <util:list id="shibboleth.authn.webauthn.ChainedCredentialPolicyList">  
       
        <bean id="SecondFactorOnlyCredentialPolicyRule" parent="AbstractCredentialPolicyRule" scope="prototype"
             class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"
-            p:activationCondition="%{idp.authn.webauthn.registration.authenticator.policy.secondFactorOnly.enabled:true}"/>
+            p:activationCondition="%{idp.authn.webauthn.registration.credential.policy.secondFactorOnly.enabled:true}"/>
          
     </util:list>       
     
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
index 311a45e..b9b8c5c 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
@@ -72,9 +72,11 @@ idp.authn.webauthn.supportedPrincipals = \
 # Enable the credential/authenticator policy engine
 #idp.authn.webauthn.credential.policy.enabled = false
 # Set the credential policies to use, defaults to a chained set of policies
-#idp.authn.webauthn.credential.policy = shibboleth.authn.webauthn.ChainedCredentialPolicies
-#  When using the default chained policy, should we enable the 'second-factor only' credential rule
-#idp.authn.webauthn.registration.authenticator.policy.secondFactorOnly.enabled = true
+#idp.authn.webauthn.credential.policy = shibboleth.authn.webauthn.ChainedCredentialPolicy
+# When using the default chained policy, which policy list should we use?
+#idp.authn.webauthn.credential.policy.chainedlist = shibboleth.authn.webauthn.ChainedCredentialPoliciesList
+#  When using the default chained policy list, should we enable the 'second-factor only' credential rule
+#idp.authn.webauthn.credential.policy.secondFactorOnly.enabled = true
 
 # Audit
 #idp.authn.webauthn.audit.enabled = false
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java
new file mode 100644
index 0000000..49c1d39
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed 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 net.shibboleth.idp.plugin.authn.webauthn.policy.impl;
+
+import static org.testng.Assert.assertTrue;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
+import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
+import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy.CredentialPolicyOutcome;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Tests for {@link ChainingCredentialPolicyRule}.
+ */
+public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
+    
+    private ChainingCredentialPolicyRule policy;
+    
+    private CredentialRegistration registration;
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        policy = new ChainingCredentialPolicyRule();
+        registration = createCredentialRegistration();
+        policy.setId("ChainingCredentialPolicyRule");
+    }
+    
+    @Test
+    public void testAllowed() throws ComponentInitializationException {
+        policy.setCredentialPolicyChain(CollectionSupport.listOf(new CredentialPolicy() {
+            
+            @Override
+            public String getId() {
+                return "Allowed Rule";
+            }
+            
+            @Override
+            public CredentialPolicyOutcome evaluate(final CredentialRegistration cred, final ProfileRequestContext prc) {
+                return CredentialPolicyOutcome.ACCEPT;
+            }
+        }));
+        policy.initialize();
+        
+        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        assertTrue(accepted == CredentialPolicyOutcome.ACCEPT);
+        
+    }
+    
+    @Test
+    public void testDisallowed() throws ComponentInitializationException {
+        policy.setCredentialPolicyChain(CollectionSupport.listOf(new CredentialPolicy() {
+            
+            @Override
+            public String getId() {
+                return "Disallowed Rule";
+            }
+            
+            @Override
+            public CredentialPolicyOutcome evaluate(final CredentialRegistration cred, final ProfileRequestContext prc) {
+                return CredentialPolicyOutcome.REJECT;
+            }
+        }));
+        policy.initialize();
+        
+        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        assertTrue(accepted == CredentialPolicyOutcome.REJECT);
+        
+    }
+    
+    @Test
+    public void testDisallowedSecondInChain() throws ComponentInitializationException {
+        policy.setCredentialPolicyChain(CollectionSupport.listOf(new CredentialPolicy() {
+            
+            @Override
+            public String getId() {
+                return "Allow Rule";
+            }
+            
+            @Override
+            public CredentialPolicyOutcome evaluate(final CredentialRegistration cred, final ProfileRequestContext prc) {
+                return CredentialPolicyOutcome.ACCEPT;
+            }
+        }, new CredentialPolicy() {
+            
+            @Override
+            public String getId() {
+                return "Disallowed Rule";
+            }
+            
+            @Override
+            public CredentialPolicyOutcome evaluate(final CredentialRegistration cred, final ProfileRequestContext prc) {
+                return CredentialPolicyOutcome.REJECT;
+            }
+        }));
+        policy.initialize();
+        
+        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        assertTrue(accepted == CredentialPolicyOutcome.REJECT);
+        
+    }
+    
+    @Test
+    public void testIgnored() throws ComponentInitializationException {
+        policy.setCredentialPolicyChain(CollectionSupport.listOf(new CredentialPolicy() {
+            
+            @Override
+            public String getId() {
+                return "Ignored Rule";
+            }
+            
+            @Override
+            public CredentialPolicyOutcome evaluate(final CredentialRegistration cred, final ProfileRequestContext prc) {
+                return CredentialPolicyOutcome.IGNORE;
+            }
+        }));
+        policy.initialize();
+        
+        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        assertTrue(accepted == CredentialPolicyOutcome.ACCEPT);
+        
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list