[java-idp-plugin-duo] branch main updated: JDUO-83 - Integrate a username collection view into existing Duo flow

Scott Cantor cantor.2 at osu.edu
Wed Jan 10 18:51:56 UTC 2024


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

scantor pushed a commit to branch main
in repository java-idp-plugin-duo.

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

The following commit(s) were added to refs/heads/main by this push:
     new 9960d32e JDUO-83 - Integrate a username collection view into existing Duo flow
9960d32e is described below

commit 9960d32e4fb1ab1969613bf801a98ce879a15291
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jan 10 13:51:50 2024 -0500

    JDUO-83 - Integrate a username collection view into existing Duo flow
    
    https://shibboleth.atlassian.net/browse/JDUO-83
    
    Reworked handling of username caching with precedence setting.
---
 .../duo/impl/CheckPasswordlessEnrollment.java      | 103 ++++++++++++++-------
 .../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml   |  30 +++---
 .../duo/impl/CheckPasswordlessEnrollmentTest.java  |   1 -
 3 files changed, 84 insertions(+), 50 deletions(-)

diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
index a0dd09f9..742e9510 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
@@ -14,6 +14,8 @@
 
 package net.shibboleth.idp.plugin.authn.duo.impl;
 
+import java.util.Collection;
+import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
@@ -36,6 +38,7 @@ import net.shibboleth.idp.session.IdPSession;
 import net.shibboleth.idp.session.context.SessionContext;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.net.CookieManager;
 import net.shibboleth.shared.net.URISupport;
@@ -93,8 +96,8 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
     /** Optional data sealer to use. */
     @Nullable private DataSealer dataSealer;
     
-    /** Whether to pull username from existing session or not. */
-    private boolean checkSession;
+    /** Order to pull username from. */
+    @Nonnull private List<String> precedence;
     
     /** Generic hook for remapping username. */
     @Nullable private BiFunction<ProfileRequestContext,String,String> duoUsernameRemappingStrategy; 
@@ -115,6 +118,8 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
             
         usernameFieldName = "j_username";
         ssoBypassFieldName = "donotcache";
+
+        precedence = CollectionSupport.listOf("form", "session", "cookie");
     }
     
     /**
@@ -199,16 +204,16 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
     }
     
     /**
-     * Sets whether tp pull username from existing session as a fallback.
+     * Sets the precedence rules to use in populating the username.
      * 
-     * <p>Defaults to false.</p>
+     * <p>Valid tokens are "form", "session", and "cookie" and that is the default order.</p>
      * 
-     * @param flag flag to set
+     * @param order precedence to use
      */
-    public void setCheckSession(final boolean flag) {
+    public void setPrecedence(@Nonnull final Collection<String> order) {
         checkSetterPreconditions();
 
-        checkSession = flag;
+        precedence = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(order));
     }
 
     /**
@@ -241,42 +246,68 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
         return true;
     }
 
-// Checkstyle: CyclomaticComplexity OFF
+// Checkstyle: CyclomaticComplexity|MethodLength OFF
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
         boolean usernameChanged = false;
+        boolean exitLoop = false;
+
+        String username;
         
-        String username = getUsernameFromForm(profileRequestContext, authenticationContext);
-        if (username != null) {
-            if (!username.equals(passwordlessContext.getUsername()) ) {
-                log.debug("{} Populating username '{}' from form submission into Duo passwordless context",
-                        getLogPrefix(), username);
-                passwordlessContext.setUsername(username);
-                usernameChanged = true;
+        for (final String source : precedence) {
+            switch (source) {
+                case "form":
+                    username = getUsernameFromForm(profileRequestContext, authenticationContext);
+                    if (username != null) {
+                        if (!username.equals(passwordlessContext.getUsername())) {
+                            log.debug("{} Populating username '{}' from form submission into DuoPasswordlessContext",
+                                    getLogPrefix(), username);
+                            passwordlessContext.setUsername(username);
+                            usernameChanged = true;
+                        }
+                        exitLoop = true;
+                    }
+                    break;
+                    
+                case "session":
+                    username = getUsernameFromSession(profileRequestContext, authenticationContext);
+                    if (username != null) {
+                        if (!username.equals(passwordlessContext.getUsername())) {
+                            log.debug("{} Populating username '{}' from session into DuoPasswordlessContext",
+                                    getLogPrefix(), username);
+                            passwordlessContext.setUsername(username);
+                            usernameChanged = true;
+                        }
+                        exitLoop = true;
+                    }
+                    break;
+                    
+                case "cookie":
+                    username = getUsernameFromCookie(profileRequestContext);
+                    if (username != null) {
+                        if (!username.equals(passwordlessContext.getUsername())) {
+                            log.debug("{} Populating cached username '{}' from cookie into DuoPasswordlessContext",
+                                    getLogPrefix(), username);
+                            passwordlessContext.setUsername(username);
+                            usernameChanged = true;
+                        }
+                        exitLoop = true;
+                    }
+                    break;
+                    
+                default:
+                    log.warn("{} Unsupported precedence value for username population: {}", getLogPrefix(), source);
+                    break;
             }
-        } else {
-            username = getUsernameFromCookie(profileRequestContext);
-            if (username != null) {
-                if (!username.equals(passwordlessContext.getUsername()) ) {
-                    log.debug("{} Populating cached username '{}' from cookie into Duo passwordless context",
-                            getLogPrefix(), username);
-                    passwordlessContext.setUsername(username);
-                    usernameChanged = true;
-                }
-            } else {
-                username = getUsernameFromSession(profileRequestContext, authenticationContext);
-                if (username != null && !username.equals(passwordlessContext.getUsername())) {
-                    log.debug("{} Populating username '{}' from session into Duo passwordless context", getLogPrefix(),
-                            username);
-                    passwordlessContext.setUsername(username);
-                    usernameChanged = true;
-                }
+            
+            if (exitLoop) {
+                break;
             }
         }
-
+        
         if (passwordlessContext.getUsername() == null) {
             passwordlessContext.setEnrolled(false);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.UNKNOWN_USERNAME);
@@ -289,14 +320,14 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
             log.debug("{} Username '{}' found to be {} of passwordless attempt", getLogPrefix(),
                     passwordlessContext.getUsername(), passwordlessContext.isEnrolled() ? "capable" : "incapable");
         } else {
-            log.debug("{} Username not available, leaving DuoPasswordlessContext unchanged", getLogPrefix());
+            log.debug("{} Username not updated, leaving DuoPasswordlessContext unchanged", getLogPrefix());
         }
         
         if (!passwordlessContext.isEnrolled()) {
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.REQUEST_UNSUPPORTED);
         }
     }
-// Checkstyle: CyclomaticComplexity ON
+// Checkstyle: CyclomaticComplexity|MethodLength ON
     
     /**
      * Gets the username from a form submission.
@@ -370,7 +401,7 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
     @Nullable private String getUsernameFromSession(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        if (checkSession && !authenticationContext.getActiveResults().isEmpty()) {
+        if (!authenticationContext.getActiveResults().isEmpty()) {
             final SessionContext sessionContext = profileRequestContext.getSubcontext(SessionContext.class);
             if (sessionContext != null) {
                 final IdPSession idpSession = sessionContext.getIdPSession();
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 522bbd60..eac9c29a 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
@@ -147,19 +147,23 @@
 
     <!-- Passwordless beans  -->
     <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('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.duo.oidc.usernameFieldName:j_username}'.trim()}"
-        p:checkSession="%{idp.authn.usernameFromSession:false}"
-        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')}"
-        p:duoUsernameRemappingStrategy="#{getObject('shibboleth.authnn.DuoOIDC.UsernameRemappingStrategy')}" />
+            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('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.duo.oidc.usernameFieldName:j_username}'.trim()}"
+            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')}"
+            p:duoUsernameRemappingStrategy="#{getObject('shibboleth.authnn.DuoOIDC.UsernameRemappingStrategy')}">
+        <property name="precedence">
+            <bean parent="shibboleth.CommaDelimStringArray"
+                c:_0="#{'%{idp.authn.usernamePrecedence:form,session,cookie}'.trim()}" />
+        </property>
+    </bean>
     
     <!-- Duo OIDC beans -->
     <bean id="PopulateDuoAuthenticationContext" scope="prototype"
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java
index 24c5c69a..f389d7b5 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java
@@ -109,7 +109,6 @@ public class CheckPasswordlessEnrollmentTest extends AbstractDuoActionTest {
         action.setCookieName(COOKIE_NAME);
         action.setHttpServletRequestSupplier(new ThreadLocalHttpServletRequestSupplier());
         action.setHttpServletResponseSupplier(new ThreadLocalHttpServletResponseSupplier());
-        action.setCheckSession(true);
         action.initialize();
     }
     

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


More information about the commits mailing list