[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