[java-idp-plugin-duo] branch dev/JDUO-82 updated: Move admin beans into global context, and clean up enrollment condition.

Scott Cantor cantor.2 at osu.edu
Mon Jan 8 16:46:34 UTC 2024


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

scantor pushed a commit to branch dev/JDUO-82
in repository java-idp-plugin-duo.

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

The following commit(s) were added to refs/heads/dev/JDUO-82 by this push:
     new ec564bd6 Move admin beans into global context, and clean up enrollment condition.
ec564bd6 is described below

commit ec564bd68dcd2c55f057ec878ee767f7c18b6ed1
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 8 11:46:31 2024 -0500

    Move admin beans into global context, and clean up enrollment condition.
---
 .../shibboleth/idp/authn/duo/DuoIntegration.java   |  6 +++-
 ...n.java => PasswordlessEnrollmentCondition.java} | 35 ++++++++++++++-------
 .../META-INF/net.shibboleth.idp/postconfig.xml     | 25 +++++++++++++++
 .../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml   | 36 +++-------------------
 4 files changed, 59 insertions(+), 43 deletions(-)

diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java b/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
index 2248d862..da054dcc 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
@@ -15,13 +15,17 @@
 package net.shibboleth.idp.authn.duo;
 
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 /**
- * Interface to a particular Duo integration point.
+ * Interface to a particular Duo AdminAPI or AuthAPI integration point.
+ * 
+ * <p>This was superseded for "standard" authentication by the {@link DuoOIDCIntegration}
+ * interface but remains for other use cases.</p>
  * 
  * @since 3.3.0
  */
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
similarity index 75%
rename from idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java
rename to idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
index 6071abf3..16f77b25 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
@@ -18,6 +18,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiPredicate;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -41,11 +42,11 @@ import net.shibboleth.shared.primitive.StringSupport;
  * <p>The second parameter is the username to evaluate.</p>
  */
 
-public class PasswordlessEnrolmentCondition extends AbstractInitializableComponent
+public class PasswordlessEnrollmentCondition extends AbstractInitializableComponent
         implements BiPredicate<ProfileRequestContext,String> {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(PasswordlessEnrolmentCondition.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(PasswordlessEnrollmentCondition.class);
     
     /** The admin client used to access the DuoAdmin API.*/
     @NonnullAfterInit private DuoAdminClient adminClient;
@@ -54,7 +55,7 @@ public class PasswordlessEnrolmentCondition extends AbstractInitializableCompone
     @Nonnull @NonnullElements private Set<String> allowedLabels;
     
     /** Constructor. */
-    public PasswordlessEnrolmentCondition() {
+    public PasswordlessEnrollmentCondition() {
         allowedLabels = CollectionSupport.emptySet();
     }
     
@@ -103,23 +104,35 @@ public class PasswordlessEnrolmentCondition extends AbstractInitializableCompone
             final Boolean enrolled = response.isEnrolled();
             
             if (enrolled == null || !enrolled) {
-                log.trace("User '{}' not enrolled", response.getUserId());
+                log.info("User '{}' not enrolled", response.getUsername());
                 return false;
             }
             
             final List<WebAuthnCredential> creds = response.getWebAuthnCredentials();
             if (creds.isEmpty()) {
-                log.trace("User '{}' has no WebAuthn credentials", response.getUserId());
+                log.info("User '{}' has no WebAuthn credentials", response.getUsername());
                 return false;
             }
             
-            log.trace("User '{}' is enrolled with {} WebAuthn credential(s)", response.getUserId(),
-                    response.getWebAuthnCredentials().size());
+            if (allowedLabels.isEmpty()) {
+                log.debug("User '{}' is enrolled with {} WebAuthn credential(s)", response.getUsername(),
+                        response.getWebAuthnCredentials().size());
+                return true;
+            }
+            
+            final Set<String> enrolledLabels = creds.stream()
+                    .map(WebAuthnCredential::getLabel)
+                    .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+            for (final String label : allowedLabels) {
+                if (enrolledLabels.contains(label)) {
+                    log.debug("User '{}' has a qualifying WebAuthn credential: '{}'", response.getUsername(), label);
+                    return true;
+                }
+            }
             
-            final boolean allowed = creds.stream().map(WebAuthnCredential::getLabel).anyMatch(allowedLabels::contains);
-            log.debug("User '{}' {} a qualifying WebAuthn credential", response.getUserId(),
-                    allowed ? "has" : "does not have");
-            return allowed;
+            log.info("User '{}' has no acceptable WebAuthn credential, enrolled credentials: {}",
+                    response.getUsername(), enrolledLabels);
+            return false;
             
         } catch (final DuoException e) {
             log.warn("Duo AdminAPI request failed, denying passwordless for '{}'", username, e);
diff --git a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 08bb6656..bed68aa2 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -52,5 +52,30 @@
     <bean id="shibboleth.DuoOIDCAuthnController"
         class="net.shibboleth.idp.plugin.authn.duo.impl.DuoOIDCAuthnController" />
 
+    <!-- Default Duo Admin API Integration for IdP-wide use. -->
+    <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" lazy-init="true"
+        class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
+        p:APIHost="%{idp.duo.oidc.admin.apiHost:%{idp.duo.oidc.apiHost:none}}"
+        p:integrationKey="%{idp.duo.oidc.admin.integrationKey:none}"
+        p:secretKey="%{idp.duo.oidc.admin.secretKey:none}"/>
+    <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant" lazy-init="true"
+        c:target-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" />
+
+    <bean id="shibboleth.authn.DuoOIDC.DefaultAdminClient" class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoAdminClient"
+        lazy-init="true"
+        p:objectMapper-ref="shibboleth.JSONObjectMapper"
+        p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.Admin.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+        p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}"
+        p:adminDuoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegrationStrategy"/>
+
+    <!-- Default passwordless condition that uses the admin API -->
+    <bean id="shibboleth.authn.DuoOIDC.Passwordless.DefaultCondition" lazy-init="true"
+            class="net.shibboleth.idp.plugin.authn.duo.PasswordlessEnrollmentCondition"
+            p:duoAdminClient="#{getObject('shibboleth.authn.DuoOIDC.AdminClient') ?: getObject('shibboleth.authn.DuoOIDC.DefaultAdminClient')}">
+        <property name="allowedLabels">
+            <bean parent="shibboleth.CommaDelimStringArray"
+                c:_0="#{'%{idp.duo.oidc.passwordless.allowedLabels:}'.trim()}" />
+        </property>
+    </bean>
 
 </beans>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index c93bda6f..d5fa518a 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -80,15 +80,6 @@
     <bean id="shibboleth.authn.DuoOIDC.Passwordless.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant"
         c:target="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.DuoIntegration')}" />
         
-    <!-- Default Duo Admin API Integration -->
-    <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" lazy-init="false"
-        class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
-        p:APIHost="%{idp.duo.oidc.admin.apiHost:%{idp.duo.oidc.apiHost:none}}"
-        p:integrationKey="%{idp.duo.oidc.admin.integrationKey:none}"
-        p:secretKey="%{idp.duo.oidc.admin.secretKey:none}"/>
-    <bean id="shibboleth.authn.DuoOIDC.Admin.DuoAdminIntegrationStrategy" parent="shibboleth.Functions.Constant"
-        c:target-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" />
-
     <!-- Default "optional" non-browser integration. -->
     <bean id="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegration" lazy-init="false"
         class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
@@ -158,34 +149,17 @@
     <bean id="CheckPasswordlessEnrollment"
         class="net.shibboleth.idp.plugin.authn.duo.impl.CheckPasswordlessEnrollment" scope="prototype"
         p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-        p:passwordlessCondition="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.Condition') ?: getObject('DefaultPasswordlessCondition')}"
+        p:passwordlessCondition="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.Condition') ?: getObject('shibboleth.authn.DuoOIDC.Passwordless.DefaultCondition')}"
         p:dataSealer="#{'%{idp.authn.usernameCookieName:}'.trim().isEmpty() ? null : getObject('shibboleth.DataSealer')}"
         p:cookieManager="#{'%{idp.authn.usernameCookieName:}'.trim().isEmpty() ? null : getObject('shibboleth.PersistentCookieManager')}"
         p:cookieName="%{idp.authn.usernameCookieName:}"
-        p:usernameFieldName="#{'%{idp.authn.oidc.usernameFieldName:j_username}'.trim()}"
+        p:usernameFieldName="#{'%{idp.duo.oidc.usernameFieldName:j_username}'.trim()}"
         p:checkSession="%{idp.authn.usernameFromSession:false}"
-        p:lowercase="%{idp.authn.oidc.lowercase:false}"
-        p:uppercase="%{idp.authn.oidc.uppercase:false}"
-        p:trim="%{idp.authn.oidc.trim:true}"
+        p:lowercase="%{idp.duo.oidc.lowercase:false}"
+        p:uppercase="%{idp.duo.oidc.uppercase:false}"
+        p:trim="%{idp.duo.oidc.trim:true}"
         p:transforms="#{getObject('shibboleth.authn.DuoOIDC.Transforms')}" />
     
-    <!-- Default passwordless condition that uses the admin API -->
-    <bean id="DefaultPasswordlessCondition"
-            class="net.shibboleth.idp.plugin.authn.duo.PasswordlessEnrolmentCondition"
-            p:duoAdminClient="#{getObject('shibboleth.authn.DuoOIDC.AdminClient') ?: getObject('DefaultDuoAdminClient')}">
-        <property name="allowedLabels">
-            <bean parent="shibboleth.CommaDelimStringArray"
-                c:_0="#{'%{idp.duo.oidc.passwordless.allowedLabels:}'.trim()}" />
-        </property>
-    </bean>
-                
-    <!-- Singleton, shared, Duo Admin API Beans -->
-    <bean id="DefaultDuoAdminClient" class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoAdminClient"
-        p:objectMapper-ref="shibboleth.JSONObjectMapper"
-        p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.Admin.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
-        p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}"
-        p:adminDuoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.Admin.DuoAdminIntegrationStrategy"/>
-            
     <!-- Duo OIDC beans -->
     <bean id="PopulateDuoAuthenticationContext" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.PopulateDuoAuthenticationContext"

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


More information about the commits mailing list