[java-idp-plugin-duo] branch main updated: JDUO-28 - Move JWT Claims Validation to OIDC commons
Phil Smart
philip.smart at jisc.ac.uk
Fri Jan 29 14:16:27 UTC 2021
This is an automated email from the git hooks/post-receive script.
philsmart 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=ffd8b62a2e6ecf5b1e351559e2059b45c34dd716
The following commit(s) were added to refs/heads/main by this push:
new ffd8b62 JDUO-28 - Move JWT Claims Validation to OIDC commons
ffd8b62 is described below
commit ffd8b62a2e6ecf5b1e351559e2059b45c34dd716
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 29 11:45:51 2021 +0000
JDUO-28 - Move JWT Claims Validation to OIDC commons
- Moved the JWT ID Token claims validation to oidc-commons.
- Configured the set of validators.
https://issues.shibboleth.net/jira/browse/JDUO-28
---
idp-duo-api/pom.xml | 7 +-
.../authn/duo/DefaultDuoOIDCIntegration.java | 2 +-
.../idp/plugin/authn/duo/DuoOIDCAuthAPI.java | 55 ----
.../authn/duo/DuoOIDCClientCapabilities.java | 2 +-
...DCClientCapabilities.java => package-info.java} | 18 +-
idp-duo-impl/pom.xml | 358 +++++++++++----------
.../authn/duo/impl/BasicJWTClaimsVerifier.java | 266 ---------------
.../duo/impl/DefaultDuoOIDCClientRegistry.java | 3 +-
.../duo/impl/DuoAudienceClaimLookupStrategy.java | 56 ++++
.../duo/impl/DuoIssuerClaimLookupStrategy.java | 75 +++++
.../duo/impl/DuoNonceClaimLookupStrategy.java | 50 +++
.../duo/impl/DuoUsernameClaimLookupStrategy.java | 50 +++
.../impl/DynamicDuoContextJWTClaimsVerifier.java | 344 --------------------
.../plugin/authn/duo/impl/ValidateTokenClaims.java | 30 +-
.../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml | 81 ++++-
.../authn/duo/impl/BasicJWTClaimsVerifierTest.java | 251 ---------------
.../impl/DuoAudienceClaimLookupStrategyTest.java | 52 +++
.../duo/impl/DuoIssuerClaimLookupStrategyTest.java | 54 ++++
.../duo/impl/DuoNonceClaimLookupStrategyTest.java | 53 +++
.../impl/DuoUsernameClaimLookupStrategyTest.java | 56 ++++
.../DynamicDuoContextJWTClaimsVerifierTest.java | 342 --------------------
.../authn/duo/impl/ValidateTokenClaimsTest.java | 28 +-
idp-duo-nimbus-client-impl/pom.xml | 12 +-
pom.xml | 34 +-
24 files changed, 754 insertions(+), 1525 deletions(-)
diff --git a/idp-duo-api/pom.xml b/idp-duo-api/pom.xml
index f0149e6..7373bc6 100644
--- a/idp-duo-api/pom.xml
+++ b/idp-duo-api/pom.xml
@@ -18,14 +18,13 @@
</properties>
- <dependencies>
- <!-- Compile time dependencies -->
+ <dependencies>
+ <!-- Provided dependencies -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
- <scope>compile</scope>
+ <scope>provided</scope>
</dependency>
- <!-- Provided dependencies -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
index 07c8c36..ef9f0c8 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
@@ -251,7 +251,7 @@ public class DefaultDuoOIDCIntegration extends AbstractInitializableComponent im
/** {@inheritDoc} */
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
+ final StringBuilder builder = new StringBuilder();
builder.append("DefaultDuoOIDCIntegration [apiHost=");
builder.append(apiHost);
builder.append(", clientId=");
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
index e0d6f7d..c5ea760 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
@@ -20,8 +20,6 @@ package net.shibboleth.idp.plugin.authn.duo;
import javax.annotation.Nonnull;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
* Constants defined in the Duo OIDC Auth API.
@@ -56,59 +54,6 @@ public final class DuoOIDCAuthAPI {
/** The name of the JSON result status message property.*/
@Nonnull @NotEmpty public static final String DUO_AUTH_RESULT_STATUS_MSG_JSON_OBJECT = "status_msg";
- //TODO MOVE these to a registered OIDC claims set in commons?
-
- //There are others.
- /** Core ID Token Claims, see OpenID Connect core 1.0 section 2.*/
- @Nonnull public enum OIDC_ID_TOKEN_CLAIMS{
-
- /**Issuer Identifier for the Issuer of the response.*/
- ISSUER_CLAIM ("iss"),
-
- /** Subject Identifier. A locally unique and never reassigned identifier within the
- * Issuer for the End-User.*/
- SUBJECT_CLAIM ("sub"),
-
- /** Audience(s) that this ID Token is intended for.*/
- AUDIENCE_CLAIM ("aud"),
-
- /** Expiration time on or after which the ID Token MUST NOT be accepted for processing.*/
- EXPIRATION_TIME_CLAIM ("exp"),
-
- /**The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.*/
- NOT_BEFORE_CLAIM ("nbf"),
-
- /** Time at which the JWT was issued.*/
- ISSUED_AT_CLAIM ("iat"),
-
- /** Time when the End-User authentication occurred.*/
- AUTHENTICATION_TIME("auth_time"),
-
- /** String value used to associate a Client session with an ID Token, and to mitigate replay attacks.*/
- NONCE("nonce"),
-
- /** Authentication Context Class Reference.*/
- AUTHENTICATION_CONTEXT_CLASS_REFERENCE("acr"),
-
- /** Authentication Methods References.*/
- AUTHENTICATION_METHODS_REFERENCES("amr"),
-
- /** Authorized party - the party to which the ID Token was issued.*/
- AUTHORIZED_PARTY("azp"),
-
- /** The "jti" (JWT ID) claim provides a unique identifier for the JWT.*/
- JWT_ID_CLAIM ("jti");
-
- @Nonnull @NotEmpty private final String claimName;
-
- private OIDC_ID_TOKEN_CLAIMS(@Nonnull @NotEmpty final String name) {
- claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
- }
-
- @Nonnull public String getClaimName() {
- return claimName;
- }
- }
/** Constructor. */
private DuoOIDCAuthAPI() {
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
index 9f13ac1..78b0a78 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
@@ -26,7 +26,7 @@ public interface DuoOIDCClientCapabilities {
* <p>Does this client support the OIDC nonce parameter.</p>
*
* <p>If the client does support a nonce, it <b>must</b> be included by the client in the authorisation request URL,
- * where it should then be returned by the OP in the id_token as part of the 2FA result.</p>
+ * where it must then be returned by the OP in the id_token as part of the 2FA result.</p>
*
* @return true iff the client supports the nonce parameter, false otherwise.
*/
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
similarity index 62%
copy from idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
copy to idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
index 9f13ac1..0273347 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
@@ -15,21 +15,5 @@
* limitations under the License.
*/
+/** Duo OIDC 2FA login flow API classes. */
package net.shibboleth.idp.plugin.authn.duo;
-
-/**
- * Exposes capabilities of a {@link DuoOIDCClient}.
- */
-public interface DuoOIDCClientCapabilities {
-
- /**
- * <p>Does this client support the OIDC nonce parameter.</p>
- *
- * <p>If the client does support a nonce, it <b>must</b> be included by the client in the authorisation request URL,
- * where it should then be returned by the OP in the id_token as part of the 2FA result.</p>
- *
- * @return true iff the client supports the nonce parameter, false otherwise.
- */
- boolean isSupportsNonce();
-
-}
diff --git a/idp-duo-impl/pom.xml b/idp-duo-impl/pom.xml
index df07ce6..eb3d718 100644
--- a/idp-duo-impl/pom.xml
+++ b/idp-duo-impl/pom.xml
@@ -1,186 +1,196 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>net.shibboleth.idp.plugin</groupId>
- <artifactId>idp-plugin-duo-parent</artifactId>
- <version>0.0.3-SNAPSHOT</version>
- </parent>
+ <parent>
+ <groupId>net.shibboleth.idp.plugin</groupId>
+ <artifactId>idp-plugin-duo-parent</artifactId>
+ <version>0.0.3-SNAPSHOT</version>
+ </parent>
- <name>Shibboleth IdP :: Plugins :: Duo 2FA Login Flow Impl</name>
- <description>IdP Duo OIDC 2FA plugin implementation.</description>
- <artifactId>idp-plugin-duo-impl</artifactId>
- <packaging>jar</packaging>
+ <name>Shibboleth IdP :: Plugins :: Duo 2FA Login Flow Impl</name>
+ <description>IdP Duo OIDC 2FA plugin implementation.</description>
+ <artifactId>idp-plugin-duo-impl</artifactId>
+ <packaging>jar</packaging>
- <properties>
- <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
- <automatic.module.name>net.shibboleth.idp.plugin.duo.impl</automatic.module.name>
- <apache.commons.lang.version>3.10</apache.commons.lang.version>
- </properties>
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+ <automatic.module.name>net.shibboleth.idp.plugin.duo.impl</automatic.module.name>
+ <apache.commons.lang.version>3.10</apache.commons.lang.version>
+ </properties>
- <dependencies>
- <!-- compile time dependencies -->
- <dependency>
- <groupId>net.shibboleth.idp.plugin</groupId>
- <artifactId>idp-plugin-duo-api</artifactId>
- </dependency>
- <dependency>
- <groupId>com.nimbusds</groupId>
- <artifactId>nimbus-jose-jwt</artifactId>
- <scope>compile</scope>
- </dependency>
+ <dependencies>
+ <!-- compile time dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.idp.plugin</groupId>
+ <artifactId>idp-plugin-duo-api</artifactId>
+ </dependency>
- <!-- Provided dependencies -->
- <dependency>
- <groupId>${idp.groupId}</groupId>
- <artifactId>idp-session-api</artifactId>
+ <!-- Provided dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-crypto-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-crypto-impl</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-session-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-messaging-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.utilities</groupId>
+ <artifactId>java-support</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring-webflow.groupId}</groupId>
+ <artifactId>spring-webflow</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
- <dependency>
- <groupId>${idp.groupId}</groupId>
- <artifactId>idp-authn-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-messaging-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.utilities</groupId>
- <artifactId>java-support</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.google.code.findbugs</groupId>
- <artifactId>jsr305</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${spring-webflow.groupId}</groupId>
- <artifactId>spring-webflow</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-profile-api</artifactId>
- <scope>provided</scope>
- </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-beans</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-profile-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <scope>provided</scope>
+ </dependency>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${spring.groupId}</groupId>
- <artifactId>spring-core</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${spring.groupId}</groupId>
- <artifactId>spring-beans</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${idp.groupId}</groupId>
- <artifactId>idp-profile-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <scope>provided</scope>
- </dependency>
-
- <!-- Test Dependencies -->
- <dependency>
- <groupId>${spring.groupId}</groupId>
- <artifactId>spring-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp</groupId>
- <artifactId>idp-conf</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp</groupId>
- <artifactId>idp-conf-impl</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp</groupId>
- <artifactId>idp-profile-impl</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp</groupId>
- <artifactId>idp-authn-impl</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp</groupId>
- <artifactId>idp-profile-api</artifactId>
- <scope>test</scope>
- <type>test-jar</type>
- </dependency>
- <!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
- <scope>test</scope>
- </dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-conf</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-conf-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-profile-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-authn-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-profile-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
- </dependencies>
+ </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>system/**</exclude>
- <exclude>conf/**</exclude>
- <exclude>credentials/**</exclude>
- </excludes>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <archive>
- <index>true</index>
- <manifestEntries>
- <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
- </manifestEntries>
- <manifestSections>
- <manifestSection>
- <name>net/shibboleth/idp/plugin/duo</name>
- <manifestEntries>
- <Implementation-Title>${project.artifactId}</Implementation-Title>
- <Implementation-Version>${project.version}</Implementation-Version>
- <Implementation-Vendor>shibboleth.net</Implementation-Vendor>
- </manifestEntries>
- </manifestSection>
- </manifestSections>
- </archive>
- </configuration>
- </plugin>
- </plugins>
- </build>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>system/**</exclude>
+ <exclude>conf/**</exclude>
+ <exclude>credentials/**</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <index>true</index>
+ <manifestEntries>
+ <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
+ </manifestEntries>
+ <manifestSections>
+ <manifestSection>
+ <name>net/shibboleth/idp/plugin/duo</name>
+ <manifestEntries>
+ <Implementation-Title>${project.artifactId}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>shibboleth.net</Implementation-Vendor>
+ </manifestEntries>
+ </manifestSection>
+ </manifestSections>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java
deleted file mode 100644
index 206a4a7..0000000
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java
+++ /dev/null
@@ -1,266 +0,0 @@
-
-package net.shibboleth.idp.plugin.authn.duo.impl;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.nimbusds.jose.proc.SecurityContext;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
-
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- * <p>
- * {@link JWTClaimsSetVerifier JWT claims verifier} implementation that only checks statically configurable claims.
- * That is, not those that are determined at runtime (for instance the issuer and audience), as these are taken
- * from the Security Context - see the {@link DynamicDuoContextJWTClaimsVerifier}.
- * </p>
- * <p>
- * This class is thread-safe once it has been initialised.
- * </p>
- *
- * <p>
- * Performs the following checks:
- * <ol>
- * <li>Checks all required claims are present.</li>
- * <li>Checks prohibited claims are not present.</li>
- * <li>If an issued-at time (iat) claim is present, rejects tokens that were issued (iat) to far away from
- * the current time. See section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>If an expiration time (exp) claim is present, makes sure it is ahead of the current time, else the JWT claims set
- * is rejected. A few minutes of {@code clockSkew} is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>If a not-before-time (nbf) claim is present, makes sure it is before the current time, else the JWT claims set is
- * rejected.</li>
- * </ol>
- * <p>To make JWT verification OIDC v1.0 complaint for verifying id_tokens, the set of required claims needs to contain
- * {'iss','sub','aud','exp','iat'}. This verifier does not check 'auth_time', which maybe required if requested
- * or 'max_age' was set.
- * </p>
- * <p>
- * This class may be extended to perform additional checks.
- * </p>
- *
- * @param <ContextType> the {@link SecurityContext} type.
- */
- at ThreadSafeAfterInit
-public class BasicJWTClaimsVerifier<ContextType extends SecurityContext> extends AbstractInitializableComponent
- implements JWTClaimsSetVerifier<ContextType> {
-
- /**
- * Positive clock skew adjustment to consider when checking JWT not before and expiration
- * (Default value: 60 seconds).
- */
- @Nonnull private Duration clockSkew;
-
- /**
- * Maximum amount (in either direction from now) of duration for which a token is valid after
- * it is issued (Default value: 60 seconds).
- */
- @Nonnull private Duration iatWindow;
-
- /**
- * The names of the JWT claims that must be present, empty set if none.
- */
- @Nonnull @NonnullElements private Set<String> requiredClaims;
-
- /**
- * The names of the JWT claims that must not be present, empty set if none.
- */
- @Nonnull @NonnullElements private Set<String> prohibitedClaims;
-
-
- /** Constructor. */
- public BasicJWTClaimsVerifier() {
- clockSkew = Duration.ofSeconds(60);
- iatWindow = Duration.ofSeconds(60);
- requiredClaims = Collections.emptySet();
- prohibitedClaims = Collections.emptySet();
- }
-
- /**
- * Set the clock skew.
- *
- * @param skew clock skew to set
- */
- public void setClockSkew(@Nonnull final Duration skew) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
- }
-
- /**
- * Sets the amount of time for which a token is valid from when it was issued.
- *
- * @param window amount of time for which a token is valid
- */
- public void setIatWindow(@Nonnull final Duration window) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- Constraint.isNotNull(window, "Token issued at window cannot be null");
- Constraint.isFalse(window.isNegative(), "Token issued at window cannot be negative");
-
- iatWindow = window;
- }
-
- /**
- * Set the required claims.
- *
- * @param claims the required claims.
- */
- public void setRequiredClaims(@Nullable final Set<String> claims) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- if (claims !=null) {
- requiredClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
- } else {
- requiredClaims = Collections.emptySet();
- }
- }
-
- /**
- * Set the prohibited claims.
- *
- * @param claims the prohibited claims.
- */
- public void setProhibitedClaims(@Nullable final Set<String> claims) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- if (claims !=null) {
- prohibitedClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
- } else {
- prohibitedClaims = Collections.emptySet();
- }
- }
-
- @Override
- public void verify(final JWTClaimsSet claimsSet, final ContextType context)
- throws BadJWTException {
-
- verifyRequiredClaims(claimsSet);
- verifyProhibitedClaims(claimsSet);
- verifyNotBefore(claimsSet);
- verifyExpiration(claimsSet);
- verifyIat(claimsSet);
-
- }
-
- /**
- * Verify the claims set contains the given, required, claims.
- *
- * @param claimsSet the claims set.
- *
- * @throws BadJWTException if a required claim is not present.
- */
- private void verifyRequiredClaims(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
- if (!claimsSet.getClaims().keySet().containsAll(requiredClaims)) {
- final Set<String> missingClaims = new HashSet<>(requiredClaims);
- missingClaims.removeAll(claimsSet.getClaims().keySet());
- throw new BadJWTException("JWT missing required claims: " + missingClaims);
- }
- }
-
- /**
- * Verify the claims set does not contain the given, prohibited, claims.
- *
- * @param claimsSet the claims set.
- *
- * @throws BadJWTException if a prohibited claim is present.
- */
- private void verifyProhibitedClaims(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
- final Set<String> presentProhibitedClaims = new HashSet<>();
- for (final String prohibited : prohibitedClaims) {
- if (claimsSet.getClaims().containsKey(prohibited)) {
- presentProhibitedClaims.add(prohibited);
- }
- if (!presentProhibitedClaims.isEmpty()) {
- throw new BadJWTException("JWT has prohibited claims: " + presentProhibitedClaims);
- }
- }
- }
-
- /**
- * If present, verifies the not-before-time has passed.
- *
- * @param claimsSet the claims set.
- *
- * @throws BadJWTException if the not-before-time is in the future.
- */
- private void verifyNotBefore(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
- final Instant now = Instant.now();
- final Date nbf = claimsSet.getNotBeforeTime();
- if (nbf != null) {
- final Instant nbfInstant = nbf.toInstant();
- final Instant nbfInstantMinusSkew = nbfInstant.minus(clockSkew);
- if (!nbfInstantMinusSkew.isBefore(now)) {
- throw new BadJWTException("JWT before use time");
- }
- }
-
- }
-
- /**
- * If present, verifies the expiration time has not passed (including the given clock skew).
- *
- * @param claimsSet the claims set.
- *
- * @throws BadJWTException if the token has expired.
- */
- private void verifyExpiration(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
- final Instant now = Instant.now();
-
- final Date exp = claimsSet.getExpirationTime();
- if (exp != null) {
- final Instant expInstant = exp.toInstant();
- final Instant expirationPlusSkew = expInstant.plus(clockSkew);
-
- if (now.isAfter(expirationPlusSkew)) {
- throw new BadJWTException("Expired JWT");
- }
- }
- }
-
- /**
- * If present, verifies the IssuedAt claim exists and is within a specified window from the current time.
- *
- * @param claimsSet the claims set.
- *
- * @throws BadJWTException if the IssuedAt claim is invalid.
- */
- private void verifyIat(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
- final Date iatDate = claimsSet.getIssueTime();
- if (iatDate != null) {
- final Instant iat = iatDate.toInstant();
- final Instant now = Instant.now();
- final Duration iatDifference = Duration.between(now, iat).abs();
-
- if (iatWindow.compareTo(iatDifference) < 0) {
- throw new BadJWTException("JWT issued-at time is too far away from the current time. "
- + "Token issued at '"+iat+"' was too far away from the current time '"+now+"' "
- + "with acceptable deviation of "
- + "'"+iatWindow+"', difference is '"+iatDifference+"'");
- }
- }
-
- }
-
-
-
-}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
index 3d2298b..417eb1e 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
@@ -104,7 +104,8 @@ public class DefaultDuoOIDCClientRegistry extends AbstractIdentifiableInitializa
//this is an atomic call, avoiding the need to synchronise here e.g. two clients should never
//be created for the same integration.
final DuoOIDCClient client = clientRegistry.computeIfAbsent(integration,clientRegistryMappingFunction);
- log.debug("Duo registry returning the DuoClient instance '{}' of type '{}'",client.getClientId(),client.getClass().getCanonicalName());
+ log.debug("Duo registry returning the DuoClient instance '{}' of type '{}'",
+ client.getClientId(),client.getClass().getCanonicalName());
return client;
} catch (final DuoClientInitializationException e) {
throw new DuoRegistryException("DuoClient could not be found or created in the registry",e);
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java
new file mode 100644
index 0000000..bc50b09
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/**
+ * Looks up the audience from the clientID in the {@link DuoOIDCIntegration} inside the context.
+ * Returns null if it fails to find the clientID. Used for JWT ID Token audience claims verification.
+ */
+public class DuoAudienceClaimLookupStrategy implements Function<ProfileRequestContext,String>{
+
+ @Override
+ public String apply(@Nonnull final ProfileRequestContext context) {
+
+ final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ return null;
+ }
+
+ final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
+ if (duoIntegration == null) {
+ return null;
+ }
+ return duoIntegration.getClientId();
+ }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java
new file mode 100644
index 0000000..6261922
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Find the issuer from the {@link DuoOIDCIntegration}. Returns null if not found.
+ */
+public class DuoIssuerClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+
+ /** HTTPS scheme protocol.*/
+ @Nonnull @NotEmpty public static final String HTTPS = "https://";
+
+ /** The default issuer path, specific to the v1 Duo flow.*/
+ @Nonnull @NotEmpty public static final String DEFAULT_ISSUER_PATH = "/oauth/v1/token";
+
+ /** The URL path component of the issuer.*/
+ @Nonnull @NotEmpty private String issuerPath;
+
+ /**
+ * Sets the issuer URL path component.
+ *
+ * @param path the issuer path
+ */
+ public void setIssuerPath(@Nonnull @NotEmpty final String path) {
+ issuerPath = Constraint.isNotEmpty(path, "Issuer URL path cannot be null or empty");
+ }
+
+ @Override
+ public String apply(@Nonnull final ProfileRequestContext context) {
+
+ final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ return null;
+ }
+ final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
+ if (duoIntegration == null) {
+ return null;
+ }
+ return HTTPS+duoIntegration.getAPIHost()+issuerPath;
+ }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java
new file mode 100644
index 0000000..c765f47
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/**
+ * Find the nonce from the {@link DuoAuthenticationContext}. Returns null if not found.
+ */
+public class DuoNonceClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+
+ @Override
+ public String apply(@Nonnull final ProfileRequestContext context) {
+
+ final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ return null;
+ }
+ return duoContext.getNonce();
+ }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java
new file mode 100644
index 0000000..18ed4bf
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/**
+ * Find the authenticating principals username from the {@link DuoAuthenticationContext}. Returns null if not found.
+ */
+public class DuoUsernameClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+
+ @Override
+ public String apply(@Nonnull final ProfileRequestContext context) {
+
+ final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ return null;
+ }
+ return duoContext.getUsername();
+ }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java
deleted file mode 100644
index 464ba87..0000000
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.proc.SecurityContext;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
-import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/**
- * <p>
- * {@link JWTClaimsSetVerifier JWT claims verifier} implementation. Extends the {@link BasicJWTClaimsVerifier}
- * to include claim checks constructed from values inside the {@link DuoOIDCIntegration}
- * stashed in the {@link SecurityContext} at runtime, namely; the issuer and audience.
- * </p>
- * <p>
- * This class is thread-safe once it has been initialised.
- * </p>
- *
- * <p>
- * In addition to the verification checks in the {@link BasicJWTClaimsVerifier}, verifies:
- <ul>
- * <li>The Audience (aud) claim contains the client_id of this client (as registered at the issuer). See
- * section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>The auth_time (when the End-User authentication took place) is within a valid expiration window. Only
- * for forced authentications.</li>
- * <li>The issuer (iss) of the token exactly matches that of the configured Duo token provider. See section
- * 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>The preferred username standard claim in the token matches the user who is currently authenticating</li>
- * <li>If present, the nonce matches that sent in the authorization request.</li>
- * </ul>
- *
- * <p>
- * This class may be extended to perform additional checks.
- * </p>
- */
- at ThreadSafeAfterInit
-public class DynamicDuoContextJWTClaimsVerifier extends BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext> {
-
- /** HTTPS scheme protocol.*/
- @Nonnull @NotEmpty public static final String HTTPS = "https://";
-
- /** The default issuer path, specific to the v1 Duo flow.*/
- @Nonnull @NotEmpty public static final String DEFAULT_ISSUER_PATH = "/oauth/v1/token";
-
- /** The default name of the username claim (an OIDC standard claims).*/
- @Nonnull @NotEmpty public static final String DEFAULT_USERNAME_CLAIM = "preferred_username";
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(DynamicDuoContextJWTClaimsVerifier.class);
-
- /**
- * If forced authentication, amount of time for which a token is valid
- * after if it was issued. (Default value: 60 seconds)
- */
- @Nonnull private Duration authnLifetime;
-
- /** The URL path component of the issuer.*/
- @Nonnull @NotEmpty private String issuerPath;
-
- /** The name of the username claim.*/
- @Nonnull @NotEmpty private String usernameClaim;
-
- /** Lookup strategy to locate the Duo authentication context. */
- @Nonnull
- private Function<ProfileRequestContext, DuoOIDCAuthenticationContext> duoContextLookupStrategy;
-
- /** Lookup strategy to locate the authentication context. */
- @Nonnull
- private Function<ProfileRequestContext, AuthenticationContext> authenticationContextLookupStrategy;
-
- /** Constructor.*/
- public DynamicDuoContextJWTClaimsVerifier() {
- super();
- // prc -> ac -> dc
- duoContextLookupStrategy = new ChildContextLookup<>(DuoOIDCAuthenticationContext.class)
- .compose(new ChildContextLookup<>(AuthenticationContext.class));
- // prc -> ac
- authenticationContextLookupStrategy = new ChildContextLookup<>(AuthenticationContext.class);
- authnLifetime = Duration.ofSeconds(60);
- issuerPath = DEFAULT_ISSUER_PATH;
- usernameClaim = DEFAULT_USERNAME_CLAIM;
- }
-
- /**
- * Set Duo authentication context lookup strategy to use.
- *
- * @param strategy lookup strategy
- */
- public void setDuoContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,DuoOIDCAuthenticationContext> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- duoContextLookupStrategy = Constraint.isNotNull(strategy, "DuoContextLookup strategy cannot be null");
- }
-
- /**
- * Set Duo authentication context lookup strategy to use.
- *
- * @param strategy lookup strategy
- */
- public void setAuthenticationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,AuthenticationContext> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- authenticationContextLookupStrategy = Constraint.isNotNull(strategy,
- "AuthenticationContextLookup strategy cannot be null");
- }
-
- /**
- * Sets the amount of time for which a token is valid from when the original authentication took place.
- * Only applies to forced authentications.
- *
- * @param lifetime amount of time for which a token is valid
- */
- public void setAuthnLifetime(@Nonnull final Duration lifetime) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- Constraint.isNotNull(lifetime, "Token authentication lifetime cannot be null");
- Constraint.isFalse(lifetime.isNegative(), "Token authentication lifetime cannot be negative");
-
- authnLifetime = lifetime;
- }
-
- /**
- * Sets the issuer URL path component.
- *
- * @param path the issuer path
- */
- public void setIssuerPath(@Nonnull @NotEmpty final String path) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- issuerPath = Constraint.isNotEmpty(path, "Issuer URL path cannot be null or empty");
- }
-
- /**
- * Sets the name of the username claim.
- *
- * @param claimName the username claim name
- */
- public void setUsernameClaim(@Nonnull @NotEmpty final String claimName) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- usernameClaim = Constraint.isNotEmpty(claimName, "Username claim name can not be null or empty");
- }
-
-
- @Override
- public void verify(@Nonnull final JWTClaimsSet claimsSet, @Nonnull final ProfileRequestJWTSecurityContext context)
- throws BadJWTException {
-
- //do basic checks first.
- super.verify(claimsSet,context);
-
- //when used within the validation action, it is unlikely these could ever be null.
- if (context == null) {
- throw new BadJWTException("Duo claims verifier requires the ProfileRequest security context");
- }
-
- final AuthenticationContext authnContext = authenticationContextLookupStrategy.apply(context.getPrc());
- if (authnContext == null) {
- throw new BadJWTException("No authentication context returned by lookup strategy, audience and issuer"
- + " could not be verified");
- }
-
- final DuoOIDCAuthenticationContext duoContext = duoContextLookupStrategy.apply(context.getPrc());
- if (duoContext == null) {
- throw new BadJWTException("No Duo context returned by lookup strategy, audience and issuer"
- + " could not be verified");
- }
-
- final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
- if (duoIntegration == null) {
- throw new BadJWTException("No Duo integration contained within the Duo context");
- }
-
- verifyAudience(claimsSet,duoIntegration);
- verifyAuthenticationTime(claimsSet,authnContext);
-
- //if the nonce exists in the id_token it must be validated alongside the issuer and username.
- if (claimsSet.getClaim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.NONCE.getClaimName()) != null) {
- //verify with nonce if present
- verifyExactMatchClaims(claimsSet, new JWTClaimsSet.Builder()
- .issuer(HTTPS+duoContext.getIntegration().getAPIHost()+issuerPath)
- .claim(usernameClaim, duoContext.getUsername())
- .claim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.NONCE.getClaimName(),duoContext.getNonce())
- .build());
- } else {
- //verify without a nonce, only issuer and username
- verifyExactMatchClaims(claimsSet, new JWTClaimsSet.Builder()
- .issuer(HTTPS+duoContext.getIntegration().getAPIHost()+issuerPath)
- .claim(usernameClaim, duoContext.getUsername())
- .build());
- }
- }
-
- /**
- * Verify the claims in <code>exactMatchClaims</code> exactly match (by Object equality) those
- * in the <code>claimsSet</code>.
- *
- * @param claimsSet the JWT claimset.
- * @param exactMatchClaims the claims to match exactly.
- *
- * @throws BadJWTException if the claim does not exist or does not match the given claim value.
- */
- private void verifyExactMatchClaims(@Nonnull final JWTClaimsSet claimsSet,
- @Nonnull final JWTClaimsSet exactMatchClaims) throws BadJWTException {
-
- for (final String exactMatch: exactMatchClaims.getClaims().keySet()) {
- final Object actualClaim = claimsSet.getClaim(exactMatch);
- if (actualClaim == null) {
- throw new BadJWTException("Claim '"+exactMatch+"' does not exist");
- }
- final Object expectedClaim = exactMatchClaims.getClaim(exactMatch);
- if (! actualClaim.equals(expectedClaim)) {
- throw new BadJWTException("JWT \"" + exactMatch + "\" claim has value "
- + actualClaim + " but should be " + expectedClaim);
- }
- }
-
- }
-
- /**
- * Verify one of the audience values in the 'aud' claim matches with the Duo clientId.
- *
- * @param claimsSet the JWT claimset.
- * @param duoIntegration the Duo integration information.
- *
- * @throws BadJWTException if the accepted audience (Duo ClientId) can not be found in
- * the JWT's audience list.
- */
- private void verifyAudience(@Nonnull final JWTClaimsSet claimsSet,
- @Nonnull final DuoOIDCIntegration duoIntegration) throws BadJWTException {
-
- // Check audience based on that specified in the duo integration
- final String acceptedAudience = duoIntegration.getClientId();
-
- final List<String> audList = claimsSet.getAudience();
- if (audList != null && !audList.isEmpty()) {
- boolean audMatch = false;
- for (final String aud : audList) {
- if (acceptedAudience.equals(aud)) {
- audMatch = true;
- break;
- }
- }
- if (!audMatch) {
- throw new BadJWTException("JWT audience rejected: " + audList);
- }
- } else {
- throw new BadJWTException("JWT missing required audience");
- }
-
- }
-
- /**
- * Verifies if the auth_time (when the End-User authentication took place) is within a
- * valid expiration window. Only applies to forced authentications.
- *
- * @param claimsSet the JWT claimset.
- * @param ac the authentication context.
- *
- * @throws BadJWTException if the auth_time is invalid.
- */
- private void verifyAuthenticationTime(@Nonnull final JWTClaimsSet claimsSet,
- @Nonnull final AuthenticationContext ac) throws BadJWTException {
-
- if (!ac.isForceAuthn()) {
- //no forced authn, so do not validate authentication time
- return;
- } else {
- //forced authn, so check authentication time
- try {
- final Date authTimeDate = claimsSet.getDateClaim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.
- AUTHENTICATION_TIME.getClaimName());
- if (authTimeDate == null) {
- throw new BadJWTException("No authentication time found in token");
- }
- final Instant authTime = authTimeDate.toInstant();
- final Instant now = Instant.now();
- final Instant expiration = authTime.plus(authnLifetime);
-
- // Check time of authentication wasn't in the future
- if (authTime.isAfter(now)) {
- log.warn("Authentication forced but is not yet valid: auth_time was {}, latest valid is: {}",
- authTime, now);
- throw new BadJWTException("JWT token authentication time is not yet valid");
- }
-
- // Check time of authentication has not expired
- if (expiration.isBefore(now)) {
- log.warn(
- "Authentication required (forced) but has expired: auth_time was '{}', "
- + "expired at: '{}', current time: '{}'",
- authTime, expiration, now);
- throw new BadJWTException("JWT token authentication time has expired");
- }
- //is OK.
-
- } catch (final ParseException e) {
- throw new BadJWTException("Autentication forced, but no authentication time found in token",e);
- }
- }
- }
-
-}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
index 5178c10..9ffbaa5 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
@@ -30,24 +30,23 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.duo.AbstractDuoAuthenticationAction;
import net.shibboleth.idp.plugin.authn.duo.DuoException;
import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
/**
- * Action that validates the claims of the Duo id_token using the supplied {@link JWTClaimsSetVerifier}. The
- * verifier <b>must</b> be thread-safe and validate the claims set against the OpenID Connect core 1.0
- * section 3.1.3.7 specification, and those required by Duo. However, the action can not guarantee
- * verification behaves in this way.
+ * Action that validates the claims of the Duo id_token using the supplied
+ * {@link JWTClaimsValidation claims validator}. The verifier <b>must</b> be thread-safe and validate the
+ * claims set against the OpenID Connect core 1.0 section 3.1.3.7 specification, and those required by Duo.
*
* @pre
*
@@ -87,14 +86,14 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
/** A cleanup hook to execute after either a successful or unsuccessful claims validation. */
@Nullable private Consumer<ProfileRequestContext> cleanupHook;
- /** The JWT claims verify used to verify the claimsset.*/
- @NonnullAfterInit private JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> claimsVerifier;
+ /** The JWT claims validator used to verify the claimsset.*/
+ @NonnullAfterInit private JWTClaimsValidation claimsValidator;
/** {@inheritDoc} */
@Override protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (claimsVerifier == null) {
+ if (claimsValidator == null) {
throw new ComponentInitializationException("Duo ClaimSet Verifier cannot be null");
}
}
@@ -123,12 +122,13 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
/**
* Set the JWT claims verifier to use.
*
- * @param verifier the claims verifier.
+ * @param validator the claims validator.
*/
- public synchronized void setClaimsVerifier(
- @Nonnull final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier) {
+ public synchronized void setClaimsValidator(
+ @Nonnull final JWTClaimsValidation validator) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- claimsVerifier = Constraint.isNotNull(verifier, "Claims verifier cannot be null");
+
+ claimsValidator = Constraint.isNotNull(validator, "Claims validator cannot be null");
}
@Override
@@ -165,11 +165,11 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
log.debug("{} Validating token claims for subject '{}'",getLogPrefix(),claimsSet.getSubject());
try {
- claimsVerifier.verify(claimsSet, new ProfileRequestJWTSecurityContext(profileRequestContext));
+ claimsValidator.validate(claimsSet,profileRequestContext);
if (cleanupHook != null) {
cleanupHook.accept(profileRequestContext);
}
- } catch (final BadJWTException e) {
+ } catch (final JWTValidationException e) {
log.error("{} Token verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
if (cleanupHook != null) {
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 f536054..7db4f8b 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
@@ -9,7 +9,6 @@
default-init-method="initialize" default-destroy-method="destroy">
- <!-- For testing, this properties placeholder does not have access to the same set of mock properties defined in SWF tests -->
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" p:placeholderPrefix="%{"
p:placeholderSuffix="}" />
@@ -44,8 +43,8 @@
class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoOIDCClientRegistry"
p:clientFactory-ref="%{idp.duo.oidc.clientFactoryBean:shibboleth.authn.DuoOIDC.sdk.clientFactory}" />
- <!-- Load all (or none) factory bean definitions from the classpath. The actual factory bean to use is defined in the
- client registry and overridable by the property idp.duo.oidc.clientFactoryBean -->
+ <!-- Load all (or none) factory bean definitions from the classpath. The actual factory bean to use is
+ defined in the client registry and overridable by the property idp.duo.oidc.clientFactoryBean -->
<import resource="classpath*:/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml" />
<!-- Can override one or more of the beans above. Note, the property override is mostly to allow tests
@@ -74,17 +73,75 @@
<bean id="ValidateTokenClaims" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateTokenClaims"
p:cleanupHook="#{getObject('shibboleth.authn.DuoOIDC.jwt.claims.CleanUpHook') ?: getObject('shibboleth.authn.DuoOIDC.jwt.claims.DefaultCleanupHook')}"
- p:claimsVerifier="#{getObject('shibboleth.authn.DuoOIDC.DuoTokenClaimsVerifier') ?: getObject('shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier')}"/>
+ p:claimsValidator="#{getObject('shibboleth.authn.DuoOIDC.DuoTokenClaimsVerifier') ?: getObject('shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier')}"/>
+ <!-- OIDC claims verification -->
<bean id="shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier"
- class="net.shibboleth.idp.plugin.authn.duo.impl.DynamicDuoContextJWTClaimsVerifier"
- p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"
- p:iatWindow="%{idp.duo.oidc.jwt.verifier.iatWindow:PT60S}"
- p:authnLifetime="%{idp.duo.oidc.jwt.verifier.authLifetime:PT60S}"
- p:issuerPath="%{idp.duo.oidc.jwt.verifier.issuerPath:/oauth/v1/token}"
- p:usernameClaim="%{idp.duo.oidc.jwt.verifier.preferredUsername:preferred_username}"
- p:requiredClaims="#{getObject('shibboleth.authn.DuoOIDC.RequiredOIDCClaims') ?: getObject('shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims')}"
- />
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidation">
+ <property name="claimValidators">
+ <list>
+ <bean id="requiredClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="#{getObject('shibboleth.authn.DuoOIDC.RequiredOIDCClaims') ?:
+ getObject('shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims')}"/>
+ <bean id="issuerClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="iss"
+ p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.IssuerLookupStrategy') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultIssuerLookupStrategy')}"/>
+ <bean id="audienceClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+ p:audienceLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.AudienceLookupStrategy') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultAudienceLookupStrategy')}"/>
+ <bean id="notBeforeClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator"
+ p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"/>
+ <bean id="expiryClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"/>
+ <bean id="issuedAtClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:iatWindow="%{idp.duo.oidc.jwt.verifier.iatWindow:PT60S}"/>
+ <bean id="usernameClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="%{idp.duo.oidc.jwt.verifier.preferredUsername:preferred_username}"
+ p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.UsernameLookupStrategy') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultUsernameLookupStrategy')}"/>
+ <!-- is always returned by Duo, so no need to evaluate if it was requested -->
+ <bean id="authenticationTimeClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.AuthenticationTimeClaimsValidator"
+ p:authnLifetime="%{idp.duo.oidc.jwt.verifier.authLifetime:PT60S}"
+ p:activationCondition="#{getObject('shibboleth.authn.DuoOIDC.jwt.AuthTimeActivationCondition') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultAuthTimeActivationCondition')}"/>
+ <bean id="nonceClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="nonce"
+ p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.NonceLookupStrategy') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultNonceLookupStrategy')}"
+ p:activationCondition="#{getObject('shibboleth.authn.DuoOIDC.jwt.NonceActivationCondition') ?:
+ getObject('shibboleth.authn.DuoOIDC.jwt.DefaultNonceActivationCondition')}"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultAuthTimeActivationCondition"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ForcedAuthenticationActivationCondition"/>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultNonceActivationCondition"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActiviationCondition"/>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultIssuerLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.duo.impl.DuoIssuerClaimLookupStrategy"
+ p:issuerPath="%{idp.duo.oidc.jwt.verifier.issuerPath:/oauth/v1/token}"/>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultNonceLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.duo.impl.DuoNonceClaimLookupStrategy"/>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultUsernameLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.duo.impl.DuoUsernameClaimLookupStrategy"/>
+
+ <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultAudienceLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.duo.impl.DuoAudienceClaimLookupStrategy"/>
<!-- These represent the default set of id_token claims which are **required** by OIDC -->
<util:set id="shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims">
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java
deleted file mode 100644
index fcd8d0d..0000000
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package net.shibboleth.idp.plugin.authn.duo.impl;
-
-import java.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.nimbusds.jwt.proc.BadJWTException;
-
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
-
-/**
- * Tests for the {@link BasicJWTClaimsVerifier}.
- */
-public class BasicJWTClaimsVerifierTest extends AbstractDuoActionTest{
-
- /** The verifier to test.*/
- @Nonnull private BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext> verifier;
-
- @BeforeMethod
- public void setUp() throws Exception {
- super.setup();
- verifier = new BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext>();
-
- }
-
- @Test(expectedExceptions = UnmodifiableComponentException.class)
- public final void testSetupAfterInit() throws ComponentInitializationException {
- verifier.initialize();
- verifier.setClockSkew(Duration.ofMillis(100));
- }
-
- @Test(expectedExceptions = BadJWTException.class)
- public final void testNotBeforeTimeInFuture() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
- "api.duosecurity.com", "duo_push"));
-
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test
- public final void testNotBeforeTimeInFutureButWithinSkew() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- verifier.setClockSkew(Duration.ofMinutes(3));
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test(expectedExceptions = BadJWTException.class)
- public final void testNotBeforeTimeInFutureBeyondSkew() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- verifier.setClockSkew(Duration.ofMinutes(1));
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test
- public final void testNotBeforeTimeInPast() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(), Instant.now().minus(Duration.ofMinutes(1)),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Test the nbf time is equal to the current time - which should be allowed per JWT spec (rfc7519)
- *
- * @throws BadJWTException on error.
- * @throws ParseException on error.
- */
- @Test
- public final void testNotBeforeTimeIsNow() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate an expired token.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testExpiredToken() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now().minus(10,ChronoUnit.MINUTES),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate an expired token but with a large clock skew.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test
- public final void testExpiredTokenLargeClockSkew() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- //set clock skew
- verifier.setClockSkew(Duration.ofMinutes(20));
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now().minus(10,ChronoUnit.MINUTES),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test
- public final void testAllRequiredOIDCClaimsPresent() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- verifier.setRequiredClaims(Set.of("iss","exp","sub","aud","iat"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
- @Test(expectedExceptions = BadJWTException.class)
- public final void testAllRequiredOIDCClaimsPresentSomeMissing() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- verifier.setRequiredClaims(Set.of("iss","exp","sub","aud","iat"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",
- Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
-
- @Test
- public final void testRequireClaimsValid() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- verifier.setRequiredClaims(Set.of("iss","exp","sub"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test(expectedExceptions = BadJWTException.class)
- public final void testRequireClaimsInvalid() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- verifier.setRequiredClaims(Set.of("iss","exp","sub","not_in_jwt"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test(expectedExceptions = BadJWTException.class)
- public final void testProhibitedClaims() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- verifier.setProhibitedClaims(Set.of("iss"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- @Test
- public final void testProhibitedClaimsDoNotExist() throws Exception {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- //should be fine, as not in JWT
- verifier.setProhibitedClaims(Set.of("not_in_jwt"));
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
- verifier.initialize();
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
-}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java
new file mode 100644
index 0000000..b0d0ce3
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java
@@ -0,0 +1,52 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoAudienceClaimLookupStrategy}.
+ */
+public class DuoAudienceClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+ /** The strategy.*/
+ @Nonnull private DuoAudienceClaimLookupStrategy strategy;
+
+ /**
+ * Setup.
+ *
+ * @throws ComponentInitializationException on failure to initialise a component.
+ */
+ @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+ super.setup();
+ strategy = new DuoAudienceClaimLookupStrategy();
+ }
+
+ @Test
+ public void applySuccess() {
+ addDuoContext();
+ addDuoIntegrationToContext();
+ final String audience = strategy.apply(prc);
+ assertEquals(audience, dc.getIntegration().getClientId());
+ }
+
+ @Test
+ public void applyNoDuoContext() {
+ final String audience = strategy.apply(prc);
+ assertEquals(audience, null);
+ }
+
+ @Test
+ public void applyNoDuoIntegration() {
+ addDuoContext();
+ final String audience = strategy.apply(prc);
+ assertEquals(audience, null);
+ }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java
new file mode 100644
index 0000000..b6ad8cd
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java
@@ -0,0 +1,54 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoIssuerLookupStrategy}.
+ */
+public class DuoIssuerClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+ /** The strategy.*/
+ @Nonnull private DuoIssuerClaimLookupStrategy strategy;
+
+ /**
+ * Setup.
+ *
+ * @throws ComponentInitializationException on failure to initialise a component.
+ */
+ @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+ super.setup();
+ strategy = new DuoIssuerClaimLookupStrategy();
+ }
+
+ @Test
+ public void applySuccess() {
+ addDuoContext();
+ addDuoIntegrationToContext();
+ //set a different path for testing.
+ strategy.setIssuerPath("/oauth/v2/token");
+ final String issuer = strategy.apply(prc);
+ assertEquals(issuer, "https://"+dc.getIntegration().getAPIHost()+"/oauth/v2/token");
+ }
+
+ @Test
+ public void applyNoDuoContext() {
+ final String issuer = strategy.apply(prc);
+ assertEquals(issuer, null);
+ }
+
+ @Test
+ public void applyNoDuoIntegration() {
+ addDuoContext();
+ final String issuer = strategy.apply(prc);
+ assertEquals(issuer, null);
+ }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java
new file mode 100644
index 0000000..c998253
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java
@@ -0,0 +1,53 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoNonceClaimLookupStrategy}.
+ */
+public class DuoNonceClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+ /** The strategy.*/
+ @Nonnull private DuoNonceClaimLookupStrategy strategy;
+
+ /**
+ * Setup.
+ *
+ * @throws ComponentInitializationException on failure to initialise a component.
+ */
+ @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+ super.setup();
+ strategy = new DuoNonceClaimLookupStrategy();
+ }
+
+ @Test
+ public void applySuccess() {
+ addDuoContext();
+ addDuoIntegrationToContext();
+ dc.setNonce("testnonce");
+ final String nonce = strategy.apply(prc);
+ assertEquals(nonce, "testnonce");
+ }
+
+ @Test
+ public void applyNoDuoContext() {
+ final String nonce = strategy.apply(prc);
+ assertEquals(nonce, null);
+ }
+
+ @Test
+ public void applyNoDuoIntegration() {
+ addDuoContext();
+ final String nonce = strategy.apply(prc);
+ assertEquals(nonce, null);
+ }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java
new file mode 100644
index 0000000..626b085
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java
@@ -0,0 +1,56 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoUsernameClaimLookupStrategy}.
+ */
+public class DuoUsernameClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+ /** The strategy.*/
+ @Nonnull private DuoUsernameClaimLookupStrategy strategy;
+
+ /**
+ * Setup.
+ *
+ * @throws ComponentInitializationException on failure to initialise a component.
+ */
+ @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+ super.setup();
+ strategy = new DuoUsernameClaimLookupStrategy();
+ }
+
+ @Test
+ public void applySuccess() {
+ addDuoContext();
+ addDuoIntegrationToContext();
+ dc.setUsername("username");
+ final String username = strategy.apply(prc);
+ assertEquals(username, "username");
+ }
+
+ @Test
+ public void applyNoUsername() {
+ addDuoContext();
+ addDuoIntegrationToContext();
+ dc.setUsername(null);
+ final String username = strategy.apply(prc);
+ assertEquals(username, null);
+ }
+
+ @Test
+ public void applyNoDuoContext() {
+ final String username = strategy.apply(prc);
+ assertEquals(username, null);
+ }
+
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java
deleted file mode 100644
index d68e912..0000000
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-
-import javax.annotation.Nonnull;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.proc.BadJWTException;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
-
-public class DynamicDuoContextJWTClaimsVerifierTest extends AbstractDuoActionTest{
-
- @Nonnull private DynamicDuoContextJWTClaimsVerifier verifier;
-
- @BeforeMethod
- public void setUp() throws Exception {
- super.setup();
- verifier = new DynamicDuoContextJWTClaimsVerifier();
-
- }
-
- /**
- * Test component can not be altered after initialisation
- *
- * @throws ComponentInitializationException on error.
- */
- @Test(expectedExceptions = UnmodifiableComponentException.class)
- public final void testSetupAfterInit() throws ComponentInitializationException {
- verifier.initialize();
- verifier.setIssuerPath("/test");
- }
-
-
- /**
- * Test blank duo integration.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testBlankDuoIntegration() throws BadJWTException, ParseException {
- addDuoContext();
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
-
- /**
- * Blank the Authentication Context, this is fatal and should result in a bad JWT exception.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testBlankAuthenticationContext() throws BadJWTException, ParseException {
-
- final JWT duoToken = createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push");
-
- //blank Authentication Context, should throw.
- prc.removeSubcontext(AuthenticationContext.class);
-
- verifier.verify(duoToken.getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
- /**
- * Blank the Duo Context, this is fatal and should result in a bad JWT exception.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testBlankDuoContext() throws BadJWTException, ParseException {
-
- final JWT duoToken = createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push");
-
- verifier.verify(duoToken.getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
- /**
- * Test if no security context is input - and is required.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testBlankSecurityContext() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "incorrect-issuer", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), null);
- }
-
- /**
- * Validate a token which has the incorrect issuer.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidIssuer() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "incorrect-issuer", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * If the username field does not exist in the claimsset, expect and error.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testClaimDoesNotExist() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- //set the preferred name to a claim that does not exist
- verifier.setUsernameClaim("not_a_claim");
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
- /**
- * Validate a token which has the incorrect audience.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidAudience() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,
- "Login Succesful","wrong-client-audience",
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
-
-
- /**
- * Validate a token which has the incorrect subject.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidSubject() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- //override subject
- dc.setUsername("wrong-subject");
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate a token which was issued too far in the past.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidIssuedAtPast() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now().minus(10,ChronoUnit.MINUTES), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate a token which was issued too far in the future.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidIssuedAtFuture() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
-
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now().plus(10,ChronoUnit.MINUTES), Instant.now(),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate a token which was outside (in the future)
- * the authentication lifetime window when using forced authn.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidAuthenticationTimeFuture() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- ac.setForceAuthn(true);
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().plus(10,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate a token which was outside (expired) the authentication lifetime window when using forced authn.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error. Expected.
- */
- @Test(expectedExceptions = BadJWTException.class)
- public final void testInvalidAuthenticationTimePast() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- ac.setForceAuthn(true);
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
- }
-
- /**
- * Validate a token which is inside the authentication lifetime window when using forced authn
- * even though it was performed 2 minutes in the past.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test
- public final void testValidAuthenticationWithinWindow() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- ac.setForceAuthn(true);
- //set a lifetime
- verifier.setAuthnLifetime(Duration.ofMinutes(5));
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().minus(2,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
- /**
- * Validate a token which is outside the authentication lifetime window but authn is not forced.
- *
- * @throws ParseException on error.
- * @throws BadJWTException on error.
- */
- @Test
- public final void testInvalidAuthenticationOutsideWindowNotForced() throws BadJWTException, ParseException {
- addDuoContext();
- addDuoIntegrationToContext();
- addAttemptedFlow("authn/DuoOIDC");
- ac.setForceAuthn(false);
- dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
- Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES),
- "api.duosecurity.com", "duo_push"));
-
- verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
- }
-
-
-
-
-
-}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
index b4685b9..e6c9d31 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
@@ -49,6 +49,8 @@ import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
/**
@@ -76,9 +78,9 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
public final void testValidToken() throws ComponentInitializationException {
//default mock will just return
- final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier =
- Mockito.mock(JWTClaimsSetVerifier.class);
- action.setClaimsVerifier(verifier);
+ final JWTClaimsValidation verifier =
+ Mockito.mock(JWTClaimsValidation.class);
+ action.setClaimsValidator(verifier);
addDuoContext();
addDuoIntegrationToContext();
@@ -100,14 +102,16 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
* @throws BadJWTException on error.
*/
@Test
- public final void testInValidToken() throws ComponentInitializationException, BadJWTException {
+ public final void testInValidToken() throws ComponentInitializationException, JWTValidationException {
//default mock will always throw a validation exception
- final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier =
- Mockito.mock(JWTClaimsSetVerifier.class);
- Mockito.doThrow(new BadJWTException("Invalid JWT")).when(verifier).
- verify(Mockito.any(JWTClaimsSet.class), Mockito.any());
- action.setClaimsVerifier(verifier);
+ //default mock will just return
+ final JWTClaimsValidation verifier =
+ Mockito.mock(JWTClaimsValidation.class);
+
+ Mockito.doThrow(new JWTValidationException("Invalid JWT")).when(verifier).
+ validate(Mockito.any(JWTClaimsSet.class), Mockito.any());
+ action.setClaimsValidator(verifier);
addDuoContext();
addDuoIntegrationToContext();
@@ -131,9 +135,9 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
public final void testBlankJWT() throws ComponentInitializationException {
//default mock will just return
- final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier =
- Mockito.mock(JWTClaimsSetVerifier.class);
- action.setClaimsVerifier(verifier);
+ final JWTClaimsValidation verifier =
+ Mockito.mock(JWTClaimsValidation.class);
+ action.setClaimsValidator(verifier);
addDuoContext();
addDuoIntegrationToContext();
diff --git a/idp-duo-nimbus-client-impl/pom.xml b/idp-duo-nimbus-client-impl/pom.xml
index 80077ad..e648999 100644
--- a/idp-duo-nimbus-client-impl/pom.xml
+++ b/idp-duo-nimbus-client-impl/pom.xml
@@ -22,12 +22,7 @@
<dependency>
<groupId>net.shibboleth.idp.plugin</groupId>
<artifactId>idp-plugin-duo-api</artifactId>
- </dependency>
- <dependency>
- <groupId>com.nimbusds</groupId>
- <artifactId>nimbus-jose-jwt</artifactId>
- <scope>compile</scope>
- </dependency>
+ </dependency>
<!-- provided dependencies -->
<dependency>
@@ -45,6 +40,11 @@
<artifactId>httpcore</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- Needed for module/plugin impls. -->
<dependency>
diff --git a/pom.xml b/pom.xml
index 62d15ca..4a448a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,8 +26,8 @@
<opensaml.groupId>org.opensaml</opensaml.groupId>
<opensaml.version>4.1.0-SNAPSHOT</opensaml.version>
<java-support.version>8.2.0-SNAPSHOT</java-support.version>
- <odic.common.crypto.version>0.0.1-SNAPSHOT</odic.common.crypto.version>
- <nimbus.version>9.0</nimbus.version>
+ <odic.common.crypto.version>0.0.1-SNAPSHOT</odic.common.crypto.version>
+ <oidc.common.version>0.0.1-SNAPSHOT</oidc.common.version>
<checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
</properties>
@@ -93,6 +93,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
+ <!-- OIDC Common BOM when importing OIDC dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-parent</artifactId>
+ <version>${oidc.common.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
<!-- Provided support dependencies -->
<dependency>
<groupId>net.shibboleth.utilities</groupId>
@@ -111,28 +119,6 @@
<artifactId>idp-plugin-duo-api</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>net.shibboleth.idp.plugin</groupId>
- <artifactId>idp-plugin-duo-native-client-impl</artifactId>
- <version>${project.version}</version>
- </dependency>
- <!-- commons OIDC -->
- <dependency>
- <groupId>net.shibboleth.oidc</groupId>
- <artifactId>oidc-common-crypto-api</artifactId>
- <version>${odic.common.crypto.version}</version>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.oidc</groupId>
- <artifactId>oidc-common-crypto-impl</artifactId>
- <version>${odic.common.crypto.version}</version>
- </dependency>
- <!-- review this dep when we move to central oidc-commons-lib -->
- <dependency>
- <groupId>com.nimbusds</groupId>
- <artifactId>nimbus-jose-jwt</artifactId>
- <version>${nimbus.version}</version>
- </dependency>
<!-- test bom dependencies -->
<dependency>
<groupId>${idp.groupId}</groupId>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list