[java-plugin-shibd-oidc] branch main updated: Add more tests, cleanup existing tests, move tests to correct packages
Codeberg
noreply at shibboleth.net
Tue May 12 08:07:12 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/11f1a1e147a634f5d79111a4ee4f23c6c36d3a32
The following commit(s) were added to refs/heads/main by this push:
new 11f1a1e Add more tests, cleanup existing tests, move tests to correct packages
11f1a1e is described below
commit 11f1a1e147a634f5d79111a4ee4f23c6c36d3a32
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue May 12 09:06:57 2026 +0100
Add more tests, cleanup existing tests, move tests to correct packages
- Some of these are taken from the RP-Proxy, and may end up in
oidc-commons, but at least for now we are increasing test coverage on
the shibd-oidc-sp
- Some Javadoc improvments
---
pom.xml | 1 -
sp-oidc-api/pom.xml | 60 ++-
.../AbstractAuthenticationLookupStrategyTest.java | 9 -
...nticationContextClassRefLookupStrategyTest.java | 7 +-
.../DisplayParameterLookupStrategyTest.java | 7 +-
.../ForceAuthnParameterLookupStrategyTest.java | 7 +-
.../config/navigate/PromptLookupStrategyTest.java | 7 +-
.../RequestedClaimsLookupStrategyTest.java | 7 +-
.../navigate/ResourceLookupStrategyTest.java | 7 +-
.../config/navigate/ScopeLookupStrategyTest.java | 7 +-
.../navigate/UiLocalesLookupStrategyTest.java | 7 +-
.../shibboleth/sp/oidc/testing/TestConstants.java | 123 +++++
.../net/shibboleth/sp/oidc/testing/TestHelper.java | 180 ++++++++
sp-oidc-bom/pom.xml | 7 +-
sp-oidc-conf-impl/pom.xml | 5 +-
.../flows/AbstractOIDCTokenConsumerFlowTest.java | 66 +--
.../oidc/flows/OIDCSessionInitiatorFlowTest.java | 1 +
.../sp/oidc/flows/OIDCTokenConsumerFlowTest.java | 119 ++---
...DCTokenConsumerFlowUsingStorageServiceTest.java | 14 +-
.../shibboleth/sp/oidc/flows/TestConstants.java | 186 --------
sp-oidc-impl/pom.xml | 13 +-
.../profile/impl/ExchangeCodeForAccessToken.java | 3 +-
.../sp/oidc/profile/impl/ExtractOIDCClaims.java | 13 +
...SetCorrelationCookieValueToContextConsumer.java | 184 --------
.../profile/impl/BaseOIDCAuthenticationTest.java | 154 +++++++
.../oidc/profile/impl/BuildRequestObjectTest.java | 512 +++++++++++++++++++++
.../impl/CreateAuthenticationStateDataTest.java | 132 +++++-
.../impl/DefaultClaimMergingStrategyTest.java | 115 +++++
.../impl/ExchangeCodeForAccessTokenTest.java | 221 +++++++++
.../profile/impl/ProcessEndUserClaimsTest.java | 274 +++++++++++
.../profile/impl/UserInfoEndpointLookupTest.java | 229 +++++++++
.../impl/ValidateUserInfoJSONObjectClaimsTest.java | 193 ++++++++
.../resources/metadata/openid-configuration.json | 60 +++
sp-oidc-testing/.checkstyle | 10 -
sp-oidc-testing/pom.xml | 78 ----
35 files changed, 2369 insertions(+), 649 deletions(-)
diff --git a/pom.xml b/pom.xml
index 6d455cc..b376b21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,7 +52,6 @@ copyright/license information. -->
<module>sp-oidc-api</module>
<module>sp-oidc-impl</module>
<module>sp-oidc-dist</module>
- <module>sp-oidc-testing</module>
</modules>
<dependencies>
diff --git a/sp-oidc-api/pom.xml b/sp-oidc-api/pom.xml
index 2057fd7..7600871 100644
--- a/sp-oidc-api/pom.xml
+++ b/sp-oidc-api/pom.xml
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- See LICENSE.txt file in the root directory of this repository for the copyright/license information. -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<!-- See LICENSE.txt file in the root directory of this repository for the
+copyright/license information. -->
+<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>
@@ -8,17 +10,18 @@
<artifactId>sp-oidc-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
-
+
<artifactId>sp-oidc-api</artifactId>
<description>SP OIDC API.</description>
<name>Shibboleth SP :: OIDC Protocol Plugin :: API</name>
<packaging>jar</packaging>
-
+
<properties>
- <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
+ <checkstyle.configLocation>
+ ${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
<automatic.module.name>net.shibboleth.sp.oidc</automatic.module.name>
</properties>
-
+
<dependencies>
<!-- Compile dependencies -->
@@ -27,20 +30,20 @@
<groupId>${shibd.groupId}</groupId>
<artifactId>sp-server-api</artifactId>
<scope>provided</scope>
- </dependency>
-
+ </dependency>
+
<dependency>
<groupId>${idp.groupId}</groupId>
<artifactId>idp-authn-api</artifactId>
<scope>provided</scope>
</dependency>
-
+
<dependency>
<groupId>${shib-profile.groupId}</groupId>
<artifactId>shib-profile-api</artifactId>
<scope>provided</scope>
</dependency>
-
+
<dependency>
<groupId>${shib-attribute.groupId}</groupId>
<artifactId>shib-attribute-api</artifactId>
@@ -61,7 +64,7 @@
<artifactId>shib-metadata-impl</artifactId>
<scope>provided</scope>
</dependency>
-
+
<dependency>
<groupId>${oidc-common.groupId}</groupId>
<artifactId>oidc-common-profile-api</artifactId>
@@ -77,10 +80,10 @@
<artifactId>oidc-common-metadata-api</artifactId>
<scope>provided</scope>
</dependency>
-
+
<!-- Test Dependencies -->
-
- <dependency>
+
+ <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
@@ -100,7 +103,34 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
-
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jdk8</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
</project>
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AbstractAuthenticationLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AbstractAuthenticationLookupStrategyTest.java
index c7cba55..0768887 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AbstractAuthenticationLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AbstractAuthenticationLookupStrategyTest.java
@@ -14,8 +14,6 @@
package net.shibboleth.sp.oidc.profile.config.navigate;
-import java.nio.charset.StandardCharsets;
-
import javax.annotation.Nonnull;
import org.opensaml.messaging.context.MessageContext;
@@ -37,13 +35,6 @@ import net.shibboleth.sp.ddf.DDF;
* An abstract test class for lookup functions that work to build authentication requests.
*/
public abstract class AbstractAuthenticationLookupStrategyTest {
-
- /** Resource URL. */
- public static final byte[] RESOURCE_URL = "https://sp.example.org/secure".getBytes(StandardCharsets.UTF_8);
-
- /** Redirect URI. */
- @Nonnull
- public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/callback";
protected RequestContext rc;
protected MessageContext mc;
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AuthenticationContextClassRefLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AuthenticationContextClassRefLookupStrategyTest.java
index 1856fc2..0142480 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AuthenticationContextClassRefLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/AuthenticationContextClassRefLookupStrategyTest.java
@@ -17,6 +17,7 @@ package net.shibboleth.sp.oidc.profile.config.navigate;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import org.testng.annotations.BeforeMethod;
@@ -31,7 +32,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
-import net.shibboleth.sp.oidc.profile.config.navigate.AuthenticationContextClassRefLookupStrategy;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -54,8 +55,8 @@ public class AuthenticationContextClassRefLookupStrategyTest extends AbstractAut
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/DisplayParameterLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/DisplayParameterLookupStrategyTest.java
index 23c382e..5c37549 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/DisplayParameterLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/DisplayParameterLookupStrategyTest.java
@@ -18,6 +18,8 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
+import java.nio.charset.StandardCharsets;
+
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -25,6 +27,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -47,8 +50,8 @@ public class DisplayParameterLookupStrategyTest extends AbstractAuthenticationLo
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ForceAuthnParameterLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ForceAuthnParameterLookupStrategyTest.java
index 669a96b..b607d33 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ForceAuthnParameterLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ForceAuthnParameterLookupStrategyTest.java
@@ -18,6 +18,8 @@ import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -26,6 +28,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -48,8 +51,8 @@ public class ForceAuthnParameterLookupStrategyTest extends AbstractAuthenticatio
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/PromptLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/PromptLookupStrategyTest.java
index dbf243f..9ea3fa4 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/PromptLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/PromptLookupStrategyTest.java
@@ -19,6 +19,8 @@ import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -28,6 +30,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -49,8 +52,8 @@ public class PromptLookupStrategyTest extends AbstractAuthenticationLookupStrate
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/RequestedClaimsLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/RequestedClaimsLookupStrategyTest.java
index bbf8d6e..8f80894 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/RequestedClaimsLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/RequestedClaimsLookupStrategyTest.java
@@ -18,6 +18,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.testng.annotations.BeforeMethod;
@@ -30,7 +31,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.FunctionSupport;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
-import net.shibboleth.sp.oidc.profile.config.navigate.RequestedClaimsLookupStrategy;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -53,8 +54,8 @@ public class RequestedClaimsLookupStrategyTest extends AbstractAuthenticationLoo
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ResourceLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ResourceLookupStrategyTest.java
index beed47a..08f7e97 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ResourceLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ResourceLookupStrategyTest.java
@@ -19,6 +19,7 @@ import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import org.testng.annotations.BeforeMethod;
@@ -29,7 +30,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
-import net.shibboleth.sp.oidc.profile.config.navigate.ResourceLookupStrategy;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -52,8 +53,8 @@ public class ResourceLookupStrategyTest extends AbstractAuthenticationLookupStra
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ScopeLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ScopeLookupStrategyTest.java
index 8d7603c..2263c52 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ScopeLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/ScopeLookupStrategyTest.java
@@ -18,6 +18,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
import java.util.Set;
import org.testng.annotations.BeforeMethod;
@@ -29,7 +30,7 @@ import net.shibboleth.shared.logic.FunctionSupport;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
-import net.shibboleth.sp.oidc.profile.config.navigate.ScopeLookupStrategy;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -52,8 +53,8 @@ public class ScopeLookupStrategyTest extends AbstractAuthenticationLookupStrateg
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/UiLocalesLookupStrategyTest.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/UiLocalesLookupStrategyTest.java
index 7b14d94..ca8a595 100644
--- a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/UiLocalesLookupStrategyTest.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/profile/config/navigate/UiLocalesLookupStrategyTest.java
@@ -18,6 +18,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import org.testng.annotations.BeforeMethod;
@@ -27,7 +28,7 @@ import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
-import net.shibboleth.sp.oidc.profile.config.navigate.UiLocalesLookupStrategy;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
@@ -50,8 +51,8 @@ public class UiLocalesLookupStrategyTest extends AbstractAuthenticationLookupStr
ddf = new DDF(null).structure();
ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ ddf.addmember(InitiatorConstants.RESPONSE_URL).string(TestConstants.RESPONSE_URL);
+ ddf.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
addDDFToAgentRequestContext(ddf);
}
diff --git a/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java
new file mode 100644
index 0000000..3c88442
--- /dev/null
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestConstants.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.testing;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/** A class to hold constants used for tests.*/
+public final class TestConstants {
+
+
+ /** The client_id to use, must match the issuer in oidc-test-agents.xml.*/
+ @Nonnull @NotEmpty public static final String CLIENT_ID = "testspclientid";
+
+ /** Flow ID. */
+ @Nonnull public static final String FLOW_ID = "sp/token-consumer";
+
+ /** Issuer. */
+ @Nonnull public static final String ISSUER = "https://op.example.org";
+
+ /** REDIRECT URI. */
+ @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/callback";
+
+ /** Resource URL. */
+ @Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
+
+ /** The nonce in the ID token.*/
+ @Nonnull public static final String ID_TOKEN_NONCE = "bd1b5f211250c57e";
+
+ /** The key to the storage record that gets held in the request state cookie set by the Storage Service.*/
+ @Nonnull public static final String STATE_COOKIE_STORAGE_KEY = "c516767a37f59fe32e049ac1cb1a8921";
+
+ /** State token string used in cookie names. */
+ public static final String STATE_TOKEN = "1761316967710_1622a5c726da8f7b36e24f19eed82aea";
+
+ /** Application ID used in cookie names. */
+ public static final String APPLICATION_ID = "test-oidc-application-with-ro";
+
+ /** Application ID used when private_key_jwt has been configured.*/
+ public static final String APPLICATION_ID_PRIVATE_KEY_JWT = "test-oidc-application-with-ro-private-key-jwt";
+
+ /** Application ID used when the form_post response mode has been configured.*/
+ public static final String APPLICATION_ID_POST_RESPONSE_MODE = "test-oidc-application-with-post-responsemode";
+
+ /** Target URL encoded in cookie. */
+ public static final String TARGET_URL = "https://sp.example.org/secure";
+
+ /** The defaulted prefix for state tokens.*/
+ public static final String STATE_TOKEN_PREFIX = "__Host-shibsp_state_";
+
+ /** Real Token response from OpenID connect tests.*/
+ @Nonnull
+ @NotEmpty
+ public static final String ACCESS_TOKEN_RESPONSE = """
+ {
+ "access_token": "W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji",
+ "refresh_token": "W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji",
+ "token_type": "Bearer",
+ "expires_in": 3600,
+ "id_token": "eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0Yzg\
+ iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI\
+ 6InVzZXItc3ViamVjdC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6\
+ XC9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3RcL2FcL3Rlc3RfcnBfcHJveHlcLy\
+ IsImV4cCI6MTY0MzI3NTQyNCwiaWF0IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQendFCqm9Pf\
+ 6Xpd4KtnHcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEatixDJWl2oRasivNCJyl6ZNeUG4YuT8S7xlgp\
+ UumoycBxrmomS7wgZSola8oZo8BuiRKlWzqLLtFkAChGi5iJEtHfVBe3k3PyQAsWRQdtixkvdxvyLq\
+ FLznIaPmMH-OZ82w7sJf4bpalZSAaR5NLpyzLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56vCE\
+ o8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-CAnHtESZ9w7JIfLLX1YlY73g",
+ "scope": "openid"
+ }
+ """;
+
+ /** Mock response from the token endpoint when an error occurs.*/
+ @Nonnull
+ @NotEmpty
+ public static final String TOKEN_RESPONSE_ERROR = """
+ {
+ "error": "invalid_request",
+ "error_description": "this request was bad"
+ }
+ """;
+
+ /** Mock response from the UserInfo endpoint when an error occurs.*/
+ @Nonnull @NotEmpty
+ public static final String USERINFO_RESPONSE_ERROR = """
+ Bearer realm="example.com",
+ error="invalid_token",
+ error_description="The access token expired"
+ """;
+
+
+ /** Mock Plain response from the UserInfo endpoint.*/
+ @Nonnull @NotEmpty
+ public static final String USERINFO_RESPONSE = """
+ {
+ "sub": "248289761001",
+ "name": "Jane Doe",
+ "given_name": "Jane",
+ "family_name": "Doe",
+ "preferred_username": "j.doe",
+ "email": "janedoe at example.com",
+ "picture": "http://example.com/janedoe/me.jpg"
+ }
+ """;
+
+ /** Private constructor.*/
+ private TestConstants() {
+
+ }
+}
diff --git a/sp-oidc-testing/src/main/java/net/shibboleth/sp/oidc/testing/TestHelper.java b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
similarity index 69%
rename from sp-oidc-testing/src/main/java/net/shibboleth/sp/oidc/testing/TestHelper.java
rename to sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
index 9c77fbb..123e635 100644
--- a/sp-oidc-testing/src/main/java/net/shibboleth/sp/oidc/testing/TestHelper.java
+++ b/sp-oidc-api/src/test/java/net/shibboleth/sp/oidc/testing/TestHelper.java
@@ -13,11 +13,17 @@
*/
package net.shibboleth.sp.oidc.testing;
+import static org.testng.Assert.fail;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
+import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
@@ -29,8 +35,14 @@ import javax.annotation.Nullable;
import org.opensaml.security.credential.Credential;
import org.testng.Assert;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
@@ -54,10 +66,18 @@ import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
import net.shibboleth.sp.profile.ConsumerConstants;
/** A helper class for testing.*/
@@ -368,4 +388,164 @@ public final class TestHelper {
+ "}";
}
+
+ /**
+ * Build cookie header bytes for the given parameters. These take the form of:
+ * <pre>
+ * __Host-shibsp_state_{appId}_{stateToken}={contents};
+ * </pre>
+ *
+ * @param stateToken the token value used for mapping the OAuth 2.0 state to the cookie
+ * @param appID the application ID
+ * @param stateCookieContents the contents of the state cookie, either the authentication request state JSON
+ * or a key to the authentication request state in the storage manager
+ * @param urlEncode do the contents need URL encoding?
+ * @return the cookie header bytes
+ * @throws Exception on error
+ */
+ public static byte[] buildCookieHeader(final String stateToken, final String appID,
+ final String stateCookieContents, final boolean urlEncode) throws Exception {
+
+ String contentsEncoded = null;
+ if (urlEncode) {
+ contentsEncoded =
+ Base64Support.encodeURLSafe(stateCookieContents.getBytes(StandardCharsets.UTF_8));
+ } else {
+ contentsEncoded = stateCookieContents;
+ }
+
+ // Compose the header with semicolons and spacing as in the example
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(TestConstants.STATE_TOKEN_PREFIX).append(appID).append('_').append(stateToken)
+ .append('=').append(contentsEncoded).append("; \n");
+
+ System.out.println(sb.toString());
+ return sb.toString().getBytes("UTF-8");
+
+ }
+
+
+ /**
+ * Build the authentication request state data for the given parameters.
+ *
+ * @param maxAge the maximum age of the authentication request, used for validation of the authentication response
+ * @param authTimeRequired a flag whether the authentication time is required, used for validation of the
+ * authentication response
+ * @param acrs the ACRs to request, used for validation of the authentication response. May be null if no
+ * ACRs are requested.
+ * @return
+ */
+ public static AuthenticationRequestStateData buildAuthenticationRequestStateData(
+ final Duration maxAge, final boolean authTimeRequired,
+ @Nullable final List<String> acrs) {
+ final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
+ .setNonce(TestConstants.ID_TOKEN_NONCE)
+ .setMaxAge(maxAge)
+ .setAuthTimeRequired(authTimeRequired);
+ state.setIssuer(TestConstants.CLIENT_ID)
+ .setAuthenticationAuthority(TestConstants.ISSUER)
+ .setResponseLocation(TestConstants.RESPONSE_URL)
+ .setRequestTime(Instant.now())
+ .setRawResource(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+
+ if (acrs != null) {
+ state.setAcrs(acrs);
+ }
+ return state;
+ }
+
+
+ /**
+ * Build the authentication request state JSON for the given parameters. This is used for the state value, and
+ * contains important information about the authentication request.
+ *
+ * @param maxAge the maximum age of the authentication request, used for validation of the authentication response
+ * @param authTimeRequired a flag whether the authentication time is required, used for validation of the
+ * authentication response
+ * @param acrs the ACRs to request, used for validation of the authentication response. May be null if no
+ * ACRs are requested.
+ * @return the authentication request state JSON for the given parameters
+ * @throws URISyntaxException on error.
+ */
+ public static String buildAuthenticationState(final Duration maxAge, final boolean authTimeRequired,
+ @Nullable final List<String> acrs) throws URISyntaxException {
+
+ final var state = buildAuthenticationRequestStateData(maxAge, authTimeRequired, acrs);
+ final ObjectMapper jsonMapper = JsonMapper.builder()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .serializationInclusion(Include.NON_ABSENT)
+ .defaultBase64Variant(Base64Variants.MODIFIED_FOR_URL)
+ .addModule(new Jdk8Module())
+ .addModule(new JavaTimeModule())
+ .build();
+
+ try {
+ return jsonMapper.writeValueAsString(state);
+ } catch (final JsonProcessingException e) {
+ fail(e.getMessage());
+ }
+ return "";
+ }
+
+
+ /**
+ * Builds a dummy OIDC authorization code response.
+ *
+ * @param redirectURI
+ * redirect URI
+ * @param mode
+ * response mode
+ *
+ * @return authentication success response for the token consumer flow
+ */
+ public static AuthenticationSuccessResponse buildOIDCAuthorizationCodeResponse(
+ @Nonnull final String redirect, @Nonnull final ResponseMode mode, final String stateString) {
+
+ final AuthorizationCode code = new AuthorizationCode("dummy-auth-code");
+ final State state = new State(stateString);
+ URI redirectURI = null;
+ try {
+ redirectURI = new URI(redirect);
+ } catch (final URISyntaxException e) {
+ fail("Bad redirect URI", e);
+ }
+
+ // Build a dummy success response (authorization code)
+ return new AuthenticationSuccessResponse(
+ redirectURI,
+ code,
+ null,
+ null,
+ state,
+ null,
+ mode
+ );
+ }
+
+
+ /**
+ * Build a invalid_request error response, from openID connect core.
+ *
+ * @return the error response
+ */
+ public static AuthenticationErrorResponse buildErrorResponse(final String stateString) {
+
+ final URI redirectURI = URI.create(TestConstants.RESPONSE_URL);
+ final State state = new State(stateString);
+
+ final ErrorObject error = new ErrorObject(
+ "invalid_request",
+ "Unsupported response_type value",
+ 400,
+ null);
+
+ return new AuthenticationErrorResponse(
+ redirectURI,
+ error,
+ state,
+ ResponseMode.QUERY);
+
+ }
+
}
diff --git a/sp-oidc-bom/pom.xml b/sp-oidc-bom/pom.xml
index 1cd15dd..65ec242 100644
--- a/sp-oidc-bom/pom.xml
+++ b/sp-oidc-bom/pom.xml
@@ -34,12 +34,7 @@
<artifactId>sp-oidc-conf-impl</artifactId>
<version>${project.version}</version>
</dependency>
-
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sp-oidc-testing</artifactId>
- <version>${project.version}</version>
- </dependency>
+
</dependencies>
</dependencyManagement>
diff --git a/sp-oidc-conf-impl/pom.xml b/sp-oidc-conf-impl/pom.xml
index 91c9944..315a27a 100644
--- a/sp-oidc-conf-impl/pom.xml
+++ b/sp-oidc-conf-impl/pom.xml
@@ -196,12 +196,13 @@
<version>${shibd.version}</version>
<type>test-jar</type>
<scope>test</scope>
- </dependency>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
- <artifactId>sp-oidc-testing</artifactId>
+ <artifactId>sp-oidc-api</artifactId>
<version>${project.version}</version>
+ <type>test-jar</type>
<scope>test</scope>
</dependency>
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
index d7900e2..77a07b9 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
@@ -19,7 +19,6 @@ import static org.testng.Assert.fail;
import java.io.IOException;
import java.net.URI;
-import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.Instant;
@@ -54,18 +53,14 @@ import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.oauth2.sdk.AuthorizationCode;
import com.nimbusds.oauth2.sdk.AuthorizationResponse;
import com.nimbusds.oauth2.sdk.ErrorObject;
-import com.nimbusds.oauth2.sdk.ResponseMode;
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
import com.nimbusds.oauth2.sdk.TokenResponse;
-import com.nimbusds.oauth2.sdk.id.State;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
-import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
@@ -84,6 +79,7 @@ import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.flows.AbstractSPFlowTest;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.oidc.testing.TestHelper;
import net.shibboleth.sp.profile.ConsumerConstants;
@@ -195,7 +191,6 @@ public abstract class AbstractOIDCTokenConsumerFlowTest extends AbstractSPFlowTe
final ErrorObject error = new ErrorObject(code, description, httpCode);
final TokenErrorResponse errorResponse = new TokenErrorResponse(error);
-
return errorResponse;
}
@@ -418,65 +413,6 @@ public abstract class AbstractOIDCTokenConsumerFlowTest extends AbstractSPFlowTe
}
- /**
- * Builds a dummy OIDC authorization code response.
- *
- * @param redirectURI
- * redirect URI
- * @param mode
- * response mode
- *
- * @return authentication success response for the token consumer flow
- */
- protected AuthenticationSuccessResponse buildOIDCAuthorizationCodeResponse(
- @Nonnull final String redirect, @Nonnull final ResponseMode mode, final String stateString) {
-
- final AuthorizationCode code = new AuthorizationCode("dummy-auth-code");
- final State state = new State(stateString);
- URI redirectURI = null;
- try {
- redirectURI = new URI(redirect);
- } catch (final URISyntaxException e) {
- fail("Bad redirect URI", e);
- }
-
- // Build a dummy success response (authorization code)
- return new AuthenticationSuccessResponse(
- redirectURI,
- code,
- null,
- null,
- state,
- null,
- mode
- );
- }
-
- /**
- * Build a invalid_request error response, from openID connect core.
- *
- * @return the error response
- */
- protected AuthenticationErrorResponse buildErrorResponse(final String stateString) {
-
- final URI redirectURI = URI.create(TestConstants.RESPONSE_URL);
- final State state = new State(stateString);
-
- final ErrorObject error = new ErrorObject(
- "invalid_request",
- "Unsupported response_type value",
- 400,
- null);
-
- return new AuthenticationErrorResponse(
- redirectURI,
- error,
- state,
- ResponseMode.QUERY);
-
- }
-
-
/**
* Encodes an OIDC response into the query string of a redirect URI embedded in a remoted message.
*
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
index 8cf10fd..2c9123a 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
@@ -64,6 +64,7 @@ import net.shibboleth.sp.flows.AbstractSPFlowTest;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index 26f52a2..2e724ea 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -35,6 +35,7 @@ import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.testing.TestConstants;
import net.shibboleth.sp.oidc.testing.TestHelper;
/**
@@ -86,19 +87,20 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
@Test
public void testSuccess_SignedIDToken_PlainUserInfo() throws Exception {
- mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(Instant.now().plusSeconds(3600), Instant.now(), null, null),
+ mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(
+ Instant.now().plusSeconds(3600), Instant.now(), null, null),
constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -125,15 +127,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.FORM_POST,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.FORM_POST,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedFormPostResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID_POST_RESPONSE_MODE,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID_POST_RESPONSE_MODE, input);
@@ -155,19 +157,20 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
*/
@Test
public void testSuccess_SignedIDToken_PlainUserInfo_PrivateKeyJWT() throws Exception {
- mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(Instant.now().plusSeconds(3600), Instant.now(), null, null),
+ mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(
+ Instant.now().plusSeconds(3600), Instant.now(), null, null),
constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID_PRIVATE_KEY_JWT,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
// Use application which requires private_key_jwt
setApplicationRequest(TestConstants.APPLICATION_ID_PRIVATE_KEY_JWT, input);
@@ -194,15 +197,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
Map.of("auth_time",Date.from(Instant.now()))), constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
+ TestHelper.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -228,15 +231,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null), constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
+ TestHelper.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -260,15 +263,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
Instant.now(), null, null), constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -294,15 +297,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null, null), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -329,15 +332,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null, Map.of("acr","loa2")), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
+ TestHelper.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -366,15 +369,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null, Map.of("acr","loa1")), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
+ TestHelper.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -401,15 +404,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null, Map.of("azp", TestConstants.CLIENT_ID)), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -434,15 +437,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
null, Map.of("azp", "bad-azp")), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -467,15 +470,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
Instant.now(), "bad-idtoken-nonce", null), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -496,15 +499,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
@@ -526,15 +529,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
Instant.now(), TestConstants.ID_TOKEN_NONCE, null), constructJWTUserInfoResponseSigned());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -557,15 +560,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
@Test
public void testFail_StateValueMisMatch() throws Exception {
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
"different-state");
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -584,13 +587,13 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
*/
@Test
public void testFail_ErrorResponse() throws Exception {
- final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(TestConstants.STATE_TOKEN));
+ final DDF input = buildRemotedQueryStringResponse(TestHelper.buildErrorResponse(TestConstants.STATE_TOKEN));
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -605,8 +608,8 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
/**
* Test error response from OP followed by a replayed success response. It should fail as the state has been
* replayed. That is, if the state originally sent that resulted in an error from the OP, where added to a
- * forged success response, it will not be allowed by the SP even if it matches to the one in stored state (a cookie
- * for example).
+ * forged success response, it will not be allowed by the SP even if it matches to the one in stored state
+ * (a cookie for example).
*
* @throws IOException on error
*/
@@ -614,13 +617,13 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
public void testFail_ErrorResponse_FollowedByReplayedSuccess() throws Exception {
// First create a failed response
- final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(TestConstants.STATE_TOKEN));
+ final DDF input = buildRemotedQueryStringResponse(TestHelper.buildErrorResponse(TestConstants.STATE_TOKEN));
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -635,15 +638,15 @@ public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest
// should fail early.
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF inputSuccessResponse = buildRemotedQueryStringResponse(response);
// Add cookies
- inputSuccessResponse.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ inputSuccessResponse.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.buildAuthenticationState(null, false, null), true));
+ TestHelper.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, inputSuccessResponse);
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
index 8c124ae..eb8b658 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
@@ -41,6 +41,8 @@ import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+import net.shibboleth.sp.oidc.testing.TestHelper;
import net.shibboleth.sp.state.impl.StorageServiceStateManager;
/**
@@ -98,7 +100,7 @@ public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTo
// Add an authentication record to the storage service
final AuthenticationRequestStateData stateData =
- TestConstants.buildAuthenticationRequestStateData(null, false, null);
+ TestHelper.buildAuthenticationRequestStateData(null, false, null);
final String serializedState = om.writeValueAsString(stateData);
final String encoded = Base64Support.encode(serializedState.getBytes(StandardCharsets.UTF_8), false);
@@ -108,7 +110,8 @@ public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTo
builder.append('!').append("testsp.example.org").append('!').append(TestConstants.APPLICATION_ID);
final String context = builder.toString();
storageService.create(context,
- TestConstants.STATE_COOKIE_STORAGE_KEY, encoded, Instant.now().plus(Duration.ofMinutes(30)).toEpochMilli());
+ TestConstants.STATE_COOKIE_STORAGE_KEY, encoded,
+ Instant.now().plus(Duration.ofMinutes(30)).toEpochMilli());
}
@@ -121,16 +124,17 @@ public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTo
@Test
public void testSuccess_SignedIDToken_PlainUserInfo() throws Exception {
- mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(Instant.now().plusSeconds(3600), Instant.now(), null, null),
+ mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(
+ Instant.now().plusSeconds(3600), Instant.now(), null, null),
constructJSONUserInfoResponse());
final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestHelper.buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ input.addmember("http.headers.Cookie").unsafe_string(TestHelper.buildCookieHeader(
TestConstants.STATE_TOKEN,
TestConstants.APPLICATION_ID,
TestConstants.STATE_COOKIE_STORAGE_KEY, false));
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
deleted file mode 100644
index f187e52..0000000
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.sp.oidc.flows;
-
-import static org.testng.Assert.fail;
-
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.core.Base64Variants;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
-
-/** A class to hold constants used for tests.*/
-public final class TestConstants {
-
- /** Private constructor.*/
- private TestConstants() {
-
- }
-
- /** The client_id to use, must match the issuer in oidc-test-agents.xml.*/
- @Nonnull @NotEmpty public static final String CLIENT_ID = "testspclientid";
-
- /** Flow ID. */
- @Nonnull public static final String FLOW_ID = "sp/token-consumer";
-
- /** Issuer. */
- @Nonnull public static final String ISSUER = "https://op.example.org";
-
- /** REDIRECT URI. */
- @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/callback";
-
- /** Resource URL. */
- @Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
-
- /** The nonce in the ID token.*/
- @Nonnull public static final String ID_TOKEN_NONCE = "bd1b5f211250c57e";
-
- /** The key to the storage record that gets held in the request state cookie set by the Storage Service.*/
- @Nonnull public static final String STATE_COOKIE_STORAGE_KEY = "c516767a37f59fe32e049ac1cb1a8921";
-
- /** State token string used in cookie names. */
- public static final String STATE_TOKEN = "1761316967710_1622a5c726da8f7b36e24f19eed82aea";
-
- /** Application ID used in cookie names. */
- public static final String APPLICATION_ID = "test-oidc-application-with-ro";
-
- /** Application ID used when private_key_jwt has been configured.*/
- public static final String APPLICATION_ID_PRIVATE_KEY_JWT = "test-oidc-application-with-ro-private-key-jwt";
-
- /** Application ID used when the form_post response mode has been configured.*/
- public static final String APPLICATION_ID_POST_RESPONSE_MODE = "test-oidc-application-with-post-responsemode";
-
- /** Target URL encoded in cookie. */
- public static final String TARGET_URL = "https://sp.example.org/secure";
-
- /** The defaulted prefix for state tokens.*/
- public static final String STATE_TOKEN_PREFIX = "__Host-shibsp_state_";
-
-
- /**
- * Build cookie header bytes for the given parameters. These take the form of:
- * <pre>
- * __Host-shibsp_state_{appId}_{stateToken}={contents};
- * </pre>
- *
- * @param stateToken the token value used for mapping the OAuth 2.0 state to the cookie
- * @param appID the application ID
- * @param stateCookieContents the contents of the state cookie, either the authentication request state JSON
- * or a key to the authentication request state in the storage manager
- * @param urlEncode TODO
- * @return the cookie header bytes
- * @throws Exception on error
- */
- public static byte[] buildCookieHeader(final String stateToken, final String appID,
- final String stateCookieContents, final boolean urlEncode) throws Exception {
-
- String contentsEncoded = null;
- if (urlEncode) {
- contentsEncoded =
- Base64Support.encodeURLSafe(stateCookieContents.getBytes(StandardCharsets.UTF_8));
- } else {
- contentsEncoded = stateCookieContents;
- }
-
- // Compose the header with semicolons and spacing as in the example
- final StringBuilder sb = new StringBuilder();
-
- sb.append(STATE_TOKEN_PREFIX).append(appID).append('_').append(stateToken)
- .append('=').append(contentsEncoded).append("; \n");
-
- System.out.println(sb.toString());
- return sb.toString().getBytes("UTF-8");
-
- }
-
- /**
- * Build the authentication request state data for the given parameters.
- *
- * @param maxAge the maximum age of the authentication request, used for validation of the authentication response
- * @param authTimeRequired a flag whether the authentication time is required, used for validation of the
- * authentication response
- * @param acrs the ACRs to request, used for validation of the authentication response. May be null if no
- * ACRs are requested.
- * @return
- */
- public static AuthenticationRequestStateData buildAuthenticationRequestStateData(
- final Duration maxAge, final boolean authTimeRequired,
- @Nullable final List<String> acrs) {
- final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
- .setNonce(ID_TOKEN_NONCE)
- .setMaxAge(maxAge)
- .setAuthTimeRequired(authTimeRequired);
- state.setIssuer(CLIENT_ID)
- .setAuthenticationAuthority(ISSUER)
- .setResponseLocation(RESPONSE_URL)
- .setRequestTime(Instant.now())
- .setRawResource(RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
-
- if (acrs != null) {
- state.setAcrs(acrs);
- }
- return state;
- }
-
-
- /**
- * Build the authentication request state JSON for the given parameters. This is used for the state value, and
- * contains important information about the authentication request.
- *
- * @param maxAge the maximum age of the authentication request, used for validation of the authentication response
- * @param authTimeRequired a flag whether the authentication time is required, used for validation of the
- * authentication response
- * @param acrs the ACRs to request, used for validation of the authentication response. May be null if no
- * ACRs are requested.
- * @return the authentication request state JSON for the given parameters
- * @throws URISyntaxException on error.
- */
- public static String buildAuthenticationState(final Duration maxAge, final boolean authTimeRequired,
- @Nullable final List<String> acrs) throws URISyntaxException {
-
- final var state = buildAuthenticationRequestStateData(maxAge, authTimeRequired, acrs);
- final ObjectMapper jsonMapper = JsonMapper.builder()
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .serializationInclusion(Include.NON_ABSENT)
- .defaultBase64Variant(Base64Variants.MODIFIED_FOR_URL)
- .addModule(new Jdk8Module())
- .addModule(new JavaTimeModule())
- .build();
-
- try {
- return jsonMapper.writeValueAsString(state);
- } catch (final JsonProcessingException e) {
- fail(e.getMessage());
- }
- return "";
- }
-}
diff --git a/sp-oidc-impl/pom.xml b/sp-oidc-impl/pom.xml
index 1e1c2a1..2b48b14 100644
--- a/sp-oidc-impl/pom.xml
+++ b/sp-oidc-impl/pom.xml
@@ -177,16 +177,17 @@
</dependency>
<dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sp-oidc-testing</artifactId>
- <version>${project.version}</version>
+ <groupId>${shibd.groupId}</groupId>
+ <artifactId>sp-server-impl</artifactId>
+ <version>${shibd.version}</version>
+ <type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>${shibd.groupId}</groupId>
- <artifactId>sp-server-impl</artifactId>
- <version>${shibd.version}</version>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sp-oidc-api</artifactId>
+ <version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
index 34c0d1d..0ad6505 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
@@ -31,7 +31,6 @@ import com.nimbusds.oauth2.sdk.TokenResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
@@ -45,7 +44,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
*
* @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
+ * @event {@link OidcEventIds#INVALID_TOKEN}
*
* @post Add the Access Token Response to the {@link AccessTokenResponseContext}.
*/
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
index 27e8976..4412505 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
@@ -73,6 +73,19 @@ import net.shibboleth.sp.profile.AbstractApplicationAction;
/**
* An action that extracts OIDC End-User claims from the combined id_token and UserInfo response claims in the
* {@link EndUserClaimsContext}.
+ *
+ * <p>Attributes decoded from the assertion(s) are in an unfiltered state and subject to the filtering
+ * service. Any other data extracted is stored directly in the filtered set.</p>
+ *
+ * <p>There are three supplemental sets: the optional "standard" set extracted from the message,
+ * an optional profile config-supplied strategy function, and an optional use of the {@link AttributeResolver}
+ * associated with the {@link Application}.</p>
+ *
+ * <p>The final set of attributes is stored back in the {@link AttributeContext} for use by later actions.</p>
+ *
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
*/
public class ExtractOIDCClaims extends AbstractApplicationAction {
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetCorrelationCookieValueToContextConsumer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetCorrelationCookieValueToContextConsumer.java
deleted file mode 100644
index bf546f1..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetCorrelationCookieValueToContextConsumer.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.sp.oidc.profile.impl;
-
-import java.nio.charset.StandardCharsets;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.minidev.json.JSONObject;
-import net.minidev.json.JSONValue;
-import net.minidev.json.parser.ParseException;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.PredicateSupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
-import net.shibboleth.sp.oidc.context.CorrelationCookieStateContext;
-
-
-
-/**
- * Consumer that decodes a correlation cookie value and stores it on a {@link CorrelationCookieStateContext}. The
- * cookie value is expected to be the raw value of the cookie, decoding happens here.
- *
- * <p>The supplied value is expected to represent a JSON object encoded either as:
- * <ul>
- * <li>a URL-safe Base64 representation of UTF-8 JSON, or</li>
- * <li>a URL-safe Base64 representation of UTF-8 JSON sealed using a {@link DataSealer}.</li>
- * </ul>
- *
- * <p>On failure, this consumer returns {@code false}.</p>
- */
- at ThreadSafeAfterInit
-public class SetCorrelationCookieValueToContextConsumer extends AbstractIdentifiableInitializableComponent
- implements BiFunction<ProfileRequestContext, String, Boolean> {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(SetCorrelationCookieValueToContextConsumer.class);
-
- /** The strategy to locate the {@link CorrelationCookieStateContext} to operate on. */
- @NonnullAfterInit
- private Function<ProfileRequestContext, CorrelationCookieStateContext> correlationCookieStateContextLookupStrategy;
-
- /** Optional data sealer to use. */
- @Nullable private DataSealer dataSealer;
-
- /**
- * If the dataSealer is provided should it be used to unwrap the correlation cookie state? Defaults to true,
- * that is, if the dataSealer is provided, always attempt to unwrap the state.
- */
- @Nonnull private Predicate<ProfileRequestContext> unsealState;
-
- /** Constructor.*/
- public SetCorrelationCookieValueToContextConsumer() {
- unsealState = PredicateSupport.alwaysTrue();
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (correlationCookieStateContextLookupStrategy == null) {
- throw new ComponentInitializationException("CorrelationCookieStateContextLookupStrategy "
- + "lookup strategy can not be null");
- }
- }
-
- /**
- * Set the strategy to locate the {@link CorrelationCookieStateContext} to operate on.
- *
- * @param strategy The strategy to set.
- */
- public void setCorrelationCookieStateContextLookupStrategy(
- final Function<ProfileRequestContext, CorrelationCookieStateContext> strategy) {
- checkSetterPreconditions();
- correlationCookieStateContextLookupStrategy = Constraint.isNotNull(strategy,
- "CorrelationCookieStateContextLookupStrategy can not be null");
- }
-
- /**
- * Sets {@link DataSealer} to use.
- *
- * @param sealer data sealer
- */
- public void setDataSealer(@Nullable final DataSealer sealer) {
- checkSetterPreconditions();
-
- dataSealer = sealer;
- }
-
- /**
- * Set the predicate to determine whether to unseal the state.
- *
- * @param predicate the seal state predicate to set.
- */
- public void setUnsealStatePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
- checkSetterPreconditions();
-
- unsealState = Constraint.isNotNull(predicate, "Seal state predicate can not be null");
- }
-
- /**
- * Set the flag to determine whether to unseal the state.
- *
- * @param flag the flag to set.
- */
- public void setUnsealState(final boolean flag) {
- checkSetterPreconditions();
-
- unsealState = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
- }
-
- /** {@inheritDoc} */
- @Override
- public Boolean apply(final ProfileRequestContext profileRequestContext, final String value) {
- // Value should be b64 encoded
- if (value == null || profileRequestContext == null) {
- return false;
- }
- final CorrelationCookieStateContext context =
- correlationCookieStateContextLookupStrategy.apply(profileRequestContext);
- if (context == null) {
- log.debug("Correlation cookie context not found, can not set correlation cookie onto the context");
- return false;
- }
- log.trace("Attempting to set correlation cookie onto the context '{}'", value);
- Object correlationCookieAsObject = null;
- try {
- final byte[] decoded = Base64Support.decodeURLSafe(value);
- final String decodedString = new String(decoded, StandardCharsets.UTF_8);
- log.trace("Attempting to set base64 URL decoded correlation cookie '{}'", decodedString);
- final var localDataSealer = dataSealer;
- if (localDataSealer != null && unsealState.test(profileRequestContext)) {
- log.trace("Attempting to unseal correlation cookie");
- final String unsealed = localDataSealer.unwrap(decodedString);
- correlationCookieAsObject = JSONValue.parseWithException(unsealed);
- } else {
- correlationCookieAsObject = JSONValue.parseWithException(decodedString);
- }
-
- } catch (final DataSealerException | DecodingException | ParseException e) {
- log.warn("Unable to process correlation cookie, was it sealed and you are not unsealing it?"
- , e);
- return false;
- }
- log.trace("Decoded correlation cookie as '{}'", correlationCookieAsObject);
- if (correlationCookieAsObject instanceof final JSONObject json) {
- context.setValue(json);
- } else {
- log.error("Decoded correlation cookie was not a JSON object");
- return false;
- }
-
- return true;
-
- }
-
-}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
new file mode 100644
index 0000000..442e412
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BaseOIDCAuthenticationTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.core.io.ClassPathResource;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+import net.shibboleth.sp.oidc.testing.TestHelper;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
+
+/** Abstract class for tests that require context setup appropriate for an OIDC request/response.*/
+public abstract class BaseOIDCAuthenticationTest extends BaseApplicationActionTest {
+
+ /** The peer entity context for outbound.*/
+ protected OIDCPeerEntityContext peerEntityCtxOutbound;
+
+ /** The peer entity context for inbound.*/
+ protected OIDCPeerEntityContext peerEntityCtxInbound;
+
+ /** The authentication request.*/
+ protected OIDCAuthenticationRequest authnRequest;
+
+ /** The RP config.*/
+ protected DefaultOIDCAuthorizationConfiguration partyConfig;
+
+ /** The OIDC provider metadata context for outbound.*/
+ @NonnullBeforeExec protected OIDCProviderMetadataContext providerCtxOutbound;
+
+ /** The OIDC provider metadata context for inbound.*/
+ @NonnullBeforeExec protected OIDCProviderMetadataContext providerCtxInbound;
+
+ /** The OP metadata to use.*/
+ private OIDCProviderMetadata metadata;
+
+
+ /**
+ * Setup the various contexts.
+ * @throws ComponentInitializationException
+ *
+ * @throws Exception on error
+ */
+ @Override
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+
+ try {
+ final var metadataFromFile = new ClassPathResource("metadata/openid-configuration.json");
+ final String json = new String(metadataFromFile.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
+ metadata = OIDCProviderMetadata.parse(JSONObjectUtils.parse(json));
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ }
+
+ // Set context for outbound messages
+ final MessageContext outMsgCtx = new MessageContext();
+ authnRequest = new OIDCAuthenticationRequest(new ClientID(TestConstants.CLIENT_ID));
+ outMsgCtx.setMessage(authnRequest);
+ prc.setOutboundMessageContext(outMsgCtx);
+ peerEntityCtxOutbound = outMsgCtx.ensureSubcontext(OIDCPeerEntityContext.class);
+ providerCtxOutbound = new OIDCProviderMetadataContext();
+ providerCtxOutbound.setProviderInformation(metadata);
+ assert providerCtxOutbound != null;
+ peerEntityCtxOutbound.addSubcontext(providerCtxOutbound);
+ outMsgCtx.addSubcontext(peerEntityCtxOutbound);
+
+ // Set context for inbound messages
+ final MessageContext inMsgCtx = new MessageContext();
+ inMsgCtx.setMessage(TestHelper.buildOIDCAuthorizationCodeResponse(
+ TestConstants.RESPONSE_URL, ResponseMode.QUERY, "state"));
+ // Add peer entity and metadata to inbound as well as outbound, depending on which side of the flow is tested
+ peerEntityCtxInbound = inMsgCtx.ensureSubcontext(OIDCPeerEntityContext.class);
+ providerCtxInbound = new OIDCProviderMetadataContext();
+ providerCtxInbound.setProviderInformation(metadata);
+ peerEntityCtxInbound.addSubcontext(providerCtxInbound);
+ inMsgCtx.addSubcontext(peerEntityCtxInbound);
+ prc.setInboundMessageContext(inMsgCtx);
+
+
+ }
+
+ /**
+ * Convert the given HTTP response to a JSONObject. This is used to convert the response from the token endpoint
+ * to a JSONObject for easier access to the values.
+ *
+ * @param response the HTTP response to convert.
+ * @return the converted JSONObject.
+ * @throws Exception on error.
+ */
+ protected JSONObject convertHttpResponseToJSONObject(final ClassicHttpResponse response) throws Exception {
+ final ObjectMapper mapper = new ObjectMapper();
+ final Map<String, Object> tokenResponseAsMap = mapper.readValue(
+ response.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
+ return new JSONObject(tokenResponseAsMap);
+ }
+
+
+ /**
+ * Get the outbound message context from the given PRC. If the PRC is null or the outbound message context
+ * is null, the assertions will fail.
+ *
+ * @param prc the profile request context
+ *
+ * @return the message context. Never null at that point.
+ */
+ @Nonnull protected MessageContext getOutboundMessageContextFailIfNull(
+ @Nullable final ProfileRequestContext prc){
+ assertNotNull(prc);
+ assert prc != null;
+ final var outboundMsgCtx = prc.getOutboundMessageContext();
+ assertNotNull(outboundMsgCtx);
+ assert outboundMsgCtx != null;
+ return outboundMsgCtx;
+ }
+
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObjectTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObjectTest.java
new file mode 100644
index 0000000..0fe2f12
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObjectTest.java
@@ -0,0 +1,512 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.List;
+
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
+import com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest;
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest;
+
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+
+/** Tests for the {@link BuildRequestObject} action.*/
+public class BuildRequestObjectTest extends BaseOIDCAuthenticationTest {
+
+ /** The action to test.*/
+ private BuildRequestObject action;
+
+ /** The RPC.*/
+ private RelyingPartyContext rpc;
+
+ /** The profile config.*/
+ private DefaultOIDCAuthorizationConfiguration oidcAuthzConfig;
+
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+ action = new BuildRequestObject();
+
+ rpc = prc.ensureSubcontext(RelyingPartyContext.class);
+ oidcAuthzConfig = new DefaultOIDCAuthorizationConfiguration();
+ final RelyingPartyConfiguration rpConfig = new BasicRelyingPartyConfiguration();
+ rpc.setProfileConfig(oidcAuthzConfig);
+ rpc.setConfiguration(rpConfig);
+ // Setup a basic authentication request
+ authnRequest.setNonce(new Nonce());
+ authnRequest.setResponseType(Constraint.isNotNull(ResponseType.CODE, "response code is null"));
+ authnRequest.setResponseMode(ResponseMode.QUERY);
+ authnRequest.setDefaultResponseMode(ResponseMode.QUERY);
+ authnRequest.setMaxAge(Duration.ofSeconds(60));
+ authnRequest.setProviderSupportsClaimsParameter(true);
+ try {
+ authnRequest.setRedirectURI(new URI(TestConstants.RESPONSE_URL));
+ } catch (final URISyntaxException e) {
+ throw new ComponentInitializationException(e);
+ }
+ }
+
+ /**
+ * Build a request object with the correct claims.
+ *
+ * @throws ComponentInitializationException on error.
+ */
+ @Test
+ public void testBuildRequestObject_Success() throws ComponentInitializationException {
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(
+ ((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Build a request object with the correct claims and custom claims.
+ *
+ * @throws ComponentInitializationException on error.
+ */
+ @Test
+ public void testBuildRequestObject_CustomClaims_Success() throws ComponentInitializationException {
+
+ action.setCustomClaimsStrategy((prc,set) -> {
+ set.setClaim("custom", "value");
+ });
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(
+ ((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getStringClaim("custom"));
+ assertEquals(claims.getStringClaim("custom"),"value");
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Build a request object with the correct set of ACRs.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRs_Success() throws ComponentInitializationException {
+
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+
+ final Object claimsObject = claims.getClaim("claims");
+ assertTrue(claimsObject instanceof OIDCClaimsRequest);
+ final OIDCClaimsRequest requestedClaims =(OIDCClaimsRequest) claimsObject;
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr"));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Test building acr_values when the claims claim is not supported.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRs_ClaimsNotSupported_Success() throws ComponentInitializationException {
+
+ authnRequest.setProviderSupportsClaimsParameter(false);
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNull(claims.getClaim("claims"));
+
+ // Now test acr_values
+ assertEquals(claims.getStringClaim("acr_values"),"urn:mace:incommon:iap:silver");
+ }
+
+ /**
+ * Test build a request object with requested claims and ACRs.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRsAndExistingRequestedClaims_Success() throws ComponentInitializationException {
+
+ final ClaimsSetRequest.Entry nameEntry =
+ new ClaimsSetRequest.Entry("first_name").withClaimRequirement(ClaimRequirement.ESSENTIAL);
+ final ClaimsSetRequest idTokenClaimsRequest = new ClaimsSetRequest().add(nameEntry);
+ final OIDCClaimsRequest requested =
+ new OIDCClaimsRequest().withIDTokenClaimsRequest(idTokenClaimsRequest);
+
+ authnRequest.setRequestedClaims(requested);
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+
+ final Object claimsObject = claims.getClaim("claims");
+ assertTrue(claimsObject instanceof OIDCClaimsRequest);
+ final OIDCClaimsRequest requestedClaims =(OIDCClaimsRequest) claimsObject;
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("first_name"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList());
+ assertEquals(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().get(0).toString(),"urn:mace:incommon:iap:silver");
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Test build a request object with ACRS when ACRs have been requested and claims configured.
+ * These should be merged in.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRsAndExistingACRs_Success() throws ComponentInitializationException {
+
+ // Add some to the existing requested claims set
+ final ClaimsSetRequest.Entry acrEntry =
+ new ClaimsSetRequest.Entry("acr").withValues(List.of(new ACR("urn:mace:incommon:iap:bronze")))
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL);
+ final ClaimsSetRequest idTokenClaimsRequest = new ClaimsSetRequest().add(acrEntry);
+ final OIDCClaimsRequest requested =
+ new OIDCClaimsRequest().withIDTokenClaimsRequest(idTokenClaimsRequest);
+
+ authnRequest.setRequestedClaims(requested);
+
+ // Now add ACRs
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver"), new ACR("urn:mace:incommon:iap:purple")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+
+ final Object claimsObject = claims.getClaim("claims");
+ assertTrue(claimsObject instanceof OIDCClaimsRequest);
+ final OIDCClaimsRequest requestedClaims =(OIDCClaimsRequest) claimsObject;
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList());
+ assertEquals(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList().size(),3);
+
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:bronze")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:silver")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:purple")));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Test build a request object with ACRS when ACRs have been requested and claims configured.
+ * These should be merged in.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRsAndExistingACRs_WithSingleValue_Success()
+ throws ComponentInitializationException {
+
+ // Add some to the existing requested claims set. Set using with Value, not Value(s)
+ final ClaimsSetRequest.Entry acrEntry =
+ new ClaimsSetRequest.Entry("acr").withValue(new ACR("urn:mace:incommon:iap:bronze"))
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL);
+ final ClaimsSetRequest idTokenClaimsRequest = new ClaimsSetRequest().add(acrEntry);
+ final OIDCClaimsRequest requested =
+ new OIDCClaimsRequest().withIDTokenClaimsRequest(idTokenClaimsRequest);
+
+ authnRequest.setRequestedClaims(requested);
+
+ // Now add ACRs
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver"), new ACR("urn:mace:incommon:iap:purple")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+
+ final Object claimsObject = claims.getClaim("claims");
+ assertTrue(claimsObject instanceof OIDCClaimsRequest);
+ final OIDCClaimsRequest requestedClaims =(OIDCClaimsRequest) claimsObject;
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList());
+ assertEquals(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList().size(),3);
+
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:bronze")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:silver")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:purple")));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Test build ACRs into an existing set of requested claims.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithACRsAndExistingACRsAndClaims_Success() throws ComponentInitializationException {
+
+ final ClaimsSetRequest.Entry nameEntry =
+ new ClaimsSetRequest.Entry("first_name").withClaimRequirement(ClaimRequirement.ESSENTIAL);
+ // Add some to the existing requested claims set
+ final ClaimsSetRequest.Entry acrEntry =
+ new ClaimsSetRequest.Entry("acr").withValues(List.of(new ACR("urn:mace:incommon:iap:bronze")))
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL);
+ final ClaimsSetRequest idTokenClaimsRequest = new ClaimsSetRequest().add(acrEntry).add(nameEntry);
+ final OIDCClaimsRequest requested =
+ new OIDCClaimsRequest().withIDTokenClaimsRequest(idTokenClaimsRequest);
+
+ authnRequest.setRequestedClaims(requested);
+
+ // Now add ACRs
+ authnRequest.setAcrs(List.of(new ACR("urn:mace:incommon:iap:silver"), new ACR("urn:mace:incommon:iap:purple")));
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getNumberClaim("max_age").longValue(),60);
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+
+ final Object claimsObject = claims.getClaim("claims");
+ assertTrue(claimsObject instanceof OIDCClaimsRequest);
+ final OIDCClaimsRequest requestedClaims =(OIDCClaimsRequest) claimsObject;
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("first_name"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr"));
+ assertNotNull(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList());
+ assertEquals(requestedClaims.getIDTokenClaimsRequest().get("acr").getValuesAsRawList().size(),3);
+
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:bronze")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:silver")));
+ assertTrue(requestedClaims.getIDTokenClaimsRequest().get("acr")
+ .getValuesAsRawList().stream().anyMatch(acr -> acr.toString().equals("urn:mace:incommon:iap:purple")));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Test build request object with requested claims.
+ *
+ * @throws ComponentInitializationException on error
+ */
+ @Test
+ public void testBuildRequestObject_WithRequestedClaims_Success() throws ComponentInitializationException {
+
+ final OIDCClaimsRequest requestedClaims = new OIDCClaimsRequest()
+ .withIDTokenClaimsRequest(new VerifiedClaimsSetRequest().add("given_name"))
+ .withUserInfoClaimsRequest(new VerifiedClaimsSetRequest().add("family_name"));
+ authnRequest.setRequestedClaims(requestedClaims);
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ final var outboundMsgCtx = getOutboundMessageContextFailIfNull(prc);
+ final var outboundMsg = outboundMsgCtx.getMessage();
+ assert outboundMsg != null;
+ assertNotNull(outboundMsg);
+ assertNotNull(((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ final ClaimsSet claims = (((OIDCAuthenticationRequest)outboundMsg).getRequestObjectClaimsSet());
+ assert claims != null;
+ assertEquals(claims.getStringClaim("scope"),"openid");
+ assertEquals(claims.getStringClaim("iss"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("client_id"),TestConstants.CLIENT_ID);
+ assertEquals(claims.getStringClaim("response_type"),"code");
+ assertEquals(claims.getStringClaim("redirect_uri"),TestConstants.RESPONSE_URL);
+ assertNotNull(claims.getStringClaim("nonce"));
+ assertNotNull(claims.getClaim("aud"));
+ assertNotNull(claims.getClaim("claims"));
+ assertTrue(isValidJSON(claims.getClaim("claims").toString()));
+
+ assertTrue(isValidJSON(claims.toJSONString()));
+ }
+
+ /**
+ * Is the string valid JSON.
+ *
+ * @param json the json to test
+ *
+ * @return true if valid, false otherwise.
+ */
+ private boolean isValidJSON(final String json) {
+ try{
+ final ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.readTree(json);
+ } catch(final Exception e){
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
index 0129b63..79714d0 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateDataTest.java
@@ -14,9 +14,139 @@
package net.shibboleth.sp.oidc.profile.impl;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
+
/**
* Tests for {@link CreateAuthenticationStateData}.
*/
-public class CreateAuthenticationStateDataTest {
+public class CreateAuthenticationStateDataTest extends BaseApplicationActionTest {
+
+ /** The action being tested. */
+ private CreateAuthenticationStateData action;
+
+ /** The RelyingPartyContext to be used in the tests. */
+ private RelyingPartyContext partyContext;
+
+ /** The RP config.*/
+ private DefaultOIDCAuthorizationConfiguration rpConfig;
+
+ /** The AuthenticationRequestStateData to be returned by the lookup strategy. */
+ private AuthenticationRequestStateData authnState;
+
+ /** The DDF input to be passed to the action. */
+ private DDF input;
+
+ /** The state context to be created by the action. */
+ private StateDataContext stateContext;
+
+ @SuppressWarnings("null")
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+
+ action = new CreateAuthenticationStateData();
+
+
+ partyContext = prc.ensureSubcontext(RelyingPartyContext.class);
+ rpConfig = new DefaultOIDCAuthorizationConfiguration();
+ partyContext.setProfileConfig(rpConfig);
+ prc.addSubcontext(partyContext);
+
+ authnState = new AuthenticationRequestStateData();
+
+ input = new DDF(null).structure();
+ arc.setInput(input);
+ input.addmember(SPConstants.TARGET).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+
+ action.setStateDataLookupStrategy(prc -> authnState);
+ stateContext = new StateDataContext();
+ action.setStateDataContextCreationStrategy(prc -> stateContext);
+
+
+ }
+
+ @Test
+ public void testSuccess_SuccessfulConstruction() throws ComponentInitializationException {
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ assertEquals(authnState.getResource(), TestConstants.RESOURCE_URL);
+
+ }
+
+ @Test
+ public void testFail_NoInput() throws ComponentInitializationException {
+ arc.setInput(null);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testFail_NoStateData() throws ComponentInitializationException {
+ action.setStateDataLookupStrategy(prc -> null);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testSuccess_NoTarget() throws ComponentInitializationException {
+ input.getmember(SPConstants.TARGET).remove();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ assertNull(authnState.getResource());
+ }
+
+ @Test
+ public void testSuccess_CheckAddress() throws ComponentInitializationException {
+ input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).addmember(RemotedHttpServletRequest.REMOTE_ADDR)
+ .string("192.168.1.1");
+ rpConfig.setCheckAddress(true);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ assertEquals(authnState.getResource(), TestConstants.RESOURCE_URL);
+ assertEquals(authnState.getClientAddress(), "192.168.1.1");
+ }
+
+ @Test
+ public void testSuccess_OldStateHasResource() throws ComponentInitializationException {
+ input.getmember(SPConstants.TARGET).remove();
+ authnState.setResource("https://sp.example.org/old");
+ stateContext.setStateData(authnState);
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ assertEquals(authnState.getResource(), "https://sp.example.org/old");
+ }
}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/DefaultClaimMergingStrategyTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/DefaultClaimMergingStrategyTest.java
new file mode 100644
index 0000000..a291e95
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/DefaultClaimMergingStrategyTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
+import net.minidev.json.parser.ParseException;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/** Test for the DefaultClaimMergingStrategy.*/
+public class DefaultClaimMergingStrategyTest {
+
+ /** Mock response from the UserInfo endpoint.*/
+ @Nonnull @NotEmpty
+ private final String USERINFO_RESPONSE ="{\n"
+ + " \"sub\": \"jdoe\",\n"
+ + " \"website\": \"https://openid.net/\",\n"
+ + " \"zoneinfo\": \"America/Los_Angeles\",\n"
+ + " \"birthdate\": \"2000-02-03\",\n"
+ + " \"gender\": \"female\",\n"
+ + " \"preferred_username\": \"d.tu\",\n"
+ + " \"given_name\": \"From UserInfo\",\n"
+ + " \"middle_name\": \"From UserInfo\",\n"
+ + " \"locale\": \"en-US\",\n"
+ + " \"updated_at\": 1580000000,\n"
+ + " \"name\": \"Demo T. User\",\n"
+ + " \"family_name\": \"User\"\n"
+ + "}";
+
+ @Test
+ public void testMerge() throws ParseException {
+
+ final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+ final ClaimsSet userInfo = new ClaimsSet((JSONObject)parser.parse(USERINFO_RESPONSE));
+
+ final JWTClaimsSet idToken = new JWTClaimsSet.Builder().subject("jdoe")
+ .claim("given_name", "FromIdToken")
+ .claim("nickname", "FromIdToken")
+ .build();
+ final ClaimsSet idTokenClaims = new ClaimsSet();
+ idTokenClaims.putAll(idToken.getClaims());
+
+ final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+ final ClaimsSet merged = strategy.apply(userInfo, idTokenClaims);
+
+ // This is a merged claim
+ assertEquals(merged.getClaim("given_name"), "FromIdToken");
+
+ // This is a claim only in the id_token
+ assertEquals(merged.getClaim("nickname"), "FromIdToken");
+
+ // This is a claim only in the userInfo response
+ assertEquals(merged.getClaim("middle_name"), "From UserInfo");
+ }
+
+ @Test
+ public void testMergeNullUserInfo() throws ParseException {
+
+ final JWTClaimsSet idToken = new JWTClaimsSet.Builder().subject("jdoe")
+ .claim("given_name", "FromIdToken")
+ .claim("nickname", "FromIdToken")
+ .build();
+ final ClaimsSet idTokenClaims = new ClaimsSet();
+ idTokenClaims.putAll(idToken.getClaims());
+ final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+ final ClaimsSet merged = strategy.apply(null, idTokenClaims);
+
+
+ assertEquals(merged.getClaim("given_name"), "FromIdToken");
+ assertEquals(merged.getClaim("nickname"), "FromIdToken");
+ // not in id_token
+ assertNull(merged.getClaim("middle_name"));
+ }
+
+ @Test
+ public void testMergeNullIdToken() throws ParseException {
+
+ final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+ final ClaimsSet userInfo = new ClaimsSet((JSONObject)parser.parse(USERINFO_RESPONSE));
+
+ final DefaultClaimMergingStrategy strategy = new DefaultClaimMergingStrategy();
+ final ClaimsSet merged = strategy.apply(userInfo, null);
+
+ assertEquals(merged.getClaim("given_name"), "From UserInfo");
+
+ assertEquals(merged.getClaim("middle_name"), "From UserInfo");
+ // not in UserInfo claims
+ assertNull(merged.getClaim("nickname"));
+
+ }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessTokenTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessTokenTest.java
new file mode 100644
index 0000000..e800cd4
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessTokenTest.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.net.URIBuilder;
+import org.mockito.Mockito;
+import org.springframework.http.MediaType;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+
+/**
+ * Tests for the {@link ExchangeCodeForAccessToken} action.
+ */
+public class ExchangeCodeForAccessTokenTest extends BaseOIDCAuthenticationTest {
+
+ /** The action to test.*/
+ private ExchangeCodeForAccessToken exchangeAction;
+
+
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+ exchangeAction = new ExchangeCodeForAccessToken();
+ }
+
+ /**
+ * Test the token exchange works using a mocked HTTP client and response.
+ * Does not check the signature is valid. Given the amount of mocking, is not
+ * a very thorough test.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testTokenExchange() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final ClassicHttpResponse httpResponse = Mockito.mock(ClassicHttpResponse.class);
+ Mockito.when(httpResponse.getCode()).thenReturn(200);
+ Mockito.when(httpResponse.getEntity()).thenReturn(
+ new StringEntity(TestConstants.ACCESS_TOKEN_RESPONSE, ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+
+
+ final OIDCTokenResponse responseToken = OIDCTokenResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<OIDCTokenResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+ // create new client with mock response
+ exchangeAction.setHttpClient(httpClient);
+
+ exchangeAction.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/token")
+ .build();
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+
+ // Add a dummy response decoder, it is not used as the response is mocked.
+ exchangeAction.setHttpResponseDecoderStrategy(response -> {
+ try {
+ return OIDCTokenResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+ } catch (final Exception e) {
+ return null;
+ }
+ });
+
+ exchangeAction.initialize();
+
+ final Event event = exchangeAction.execute(src);
+ //Null is success
+ assertNull(event);
+
+ }
+
+ @Test
+ public void testTokenExchange_WrongTokenType() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+
+ final OIDCTokenResponse responseToken = null;
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<OIDCTokenResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+
+ // create new client with mock response
+ exchangeAction.setHttpClient(httpClient);
+
+ exchangeAction.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/token")
+ .build();
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ exchangeAction.setHttpResponseDecoderStrategy(response -> {
+ return null;
+ });
+
+ exchangeAction.initialize();
+
+ final Event event = exchangeAction.execute(src);
+ assertNotNull(event);
+ assert event != null;
+ assertEquals(OidcEventIds.INVALID_TOKEN,event.getId());
+
+ }
+
+ @Test
+ public void testTokenExchange_ErrorResponse() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final ClassicHttpResponse httpResponse = Mockito.mock(ClassicHttpResponse.class);
+ Mockito.when(httpResponse.getCode()).thenReturn(400);
+ Mockito.when(httpResponse.getEntity()).thenReturn(
+ new StringEntity(TestConstants.TOKEN_RESPONSE_ERROR, ContentType.parse(MediaType.APPLICATION_JSON_VALUE)));
+
+ final TokenErrorResponse responseToken = TokenErrorResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<TokenErrorResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+ // create new client with mock response
+ exchangeAction.setHttpClient(httpClient);
+
+ exchangeAction.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/token")
+ .build();
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ exchangeAction.setHttpResponseDecoderStrategy(response -> {
+ try {
+ return TokenErrorResponse.parse(convertHttpResponseToJSONObject(httpResponse));
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ return null;
+ }
+ });
+
+ exchangeAction.initialize();
+
+ final Event event = exchangeAction.execute(src);
+ assert event != null;
+ assertNotNull(event);
+ assertEquals(OidcEventIds.INVALID_TOKEN,event.getId());
+
+ }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessEndUserClaimsTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessEndUserClaimsTest.java
new file mode 100644
index 0000000..ea5b35a
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ProcessEndUserClaimsTest.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.mockito.Mockito;
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+
+import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.oidc.profile.context.EndUserClaimsContext;
+import net.shibboleth.oidc.profile.context.UserInfoResponseContext;
+import net.shibboleth.oidc.security.jwt.claims.impl.JWTClaims;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.relyingparty.BasicRelyingPartyConfiguration;
+import net.shibboleth.profile.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/** Tests for {@link ProcessEndUserClaims}.*/
+public class ProcessEndUserClaimsTest extends BaseOIDCAuthenticationTest {
+
+ /** The action to test.*/
+ private ProcessEndUserClaims action;
+
+ /** The RPC.*/
+ private RelyingPartyContext rpc;
+
+ /** The profile config.*/
+ private DefaultOIDCAuthorizationConfiguration oidcAuthzConfig;
+
+
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+ action = new ProcessEndUserClaims();
+
+ rpc = prc.ensureSubcontext(RelyingPartyContext.class);
+ oidcAuthzConfig = new DefaultOIDCAuthorizationConfiguration();
+ final RelyingPartyConfiguration rpConfig = new BasicRelyingPartyConfiguration();
+ rpc.setProfileConfig(oidcAuthzConfig);
+ rpc.setConfiguration(rpConfig);
+
+ // Merge by putting all from each
+ action.setClaimMergingStrategy((idToken, userInfo) -> {
+ final ClaimsSet out = new ClaimsSet();
+ out.putAll(idToken.toJSONObject());
+ out.putAll(userInfo.toJSONObject());
+ return out;
+ });
+
+ //remove some validation claims
+ action.setClaimSanitizationStrategy(claims -> {
+ final Set<String> validationClaims = Set.of(
+ JWTClaims.ISSUER_CLAIM.getClaimName(),
+ JWTClaims.AUDIENCE_CLAIM.getClaimName(),
+ JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName());
+
+ final ClaimsSet sanitizedClaims = new ClaimsSet();
+ final Map<String, Object> filteredMap = claims.toJSONObject().entrySet()
+ .stream()
+ .filter(c -> !validationClaims.contains(c.getKey()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ sanitizedClaims.putAll(filteredMap);
+ return sanitizedClaims;
+ });
+
+ action.setAccessTokenResponseContextLookupStrategy(prc -> {
+ final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+ final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+ atrc.setTokenResponse(tokenResponse);
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("https://op.example.com")
+ .audience("https://rp.example.com")
+ .subject("jdoe")
+ .claim("family_name", "blogs")
+ .claim("name","jdoe")
+ .expirationTime(Date.from(Instant.now().plusSeconds(120)))
+ .build();
+ final OIDCTokens tokens = new OIDCTokens(new PlainJWT(claimsSet),
+ new BearerAccessToken("access_token_value"),
+ new RefreshToken("refresh_token_value"));
+ Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+ return atrc;
+ });
+
+ action.setUserInfoResponseContextLookupStrategy(prc -> {
+ final UserInfoResponseContext uirc = new UserInfoResponseContext();
+ final ClaimsSet claims = new ClaimsSet();
+ claims.setClaim("given_name", "joe");
+ claims.setClaim("email", "joe at example.com");
+ claims.setClaim("sub", "jdoe");
+ final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new UserInfo(claims.toJSONObject()));
+ uirc.setUserInfo(uir);
+ return uirc;
+ });
+ }
+
+ @Test
+ public void testSuccess() throws Exception {
+ action.initialize();
+ action.execute(src);
+
+ assertNotNull(prc.getSubcontext(EndUserClaimsContext.class));
+ final EndUserClaimsContext claimsContext =
+ prc.getSubcontext(EndUserClaimsContext.class);
+ assert claimsContext != null;
+ final var claims = claimsContext.getEndUserClaims();
+ assert claims != null;
+ assertNull(claims.getClaim("iss"));
+ assertNull(claims.getClaim("aud"));
+ assertEquals(claims.getClaim("sub"), "jdoe");
+ assertEquals(claims.getClaim("family_name"), "blogs");
+ assertEquals(claims.getClaim("given_name"), "joe");
+ assertEquals(claims.getClaim("email"), "joe at example.com");
+ }
+
+ @Test
+ public void testSuccesfulMerge_ExpirySanatized() throws ComponentInitializationException {
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertNotNull(prc.getSubcontext(EndUserClaimsContext.class));
+ final EndUserClaimsContext claimsContext =
+ prc.getSubcontext(EndUserClaimsContext.class);
+ assert claimsContext != null;
+ final var claims = claimsContext.getEndUserClaims();
+ assert claims != null;
+ assertEquals(claims.getClaim("name"),"jdoe");
+ assertEquals(claims.getClaim("sub"),"jdoe");
+ assertEquals(claims.getClaim("given_name"),"joe");
+ assertEquals(claims.getClaim("email"),"joe at example.com");
+ assertNull(claims.getIssuer());
+ assertNull(claims.getAudience());
+ assertNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+ }
+
+ @Test
+ public void testSuccesfulMerge_NoSanitization() throws ComponentInitializationException {
+
+ action.setEnableClaimSanitizationStrategy(false);
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ assertNotNull(prc.getSubcontext(EndUserClaimsContext.class));
+ final EndUserClaimsContext claimsContext =
+ prc.getSubcontext(EndUserClaimsContext.class);
+ assert claimsContext != null;
+ final var claims = claimsContext.getEndUserClaims();
+ assert claims != null;
+ assertEquals(claims.getClaim("name"),"jdoe");
+ assertEquals(claims.getClaim("sub"),"jdoe");
+ assertEquals(claims.getClaim("given_name"),"joe");
+ assertEquals(claims.getClaim("email"),"joe at example.com");
+ assertNotNull(claims.getIssuer());
+ assertNotNull(claims.getAudience());
+ assertNotNull(claims.getDateClaim(JWTClaims.EXPIRATION_TIME_CLAIM.getClaimName()));
+ }
+
+ @Test
+ public void testSuccess_NoUserInfoClaims() throws Exception {
+
+ action.setUserInfoResponseContextLookupStrategy(prc -> null);
+
+ action.initialize();
+ action.execute(src);
+
+ assertNotNull(prc.getSubcontext(EndUserClaimsContext.class));
+ final EndUserClaimsContext claimsContext =
+ prc.getSubcontext(EndUserClaimsContext.class);
+ assert claimsContext != null;
+ final var claims = claimsContext.getEndUserClaims();
+ assert claims != null;
+ assertNull(claims.getClaim("iss"));
+ assertNull(claims.getClaim("aud"));
+ assertEquals(claims.getClaim("sub"), "jdoe");
+ assertEquals(claims.getClaim("family_name"), "blogs");
+ assertNull(claims.getClaim("given_name"), "joe");
+ assertNull(claims.getClaim("email"), "joe at example.com");
+ }
+
+ @Test
+ public void testSuccess_NoOIDCToken() throws Exception {
+
+ action.setAccessTokenResponseContextLookupStrategy(prc -> {
+ final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+ final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+ Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(null);
+ atrc.setTokenResponse(tokenResponse);
+ return atrc;
+ });
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assert event != null;
+ assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+
+ }
+
+ @Test
+ public void testSuccess_NoIDTokenClaims() throws Exception {
+
+ action.setAccessTokenResponseContextLookupStrategy(prc -> {
+ final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+ final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+ atrc.setTokenResponse(tokenResponse);
+ try {
+ final OIDCTokens tokens = new OIDCTokens(new PlainJWT(new ClaimsSet().toJWTClaimsSet()),
+ new BearerAccessToken("access_token_value"),
+ new RefreshToken("refresh_token_value"));
+ Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+ } catch (final ParseException e) {
+ fail(e.getMessage());
+ }
+ return atrc;
+ });
+
+ action.initialize();
+ action.execute(src);
+
+ assertNotNull(prc.getSubcontext(EndUserClaimsContext.class));
+ final EndUserClaimsContext claimsContext =
+ prc.getSubcontext(EndUserClaimsContext.class);
+ assert claimsContext != null;
+ final var claims = claimsContext.getEndUserClaims();
+ assert claims != null;
+ assertNull(claims.getClaim("iss"));
+ assertNull(claims.getClaim("aud"));
+ assertEquals(claims.getClaim("given_name"), "joe");
+ assertEquals(claims.getClaim("email"), "joe at example.com");
+
+ }
+
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UserInfoEndpointLookupTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UserInfoEndpointLookupTest.java
new file mode 100644
index 0000000..296e595
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UserInfoEndpointLookupTest.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.net.URIBuilder;
+import org.mockito.Mockito;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.common.contenttype.ContentType;
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+import net.shibboleth.oidc.profile.context.UserInfoResponseContext;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.oidc.testing.TestConstants;
+
+/**
+ * Tests for the {@link UserInfoEndpointLookup} action.
+ */
+public class UserInfoEndpointLookupTest extends BaseOIDCAuthenticationTest {
+
+ /** The action to test.*/
+ private UserInfoEndpointLookup action;
+
+
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+ action = new UserInfoEndpointLookup();
+ }
+
+ /**
+ * Test the token exchange works using a mocked HTTP client and response.
+ * Does not check the signature is valid. Given the amount of mocking, is not
+ * a very thorough test.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testLookupSuccess() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final ClassicHttpResponse httpResponse = Mockito.mock(ClassicHttpResponse.class);
+ Mockito.when(httpResponse.getCode()).thenReturn(200);
+ Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(TestConstants.USERINFO_RESPONSE));
+
+ final UserInfoSuccessResponse responseToken = new
+ UserInfoSuccessResponse(new UserInfo(convertHttpResponseToJSONObject(httpResponse)));
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<UserInfoSuccessResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+ // create new client with mock response
+ action.setHttpClient(httpClient);
+
+ action.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/userinfo")
+ .build();
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ // Not used, just needed to avoid null response decoder
+ action.setHttpResponseDecoderStrategy(response -> {
+ try {
+ return new UserInfoSuccessResponse(new UserInfo(convertHttpResponseToJSONObject(httpResponse)));
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ return null;
+ }
+ });
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ //Null is success
+ assertNull(event);
+ final var inboundMsgCtx = prc.getInboundMessageContext();
+ assert inboundMsgCtx != null;
+ final var userInfoCtx = inboundMsgCtx.getSubcontext(UserInfoResponseContext.class);
+ assertNotNull(userInfoCtx);
+ assert userInfoCtx != null;
+ assertNotNull(userInfoCtx.getUserInfo());
+ final var userInfo = userInfoCtx.getUserInfo();
+ assert userInfo != null;
+ assertEquals(userInfo.getEntityContentType(), ContentType.APPLICATION_JSON);
+ assertNotNull(userInfo.getUserInfo().getClaim("sub"),"248289761001");
+
+ }
+
+ @Test
+ public void testUserInfoLookup_ErrorResponse() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final UserInfoErrorResponse responseToken = UserInfoErrorResponse.parse(TestConstants.USERINFO_RESPONSE_ERROR);
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<UserInfoErrorResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+ // create new client with mock response
+ action.setHttpClient(httpClient);
+
+ action.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/userinfo")
+ .build();
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ // This is a just to avoid a null response decoder, is not used.
+ action.setHttpResponseDecoderStrategy(response -> {
+ try {
+ return UserInfoErrorResponse.parse(TestConstants.USERINFO_RESPONSE_ERROR);
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ return null;
+ }
+ });
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assert event != null;
+ assertEquals(event.getId(), OidcEventIds.INVALID_USERINFO_CLAIMS);
+
+ }
+
+ @Test
+ public void testNullUserInfoResponse() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+
+ final OIDCTokenResponse responseToken = null;
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(),
+ (HttpContext) Mockito.any(), (HttpClientResponseHandler<OIDCTokenResponse>)Mockito.any()))
+ .thenReturn(responseToken);
+
+
+ // create new client with mock response
+ action.setHttpClient(httpClient);
+
+ action.setHttpRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/userinfo")
+ .build();
+ } catch (final URISyntaxException e) {
+ fail(e.getMessage());
+ return null;
+ }
+
+ // Add headers and create request.
+ final ClassicRequestBuilder rb = ClassicRequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_URLENCODED.getType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ action.setHttpResponseDecoderStrategy(response -> null);
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+
+ //Null is success
+ assertNotNull(event);
+ assert event != null;
+ assertEquals(event.getId(),OidcEventIds.INVALID_USERINFO_CLAIMS);
+
+ }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaimsTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaimsTest.java
new file mode 100644
index 0000000..18d7cbf
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaimsTest.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.time.Instant;
+import java.util.Date;
+
+import org.mockito.Mockito;
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.oidc.profile.context.UserInfoResponseContext;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/** Tests for {@link ValidateUserInfoJSONObjectClaims}.*/
+public class ValidateUserInfoJSONObjectClaimsTest extends BaseOIDCAuthenticationTest {
+
+ /** The action to test*/
+ private ValidateUserInfoJSONObjectClaims action;
+
+ private UserInfoResponseContext userInfoCtx;
+
+ private AccessTokenResponseContext accessTokenCtx;
+
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws ComponentInitializationException {
+ super.beforeMethod();
+ action = new ValidateUserInfoJSONObjectClaims();
+ userInfoCtx = new UserInfoResponseContext();
+ accessTokenCtx = new AccessTokenResponseContext();
+
+ action.setTokenResponseContextLookupStrategy(prc -> {
+ final AccessTokenResponseContext atrc = new AccessTokenResponseContext();
+ final OIDCTokenResponse tokenResponse = Mockito.mock(OIDCTokenResponse.class);
+ atrc.setTokenResponse(tokenResponse);
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer("https://op.example.com")
+ .audience("https://rp.example.com")
+ .subject("jdoe")
+ .claim("family_name", "blogs")
+ .claim("name","jdoe")
+ .expirationTime(Date.from(Instant.now().plusSeconds(120)))
+ .build();
+ final OIDCTokens tokens = new OIDCTokens(new PlainJWT(claimsSet),
+ new BearerAccessToken("access_token_value"),
+ new RefreshToken("refresh_token_value"));
+ Mockito.when(tokenResponse.getOIDCTokens()).thenReturn(tokens);
+ return atrc;
+ });
+
+ action.setUserInfoResponseContextLookupStrategy(prc -> {
+ final UserInfoResponseContext uirc = new UserInfoResponseContext();
+ final ClaimsSet claims = new ClaimsSet();
+ claims.setClaim("given_name", "joe");
+ claims.setClaim("email", "joe at example.com");
+ claims.setClaim("sub", "jdoe");
+ final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new UserInfo(claims.toJSONObject()));
+ uirc.setUserInfo(uir);
+ return uirc;
+ });
+
+ }
+
+ @Test
+ public void testValidate_Fail_NoTokenResponse() throws Exception {
+ action.setTokenResponseContextLookupStrategy(prc -> new AccessTokenResponseContext());
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testValidate_Fail_NoTokenResponseContext() throws Exception {
+ action.setTokenResponseContextLookupStrategy(prc -> null);
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testValidate_Fail_NoUserInfoResponseContext() throws Exception {
+ action.setUserInfoResponseContextLookupStrategy(prc -> null);
+
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testValidate_Fail_NoUserInfo() throws Exception {
+ action.setUserInfoResponseContextLookupStrategy(prc -> new UserInfoResponseContext());
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test
+ public void testValidate_Success() throws Exception {
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ }
+
+ @Test
+ public void testValidate_Success_UserInfoJWT() throws Exception {
+ action.setUserInfoResponseContextLookupStrategy(prc -> {
+ final UserInfoResponseContext uirc = new UserInfoResponseContext();
+ final JWTClaimsSet claims = new JWTClaimsSet.Builder().subject("jdoe")
+ .claim("given_name", "joe")
+ .claim("email", "joe at example.com").build();
+ final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new PlainJWT(claims));
+ uirc.setUserInfo(uir);
+ return uirc;
+ });
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNull(event);
+ }
+
+ @Test
+ public void testValidate_Fail_SubDoesNotMatchIdToken_UserInfoJWT() throws Exception {
+ action.setUserInfoResponseContextLookupStrategy(prc -> {
+ final UserInfoResponseContext uirc = new UserInfoResponseContext();
+ final JWTClaimsSet claims = new JWTClaimsSet.Builder().subject("new-sub-does-not-match")
+ .claim("given_name", "joe")
+ .claim("email", "joe at example.com").build();
+ final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new PlainJWT(claims));
+ uirc.setUserInfo(uir);
+ return uirc;
+ });
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), OidcEventIds.INVALID_USERINFO_CLAIMS);
+ }
+
+ @Test
+ public void testValidate_Fail_SubDoesNotMatchIdToken() throws Exception {
+ action.setUserInfoResponseContextLookupStrategy(prc -> {
+ final UserInfoResponseContext uirc = new UserInfoResponseContext();
+ final ClaimsSet claims = new ClaimsSet();
+ claims.setClaim("given_name", "joe");
+ claims.setClaim("email", "joe at example.com");
+ claims.setClaim("sub", "new-sub-does-not-match");
+ final UserInfoSuccessResponse uir = new UserInfoSuccessResponse(new UserInfo(claims.toJSONObject()));
+ uirc.setUserInfo(uir);
+ return uirc;
+ });
+ action.initialize();
+ final Event event = action.execute(src);
+ assertNotNull(event);
+ assertEquals(event.getId(), OidcEventIds.INVALID_USERINFO_CLAIMS);
+ }
+
+}
diff --git a/sp-oidc-impl/src/test/resources/metadata/openid-configuration.json b/sp-oidc-impl/src/test/resources/metadata/openid-configuration.json
new file mode 100644
index 0000000..592e3b4
--- /dev/null
+++ b/sp-oidc-impl/src/test/resources/metadata/openid-configuration.json
@@ -0,0 +1,60 @@
+{
+"issuer": "https://op.example.org",
+"authorization_endpoint": "https://op.example.org/auth",
+"token_endpoint": "https://oauth2.op.example.org/token",
+"userinfo_endpoint": "https://op.example.org/userinfo",
+"revocation_endpoint": "https://op.example.org/revoke",
+"jwks_uri": "https://op.example.org/keys",
+"request_parameter_supported" : true,
+"request_object_signing_alg_values_supported" : ["RS256"],
+"response_types_supported": [
+"code",
+"token",
+"id_token",
+"code token",
+"code id_token",
+"token id_token",
+"code token id_token",
+"none"
+],
+"request_object_encryption_enc_values_supported" : ["A128CBC-HS256"],
+"request_object_encryption_alg_values_supported" : ["RSA-OAEP-256"],
+"subject_types_supported": [
+"public"
+],
+"id_token_signing_alg_values_supported": [
+"RS256"
+],
+"scopes_supported": [
+"openid",
+"email",
+"profile"
+],
+"claims_parameter_supported" : true,
+"token_endpoint_auth_methods_supported": [
+"client_secret_post",
+"client_secret_basic"
+],
+"claims_supported": [
+"aud",
+"email",
+"email_verified",
+"exp",
+"family_name",
+"given_name",
+"iat",
+"iss",
+"locale",
+"name",
+"picture",
+"sub"
+],
+"code_challenge_methods_supported": [
+"plain",
+"S256"
+],
+"grant_types_supported": [
+"authorization_code",
+"refresh_token"
+]
+}
\ No newline at end of file
diff --git a/sp-oidc-testing/.checkstyle b/sp-oidc-testing/.checkstyle
deleted file mode 100644
index 1253c35..0000000
--- a/sp-oidc-testing/.checkstyle
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
- <local-check-config name="Shibboleth Checkstyle" location="/java-plugin-shibd-oidc/resources/checkstyle/checkstyle.xml" type="project" description="">
- <additional-data name="protect-config-file" value="false"/>
- </local-check-config>
- <fileset name="all" enabled="true" check-config-name="Shibboleth Checkstyle" local="true">
- <file-match-pattern match-pattern="." include-pattern="true"/>
- </fileset>
-</fileset-config>
diff --git a/sp-oidc-testing/pom.xml b/sp-oidc-testing/pom.xml
deleted file mode 100644
index d88b86d..0000000
--- a/sp-oidc-testing/pom.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- See LICENSE.txt file in the root directory of this repository for the
-copyright/license information. -->
-<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.sp</groupId>
- <artifactId>sp-oidc-parent</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
-
- <artifactId>sp-oidc-testing</artifactId>
- <name>Shibboleth SP :: OIDC Protocol Plugin :: Testing API</name>
- <description>Testing APIs for OIDC SP components</description>
- <packaging>jar</packaging>
-
- <properties>
- <checkstyle.configLocation>${project.basedir}/../resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
- <automatic.module.name>net.shibboleth.sp.oidc.testing</automatic.module.name>
- </properties>
-
- <dependencies>
- <!-- Compile dependencies -->
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sp-oidc-api</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sp-server-api</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.testng</groupId>
- <artifactId>testng</artifactId>
- <scope>compile</scope>
- </dependency>
- <!-- Provided dependencies -->
- <dependency>
- <groupId>${shib-profile.groupId}</groupId>
- <artifactId>shib-profile-impl</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${oidc-common.groupId}</groupId>
- <artifactId>oidc-common-crypto-impl</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-core-impl</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>${oidc-common.groupId}</groupId>
- <artifactId>oidc-common-crypto-api</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.datatype</groupId>
- <artifactId>jackson-datatype-jdk8</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <scope>provided</scope>
- </dependency>
- </dependencies>
-</project>
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list