[java-idp-plugin-duo] 01/02: Move test from oidc-common into Duo and add further unit tests
Codeberg
noreply at shibboleth.net
Wed Sep 23 14:54:32 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-idp-plugin-duo.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-duo/commit/a0f3192e1f5aa2867674a81f439a43429cacd3fc
commit a0f3192e1f5aa2867674a81f439a43429cacd3fc
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Sep 23 15:29:48 2026 +0100
Move test from oidc-common into Duo and add further unit tests
- DuoAuthenticationTimeClaimsValidator is a variant of the
AuthenticationTimeClaimsValidator in commons, as the semantics have
changed a bit. So I moved in the tests for
AuthenticationTimeClaimsValidator from commons and modified them
slightly.
---
.../authn/duo/AuthenticationRequestOptions.java | 2 +-
...uthenticationRequestTimeLookupStrategyTest.java | 77 +++++++
.../DuoAuthenticationTimeClaimsValidatorTest.java | 224 +++++++++++++++++++++
3 files changed, 302 insertions(+), 1 deletion(-)
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestOptions.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestOptions.java
index 5b50edf9..a2fde851 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestOptions.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestOptions.java
@@ -49,7 +49,7 @@ public class AuthenticationRequestOptions {
*
* @return this options
*/
- public AuthenticationRequestOptions setUsername(@Nullable final String value) {
+ @Nonnull public AuthenticationRequestOptions setUsername(@Nullable final String value) {
username = value;
return this;
}
diff --git a/idp-duo-api/src/test/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestTimeLookupStrategyTest.java b/idp-duo-api/src/test/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestTimeLookupStrategyTest.java
new file mode 100644
index 00000000..f746a4a8
--- /dev/null
+++ b/idp-duo-api/src/test/java/net/shibboleth/idp/plugin/authn/duo/AuthenticationRequestTimeLookupStrategyTest.java
@@ -0,0 +1,77 @@
+
+package net.shibboleth.idp.plugin.authn.duo;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/**
+ * Tests for AuthenticationRequestTimeLookupStrategy.
+ */
+public class AuthenticationRequestTimeLookupStrategyTest {
+
+ private ProfileRequestContext prc;
+
+ private AuthenticationContext authnContext;
+
+ private DuoOIDCAuthenticationContext duoCtx;
+
+ private DefaultDuoOIDCIntegration integ;
+
+ @BeforeMethod
+ public void setup() {
+ prc = new ProfileRequestContext();
+ authnContext = new AuthenticationContext();
+ duoCtx = authnContext.ensureSubcontext(DuoOIDCAuthenticationContext.class);
+ prc.addSubcontext(authnContext);
+ integ = new DefaultDuoOIDCIntegration();
+ integ.setAPIHost("host.com");
+ integ.setAuthorizeEndpoint("https://host.com/authz");
+ integ.setTokenEndpoint("https://host.com/token");
+ integ.setHealthCheckEndpoint("https://host.com/health");
+ integ.setClientId("DIU6GEFWG5LIUBVV2M3P");
+ integ.setRegisteredRedirectURI("http://localhost/");
+ integ.setSecretKey("rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh");
+ duoCtx.setIntegration(integ);
+ }
+
+ @Test
+ public void testRequestTimeWithClockSkew() {
+ final Duration skew = Duration.ofSeconds(30);
+ final Instant requestTime = Instant.now();
+ duoCtx.setAuthnRequestTime(requestTime);
+ final AuthenticationRequestTimeLookupStrategy strategy = new AuthenticationRequestTimeLookupStrategy(skew);
+ Assert.assertEquals(strategy.apply(prc), requestTime.minus(skew));
+ }
+
+ @Test
+ public void testRequestTimeWithNoClockSkew() {
+ final Duration skew = Duration.ofSeconds(0);
+ final Instant requestTime = Instant.now();
+ duoCtx.setAuthnRequestTime(requestTime);
+ final AuthenticationRequestTimeLookupStrategy strategy = new AuthenticationRequestTimeLookupStrategy(skew);
+ Assert.assertEquals(strategy.apply(prc), requestTime);
+ }
+
+ @Test
+ public void testNoAuthenticationRequestTimeSet_UseDefault() {
+ final Duration skew = Duration.ofSeconds(30);
+ final AuthenticationRequestTimeLookupStrategy strategy = new AuthenticationRequestTimeLookupStrategy(skew);
+
+ final Instant before = Instant.now().minus(skew);
+ final Instant requestTime = strategy.apply(prc);
+ final Instant after = Instant.now().minus(skew);
+
+ Assert.assertFalse(requestTime.isBefore(before));
+ Assert.assertFalse(requestTime.isAfter(after));
+ }
+
+
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthenticationTimeClaimsValidatorTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthenticationTimeClaimsValidatorTest.java
new file mode 100644
index 00000000..f7700104
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthenticationTimeClaimsValidatorTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 java.time.Duration;
+import java.time.Instant;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.security.jwt.claims.impl.AuthenticationTimeClaimsValidator;
+import net.shibboleth.oidc.security.jwt.claims.impl.IDTokenClaims;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/** Test for the {@link AuthenticationTimeClaimsValidator}. */
+ at SuppressWarnings(value={"javadoc","null"})
+public class DuoAuthenticationTimeClaimsValidatorTest {
+
+ /** The validator to test.*/
+ private DuoAuthenticationTimeClaimsValidator validator;
+
+ /** The request context.*/
+ protected RequestContext src;
+
+ /** The profile request context.*/
+ protected ProfileRequestContext prc;
+
+ /** The authentication context to add to the profile request context.*/
+ protected AuthenticationContext ac;
+
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ validator = new DuoAuthenticationTimeClaimsValidator();
+ src = new RequestContextBuilder().buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+ ac = new AuthenticationContext();
+
+ prc.addSubcontext(ac);
+ }
+
+ @Test
+ public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.setClockSkew(Duration.ofSeconds(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ /**
+ * Check that a null outcome from the authn lifetime strategy disables the check. If it does not an exception
+ * would be thrown.
+ *
+ * @throws JWTValidationException on error
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void doValidateTest_AuthLifetimeDisablesTest()
+ throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().minus(Duration.ofHours(1))
+ .getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetimeLookupStrategy(prc -> null);
+ validator.setClockSkew(Duration.ofSeconds(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTest_UseAuthnStrategy() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetimeLookupStrategy(prc -> Duration.ofMinutes(1));
+ validator.setClockSkew(Duration.ofSeconds(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTest_DefaultAuthnTime() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setClockSkew(Duration.ofSeconds(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateTestWithinLifetime() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.setClockSkew(Duration.ofSeconds(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doValidateNoClaimButNotActive() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setActivationCondition((prc,claims) -> false);
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doRejectedTestCanNotParseDate() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),"not-a-date").build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doRejectedTest() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofMinutes(10)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.setClockSkew(Duration.ofMinutes(0));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doRejectedInTheFuture() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().plus(Duration.ofSeconds(30)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ //set clockskew to 10 seconds, and JWT is 30 seconds in the future, so should throw
+ validator.setClockSkew(Duration.ofSeconds(10));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test
+ public void doRejectedInTheFutureButInsideSkew() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().plus(Duration.ofSeconds(30)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ //set clockskew to 60 seconds, and JWT is 30 seconds in the future, so should be fine
+ validator.setClockSkew(Duration.ofSeconds(60));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doRejectedNoClaim() throws JWTValidationException, ComponentInitializationException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+ validator.setId("test-validator");
+ validator.setAuthnLifetime(Duration.ofMinutes(1));
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+ @Test(expectedExceptions = JWTValidationException.class)
+ public void doRejectedForceAuthenticationRequestButOldAuthnTime()
+ throws JWTValidationException, ComponentInitializationException {
+ // Authentication occurs 10 second before the authn request was sent
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().minus(Duration.ofSeconds(10)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ // returning 0 seconds 'signals' forced authentication
+ validator.setAuthnLifetime(Duration.ofSeconds(0));
+ validator.setAuthnRequestTimeLookupStrategy(prc -> Instant.now());
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+ @Test
+ public void doValidForceAuthenticationRequest()
+ throws JWTValidationException, ComponentInitializationException {
+ // Authentication occurs 10 second after the authn request was sent
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+ IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),
+ Instant.now().plus(Duration.ofSeconds(10)).getEpochSecond()).build();
+ validator.setId("test-validator");
+ // returning 0 seconds 'signals' forced authentication
+ validator.setAuthnLifetime(Duration.ofSeconds(0));
+ validator.setAuthnRequestTimeLookupStrategy(prc -> Instant.now());
+ validator.initialize();
+ validator.validate(claimsSet, prc);
+ }
+
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list