[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