[java-idp-plugin-duo] branch main updated: Unit test for passwordless opt-in management action, fixed an edge case.
Scott Cantor
cantor.2 at osu.edu
Wed Apr 17 14:03:17 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=11519c6b493928173ed6b885cdaf70397ad692d0
The following commit(s) were added to refs/heads/main by this push:
new 11519c6b Unit test for passwordless opt-in management action, fixed an edge case.
11519c6b is described below
commit 11519c6b493928173ed6b885cdaf70397ad692d0
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Apr 17 10:03:11 2024 -0400
Unit test for passwordless opt-in management action, fixed an edge case.
---
.../impl/PostValidatePasswordlessEvaluation.java | 88 +++++----
.../duo/impl/PasswordlessCookieManagerTest.java | 2 +-
.../PostValidatePasswordlessEvaluationTest.java | 213 +++++++++++++++++++++
3 files changed, 262 insertions(+), 41 deletions(-)
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluation.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluation.java
index 6dfcca55..8ffb356c 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluation.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluation.java
@@ -27,11 +27,9 @@ import org.slf4j.Logger;
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
import net.shibboleth.idp.plugin.authn.duo.PasswordlessCookieManager;
import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
@@ -85,19 +83,16 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
/** Condition governing "new" eligibility. */
@Nonnull private Predicate<ProfileRequestContext> passwordlessCondition;
-
+
+ /** Cookie manager to use. */
+ @NonnullAfterInit private PasswordlessCookieManager cookieManager;
+
/** Whether to require the authentication be cacheable to allow this. */
private boolean requireResultCacheable;
/** Whether to detect mismatches between cookie and current username. */
private boolean detectUsernameMismatch;
- /** Cookie manager to use. */
- @NonnullAfterInit private PasswordlessCookieManager cookieManager;
-
- /** Duo authentiction context. */
- @NonnullBeforeExec private DuoOIDCAuthenticationContext duoContext;
-
/** Constructor. */
public PostValidatePasswordlessEvaluation() {
passwordlessCondition = PredicateSupport.alwaysFalse();
@@ -125,6 +120,17 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
passwordlessCondition = Constraint.isNotNull(condition, "Passwordless eligibility condition cannot be null");
}
+
+ /**
+ * Sets {@link PasswordlessCookieManager} to use.
+ *
+ * @param manager cookie manager
+ */
+ public void setCookieManager(@Nullable final PasswordlessCookieManager manager) {
+ checkSetterPreconditions();
+
+ cookieManager = Constraint.isNotNull(manager, "PasswordlessCookieManager cannot be null");
+ }
/**
* Sets whether a non-cacheable result should force the condition to return false.
@@ -151,17 +157,6 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
checkSetterPreconditions();
detectUsernameMismatch = flag;
}
-
- /**
- * Sets {@link PasswordlessCookieManager} to use.
- *
- * @param manager cookie manager
- */
- public void setCookieManager(@Nullable final PasswordlessCookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "PasswordlessCookieManager cannot be null");
- }
/** {@inheritDoc} */
@Override
@@ -181,30 +176,12 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
return true;
}
-// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
-
- duoContext = authenticationContext.getSubcontext(DuoOIDCAuthenticationContext.class);
- if (duoContext == null) {
- log.error("{} No DuoAuthenticationContext available", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return;
- }
-
- final DuoOIDCIntegration integration = duoContext.getIntegration();
- if (integration == null) {
- log.error("{} No DuoOIDCIntegration available", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return;
- }
-
- final String username = duoContext.getUsername();
+ final String username = getUsername(profileRequestContext, authenticationContext);
if (username == null) {
- log.error("{} No username available", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
@@ -231,6 +208,10 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
log.info("{} Clearing existing guard cookie for original username '{}'", getLogPrefix(),
cookie);
cookieManager.clearCookie();
+ } else {
+ log.info("{} Ignoring username mismatch, left guard cookie for original username '{}'", getLogPrefix(),
+ cookie);
+ return;
}
}
@@ -242,7 +223,6 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
log.debug("{} User '{}' not eligible for passwordless", getLogPrefix(), username);
}
}
- // Checkstyle: CyclomaticComplexity ON
/** {@inheritDoc} */
@Override
@@ -254,4 +234,32 @@ public class PostValidatePasswordlessEvaluation extends AbstractAuthenticationAc
}
}
+ /**
+ * Gets the username associated with this Duo run.
+ *
+ * @param profileRequestContext profile request context
+ * @param authenticationContext authentication context
+ *
+ * @return username from current transaction or null
+ */
+ @Nullable private String getUsername(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+ final DuoOIDCAuthenticationContext duoContext =
+ authenticationContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ log.error("{} No DuoOIDCAuthenticationContext available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return null;
+ }
+
+ final String username = duoContext.getUsername();
+ if (username == null) {
+ log.error("{} No username available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return null;
+ }
+
+ return username;
+ }
+
}
\ No newline at end of file
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PasswordlessCookieManagerTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PasswordlessCookieManagerTest.java
index 361cded3..5f9439b6 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PasswordlessCookieManagerTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PasswordlessCookieManagerTest.java
@@ -51,10 +51,10 @@ public class PasswordlessCookieManagerTest {
protected PasswordlessCookieManager cookieManager;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
+ protected DataSealer dataSealer;
private Resource keystoreResource;
private Resource versionResource;
- private DataSealer dataSealer;
@BeforeClass
public void beforeClass() throws DataSealerException, ComponentInitializationException {
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluationTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluationTest.java
new file mode 100644
index 00000000..b5f62975
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/PostValidatePasswordlessEvaluationTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.duo.impl;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.net.UrlEscapers;
+
+import jakarta.servlet.http.Cookie;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.PasswordlessCookieManager;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.net.URISupport;
+import net.shibboleth.shared.security.DataSealerException;
+
+/**
+ * Unit test for {@link PostValidatePasswordlessEvaluation} action.
+ */
+ at SuppressWarnings("javadoc")
+public class PostValidatePasswordlessEvaluationTest extends PasswordlessCookieManagerTest {
+
+ private PostValidatePasswordlessEvaluation action;
+
+ private RequestContext src;
+ private ProfileRequestContext prc;
+ private AuthenticationContext ac;
+ private DuoOIDCAuthenticationContext dac;
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.setUp();
+
+ action = new PostValidatePasswordlessEvaluation();
+ action.setCookieManager(cookieManager);
+ action.setPasswordlessCondition(PredicateSupport.alwaysTrue());
+
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+ ac = prc.ensureSubcontext(AuthenticationContext.class);
+ dac = ac.ensureSubcontext(DuoOIDCAuthenticationContext.class);
+ }
+
+ @Test
+ public void testNoUsername() throws ComponentInitializationException {
+ action.initialize();
+
+ Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+
+ ac.removeSubcontext(DuoOIDCAuthenticationContext.class);
+
+ event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testNotCacheableCleared() throws ComponentInitializationException {
+ dac.setUsername("jdoe");
+ ac.setResultCacheable(false);
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Make sure cookie got cleared.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ assert cookie != null;
+ Assert.assertEquals(cookie.getMaxAge(), 0);
+ }
+
+ @Test
+ public void testNotCacheableNotCleared() throws ComponentInitializationException, DataSealerException {
+
+ String cookieValue = UrlEscapers.urlFormParameterEscaper().escape(dataSealer.wrap("jdoe"));
+ request.setCookies(new Cookie(COOKIE_NAME, cookieValue));
+
+ dac.setUsername("jdoe");
+ ac.setResultCacheable(false);
+
+ action.setRequireResultCacheable(false);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Make sure cookie was refreshed.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ assert cookie != null;
+ Assert.assertEquals(cookie.getName(), COOKIE_NAME);
+ cookieValue = URISupport.doURLDecode(cookie.getValue());
+ assert cookieValue != null;
+ Assert.assertEquals(dataSealer.unwrap(cookieValue), "jdoe");
+ }
+
+ @Test
+ public void testOptOutSkips() throws ComponentInitializationException {
+ dac.setUsername("jdoe");
+ request.setCookies(new Cookie(COOKIE_NAME, PasswordlessCookieManager.NEGATIVE_VALUE));
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Make sure cookie wasn't updated.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ Assert.assertNull(cookie);
+ }
+
+ @Test
+ public void testNoCookieEligible() throws ComponentInitializationException {
+ dac.setUsername("jdoe");
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, PostValidatePasswordlessEvaluation.PROMPT_USER_EVENT);
+ }
+
+ @Test
+ public void testNoCookieNotEligible() throws ComponentInitializationException {
+ dac.setUsername("jdoe");
+
+ action.setPasswordlessCondition(PredicateSupport.alwaysFalse());
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ }
+
+ @Test
+ public void testCookieMatches() throws ComponentInitializationException, DataSealerException {
+ dac.setUsername("jdoe");
+
+ String cookieValue = UrlEscapers.urlFormParameterEscaper().escape(dataSealer.wrap("jdoe"));
+ request.setCookies(new Cookie(COOKIE_NAME, cookieValue));
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Check that cookie was refreshed.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ assert cookie != null;
+ Assert.assertEquals(cookie.getName(), COOKIE_NAME);
+ cookieValue = URISupport.doURLDecode(cookie.getValue());
+ assert cookieValue != null;
+ Assert.assertEquals(dataSealer.unwrap(cookieValue), "jdoe");
+ }
+
+ @Test
+ public void testCookieMismatch() throws ComponentInitializationException, DataSealerException {
+ dac.setUsername("jdoe");
+
+ String cookieValue = UrlEscapers.urlFormParameterEscaper().escape(dataSealer.wrap("jdoe2"));
+ request.setCookies(new Cookie(COOKIE_NAME, cookieValue));
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, PostValidatePasswordlessEvaluation.PROMPT_USER_EVENT);
+
+ // Check that cookie was cleared.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ assert cookie != null;
+ Assert.assertEquals(cookie.getMaxAge(), 0);
+ }
+
+ @Test
+ public void testCookieMismatchIgnore() throws ComponentInitializationException, DataSealerException {
+ dac.setUsername("jdoe");
+
+ String cookieValue = UrlEscapers.urlFormParameterEscaper().escape(dataSealer.wrap("jdoe2"));
+ request.setCookies(new Cookie(COOKIE_NAME, cookieValue));
+
+ action.setDetectUsernameMismatch(false);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ // Check that cookie wasn't cleared.
+ final Cookie cookie = response.getCookie(COOKIE_NAME);
+ Assert.assertNull(cookie);
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list