[java-idp-plugin-oidc-rp] branch main updated: First commit
Phil Smart
philip.smart at jisc.ac.uk
Thu Jun 3 16:27:31 UTC 2021
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=6c243b73114960e1fd94fa39c9de45b82d8f3a5e
The following commit(s) were added to refs/heads/main by this push:
new 6c243b7 First commit
6c243b7 is described below
commit 6c243b73114960e1fd94fa39c9de45b82d8f3a5e
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Jun 3 17:27:28 2021 +0100
First commit
Is an authn pluginification of the existing mpassid oidc rp branch of
the IdP (feature/mpassid-oidc-int).
---
.gitignore | 37 +-
checkstyle.xml | 117 +++++
java-idp-oidc-rp-api/pom.xml | 51 ++
.../idp/plugin/authn/oidc/rp/Version.java | 32 ++
.../oidc/rp/context/OpenIDConnectContext.java | 454 ++++++++++++++++
java-idp-oidc-rp-dist/pom.xml | 51 ++
.../src/main/assembly/assembly-tgz.xml | 72 +++
.../src/main/assembly/assembly-zip.xml | 72 +++
.../src/main/resources/bootstrap/keys.txt | 1 +
java-idp-oidc-rp-impl/.checkstyle | 7 +
java-idp-oidc-rp-impl/pom.xml | 118 +++++
.../idp/plugin/authn/oidc/rp/OIDCRPModule.java | 25 +
.../idp/plugin/authn/oidc/rp/OIDCRPPlugin.java | 27 +
.../oidc/rp/impl/AuthorizationController.java | 158 ++++++
.../authn/oidc/rp/impl/GetOIDCTokenResponse.java | 137 +++++
.../authn/oidc/rp/impl/SetOIDCInformation.java | 583 +++++++++++++++++++++
.../authn/oidc/rp/impl/ValidateIDTokenACR.java | 103 ++++
.../oidc/rp/impl/ValidateIDTokenAudience.java | 91 ++++
.../rp/impl/ValidateIDTokenAuthenticationTime.java | 185 +++++++
.../rp/impl/ValidateIDTokenAuthorizedParty.java | 89 ++++
.../rp/impl/ValidateIDTokenExpirationTime.java | 87 +++
.../authn/oidc/rp/impl/ValidateIDTokenIssuer.java | 83 +++
.../oidc/rp/impl/ValidateIDTokenSignature.java | 159 ++++++
.../oidc/rp/impl/ValidateOIDCAuthentication.java | 209 ++++++++
.../impl/ValidateOIDCAuthenticationResponse.java | 123 +++++
.../plugin/authn/oidc/rp/impl/package-info.java | 22 +
.../META-INF/net.shibboleth.idp/postconfig.xml | 48 ++
.../OIDCRelyingParty/oidc-relying-party-beans.xml | 79 +++
.../OIDCRelyingParty/oidc-relying-party-flow.xml | 60 +++
.../services/net.shibboleth.idp.module.IdPModule | 1 +
.../services/net.shibboleth.idp.plugin.IdPPlugin | 2 +
.../authn/oidc/rp/conf/authn/oidc-rp.properties | 7 +
.../idp/plugin/authn/oidc/rp/module.properties | 14 +
.../idp/plugin/authn/oidc/rp/plugin.properties | 9 +
pom.xml | 145 +++++
35 files changed, 3430 insertions(+), 28 deletions(-)
diff --git a/.gitignore b/.gitignore
index 8eeb321..0ca7416 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,29 +1,10 @@
-# A simulation of Subversion default ignores, generated by reposurgeon.
-*.o
-*.lo
-*.la
-*.al
-*.libs
-*.so
-*.so.[0-9]*
-*.a
-*.pyc
-*.pyo
-*.rej
-*.iml
-*~
-*.#*
-.*.swp
-.DS_store
.DS_Store
-# Simulated Subversion default ignores end here
-# The contents of the svn:ignore property on the branch root.
-/test-output
-/target
-.factorypath
-classpath:
-/.checkstyle
-/.DS_Store
-target/
-bin/
-test-output/
+*/target
+*/test-output
+*~
+*/.classpath
+*/.project
+*/.settings
+/.project
+/.settings
+target
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..f3f13f6
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+ This configuration file was written by the eclipse-cs plugin configuration editor
+-->
+<!--
+ Checkstyle-Configuration: Shibboleth Checkstyle
+ Description: none
+-->
+<module name="Checker">
+ <property name="severity" value="warning"/>
+ <module name="TreeWalker">
+ <property name="tabWidth" value="4"/>
+ <module name="JavadocMethod"/>
+ <module name="JavadocType">
+ <property name="allowUnknownTags" value="true"/>
+ </module>
+ <module name="JavadocVariable"/>
+ <module name="JavadocStyle">
+ <property name="checkEmptyJavadoc" value="true"/>
+ </module>
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+ <module name="AvoidStarImport"/>
+ <module name="IllegalImport"/>
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+ <module name="MethodLength">
+ <property name="max" value="70"/>
+ </module>
+ <module name="ParameterNumber">
+ <property name="max" value="5"/>
+ </module>
+ <module name="EmptyForIteratorPad"/>
+ <module name="MethodParamPad"/>
+ <module name="ModifierOrder"/>
+ <module name="AvoidNestedBlocks"/>
+ <module name="LeftCurly"/>
+ <module name="NeedBraces"/>
+ <module name="RightCurly"/>
+ <module name="EmptyStatement"/>
+ <module name="EqualsHashCode"/>
+ <module name="HiddenField"/>
+ <module name="IllegalInstantiation"/>
+ <module name="InnerAssignment"/>
+ <module name="MissingSwitchDefault"/>
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+ <module name="FinalClass"/>
+ <module name="HideUtilityClassConstructor"/>
+ <module name="VisibilityModifier"/>
+ <module name="ArrayTypeStyle"/>
+ <module name="UpperEll"/>
+ <module name="AnonInnerLength"/>
+ <module name="EmptyForInitializerPad"/>
+ <module name="CovariantEquals"/>
+ <module name="DefaultComesLast"/>
+ <module name="DeclarationOrder"/>
+ <module name="ExplicitInitialization"/>
+ <module name="FallThrough"/>
+ <module name="IllegalThrows"/>
+ <module name="MultipleVariableDeclarations"/>
+ <module name="PackageDeclaration"/>
+ <module name="ParameterAssignment"/>
+ <module name="ReturnCount">
+ <property name="max" value="8"/>
+ <property name="maxForVoid" value="8"/>
+ </module>
+ <module name="StringLiteralEquality"/>
+ <module name="SuperFinalize"/>
+ <module name="ArrayTrailingComma"/>
+ <module name="UnnecessaryParentheses"/>
+ <module name="MutableException"/>
+ <module name="ThrowsCount">
+ <property name="max" value="3"/>
+ </module>
+ <module name="CyclomaticComplexity"/>
+ <module name="TrailingComment"/>
+ <module name="EqualsAvoidNull"/>
+ <module name="ModifiedControlVariable"/>
+ <module name="FinalParameters">
+ <property name="tokens" value="METHOD_DEF,CTOR_DEF,LITERAL_CATCH"/>
+ </module>
+ <module name="FinalLocalVariable">
+ <property name="tokens" value="PARAMETER_DEF,VARIABLE_DEF"/>
+ <property name="validateEnhancedForLoopVariable" value="true"/>
+ </module>
+ <module name="SuppressionCommentFilter">
+ <property name="offCommentFormat" value="\bCheck[Ss]tyle:\s*([\w|]+)\s+OFF\b"/>
+ <property name="onCommentFormat" value="\bCheck[Ss]tyle:\s*([\w|]+)\s+ON\b"/>
+ <property name="checkFormat" value="$1"/>
+ </module>
+ <module name="MissingJavadocMethod"/>
+ <module name="MissingJavadocPackage"/>
+ <module name="MissingJavadocType"/>
+ <module name="InvalidJavadocPosition"/>
+ </module>
+ <module name="FileTabCharacter"/>
+ <module name="FileLength">
+ <property name="max" value="1000"/>
+ </module>
+ <module name="Header">
+ <property name="header" value="/*\n * Licensed to the University Corporation for Advanced Internet Development,\n * Inc. (UCAID) under one or more contributor license agreements. See the\n * NOTICE file distributed with this work for additional information regarding\n * copyright ownership. The UCAID licenses this file to You under the Apache\n * License, Version 2.0 (the "License"); you may not use this file except in\n * compliance with the License. You may obtain a cop [...]
+ </module>
+ <module name="JavadocPackage"/>
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ </module>
+</module>
diff --git a/java-idp-oidc-rp-api/pom.xml b/java-idp-oidc-rp-api/pom.xml
new file mode 100644
index 0000000..67d9a6d
--- /dev/null
+++ b/java-idp-oidc-rp-api/pom.xml
@@ -0,0 +1,51 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>idp-plugin-oidc-rp-api</artifactId>
+ <packaging>jar</packaging>
+ <name>Shibboleth IdP :: Plugins :: OIDC RP Login Flow API</name>
+ <description>IdP java-idp-oidc-rp plugin API.</description>
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+ <automatic.module.name>net.shibboleth.idp.plugin.authn.oidc.rp.api</automatic.module.name>
+ </properties>
+
+ <dependencies>
+ <!-- Provided dependencies -->
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/Version.java b/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/Version.java
new file mode 100644
index 0000000..da5b500
--- /dev/null
+++ b/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/Version.java
@@ -0,0 +1,32 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp;
+
+import javax.annotation.Nullable;
+
+/** Class for getting and printing the version of the plugin. */
+public final class Version {
+
+ /** IdP version. */
+ @Nullable private static final String VERSION = Version.class.getPackage().getImplementationVersion();
+
+ /** Constructor. */
+ private Version() {
+ }
+
+ /**
+ * Main entry point to program.
+ *
+ * @param args command line arguments
+ */
+ public static void main(final String[] args) {
+ System.out.println(VERSION);
+ }
+
+ /**
+ * Get the version of the plugin.
+ *
+ * @return version of the plugin
+ */
+ @Nullable public static String getVersion() {
+ return VERSION;
+ }
+}
\ No newline at end of file
diff --git a/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java b/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java
new file mode 100644
index 0000000..7c16c92
--- /dev/null
+++ b/java-idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OpenIDConnectContext.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.Display;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.Prompt;
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+
+/**
+ * Context that carries OpenID Connect client and provider metadata used during authentication of a subject from
+ * an OpenID Connect Provider.
+ *
+ * <p>Any OPTIONAL parameters of an ODIC authentication request are allowably <code>null</code> - even Lists.
+ * For a list of OPTIONAL authn parameters, see OpenID Connect Core 1.0 section 3.1.2.1</p>
+ *
+ *
+ * @parent {@link AuthenticationContext}
+ * @added After the RelyingPartyUIContext has been added, before external redirect to the OpenID Connect servlet.
+ *
+ * @since 4.0.0
+ */
+public class OpenIDConnectContext extends BaseContext {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(OpenIDConnectContext.class);
+
+ /** Client Id. */
+ @Nullable private ClientID clientID;
+
+ /** Client Secret. */
+ @Nullable private Secret clientSecret;
+
+ /** Scope. Must contain an openid scope.*/
+ @Nullable private Scope scope;
+
+ /**
+ * OIDC Prompt. Specifies whether the Authorization Server prompts
+ * the End-User for re-authentication and consent.
+ */
+ @Nullable private Prompt prompt;
+
+ /** OIDC Authentication Class Reference values.*/
+ @Nullable @NonnullElements private List<ACR> acrs;
+
+ /**
+ * OIDC Display. Value that specifies how the Authorization Server
+ * displays the authentication and consent user interface pages to the End-User.
+ */
+ @Nullable private Display display;
+
+ /** OIDC provider metadata. */
+ @Nullable private OIDCProviderMetadata oIDCProviderMetadata;
+
+ /** OIDC authentication request URI. */
+ @Nullable private URI authenticationRequestURI;
+
+ /** OIDC authentication response URI. */
+ @Nullable private URI authenticationResponseURI;
+
+ /** OIDC authentication success response. */
+ @Nullable private AuthenticationSuccessResponse authSuccessResponse;
+
+ /** OIDC token response. */
+ @Nullable private OIDCTokenResponse oidcTknResponse;
+
+ /**
+ * State parameter. Opaque value used to maintain state between the request
+ * and the callback. Typically for CSRF protection.
+ */
+ @Nullable private State state;
+
+ /**
+ * Nonce parameter. String value used to associate a Client session with an
+ * ID Token, and to mitigate replay attacks.
+ */
+ @Nullable private Nonce nonce;
+
+ /** Redirect URI to which the response will be sent. */
+ @Nullable private URI redirectURI;
+
+ /** ID Token from the token endpoint response. */
+ @Nullable private JWT idToken;
+
+ /** Resolved attributes. */
+ //TODO Resolved Attributes never get added to the Context.
+ @Nullable private Map<String, IdPAttribute> resolvedIdPAttributes;
+
+
+ /**
+ * Get the resolved attributes.
+ *
+ * @return resolved attributes, may be null.
+ */
+ @Nullable @Live public Map<String, IdPAttribute> getResolvedIdPAttributes() {
+ return resolvedIdPAttributes;
+ }
+
+ /**
+ * Set resolved attributes to context to help form requested objects.
+ *
+ * @param idPAttributes resolved attributes
+ */
+ public void setResolvedIdPAttributes(@Nullable final Map<String, IdPAttribute> idPAttributes) {
+ resolvedIdPAttributes = idPAttributes;
+ }
+
+ /**
+ * Get the id token received from OpenID Connect Provider.
+ *
+ * @return id token or <code>null</code> if not set.
+ */
+ @Nullable public JWT getIDToken() {
+ return idToken;
+ }
+
+ /**
+ * Set the id token received from OpenID Connect Provider.
+ *
+ * @param token from the OpenID Connect Provider.
+ */
+ public void setIDToken(@Nullable final JWT token) {
+ idToken = token;
+ }
+
+ /**
+ * Get the redirect URI of the client.
+ *
+ * @return redirect uri
+ */
+ @Nullable public URI getRedirectURI() {
+ return redirectURI;
+ }
+
+ /**
+ * Set the redirect URI of the client. The redirect URI is required for authorisation code flows.
+ *
+ * @param uri of the client, must not be <code>null</code>.
+ */
+ public void setRedirectURI(@Nonnull final URI uri) {
+ redirectURI = Constraint.isNotNull(uri, "Redirect URI cannot be null");
+ }
+
+ /**
+ * Get the OAuth2 client id.
+ *
+ * @return client id.
+ */
+ @Nullable public ClientID getClientID() {
+ return clientID;
+ }
+
+ /**
+ * Set the Oauth2 client id. The client id can not be <code>null</code>.
+ *
+ * @param id Oauth2 Client ID, must not be <code>null</code>.
+ */
+ public void setClientID(@Nonnull final ClientID id) {
+ clientID = Constraint.isNotNull(id, "Client ID cannot be null");
+ }
+
+ /**
+ * Set the client secret.
+ *
+ * @param secret of the client, must not be <code>null</code>.
+ */
+ public void setClientSecret(@Nonnull final Secret secret) {
+ clientSecret = Constraint.isNotNull(secret, "Client secret cannot be null");
+ }
+
+ /**
+ * Get the client secret.
+ *
+ * @return client secret.
+ */
+ @Nullable public Secret getClientSecret() {
+ return clientSecret;
+ }
+
+ /**
+ * Get the scope used for authentication request.
+ *
+ * @return scope
+ */
+ public Scope getScope() {
+ return scope;
+ }
+
+ /**
+ * Set the scope used for forming an authentication request.
+ *
+ * @param scp scope of the request, must not be <code>null</code>
+ */
+ public void setScope(@Nonnull final Scope scp) {
+ scope = Constraint.isNotNull(scp, "Scope cannot be null");
+ }
+
+ /**
+ * Get the value for prompt used for forming an authentication request.
+ *
+ * @return prompt
+ */
+ @Nullable public Prompt getPrompt() {
+ return prompt;
+ }
+
+ /**
+ * Set the value for prompt used for forming an authentication request.
+ *
+ * @param prmpt used for forming an authentication request
+ */
+ public void setPrompt(@Nullable final Prompt prmpt) {
+ prompt = prmpt;
+ }
+
+ /**
+ * Get the values for acr used for forming authentication request.
+ * Allowably a <code>null</code> {@link List} if not set.
+ *
+ * @return acrs or <code>null</code> if not set.
+ */
+ @Nullable @NonnullElements @Unmodifiable @NotLive public List<ACR> getAcrs() {
+ return acrs;
+ }
+
+ /**
+ * Set the values for acr used for forming authentication request. Sets a
+ * <code>null</code> {@link List} if no ACRs are defined.
+ *
+ * @param acrList values used for forming authentication request
+ */
+ public void setAcrs(@Nullable @NonnullElements final List<ACR> acrList) {
+ if (acrList!=null) {
+ ImmutableList.copyOf(Collections2.filter(acrList, Predicates.notNull()));
+ } else {
+ acrs = null;
+ }
+
+ }
+
+ /**
+ * Get the display value used for forming authentication request.
+ *
+ * @return display
+ */
+ @Nullable public Display getDisplay() {
+ return display;
+ }
+
+ /**
+ * Set the display value used for forming authentication request.
+ *
+ * @param dspl value for forming authentication request.
+ */
+ public void setDisplay(@Nullable final Display dspl) {
+ display = dspl;
+ }
+
+ /**
+ * Get OpenID Connect Provider metadata.
+ *
+ * @return OpenID Connect Provider metadata
+ */
+ @Nullable public OIDCProviderMetadata getoIDCProviderMetadata() {
+ return oIDCProviderMetadata;
+ }
+
+ /**
+ * Set OpenID Connect Provider metatadata.
+ *
+ * @param oIDCPrvdrMtdt OpenID Connect Provider metadata, must not be <code>null</code>.
+ */
+ public void setoIDCProviderMetadata(@Nonnull final OIDCProviderMetadata oIDCPrvdrMtdt) {
+ oIDCProviderMetadata = Constraint.isNotNull(oIDCPrvdrMtdt, "OpenID Connect metadata location cannot be null ");
+ }
+
+ /**
+ * Returns the oidc authentication request URI to be used for authentication.
+ *
+ * @return request URI for authentication
+ */
+ @Nullable public URI getAuthenticationRequestURI() {
+ return authenticationRequestURI;
+ }
+
+ /**
+ * Set the oidc provider request URI for authentication.
+ *
+ * @param request to be used for authentication, must not be <code>null</code>.
+ */
+ public void setAuthenticationRequestURI(@Nonnull final URI request) {
+ authenticationRequestURI = Constraint.isNotNull(request,
+ "Authentication Request URI request can not be null");
+
+ }
+
+ /**
+ * Returns the OIDC token response or null.
+ *
+ * @return token response
+ */
+ @Nullable public OIDCTokenResponse getOidcTokenResponse() {
+ return oidcTknResponse;
+ }
+
+ /**
+ * Sets both the token response and the <code>idToken</code> from the
+ * token response if it is not <code>null</code>.
+ *
+ * @param oidcTokenResponse response from provider
+ */
+ public void setOidcTokenResponse(@Nullable final OIDCTokenResponse oidcTokenResponse) {
+ oidcTknResponse = oidcTokenResponse;
+ if (oidcTokenResponse != null && oidcTokenResponse.getOIDCTokens() != null) {
+ idToken = oidcTokenResponse.getOIDCTokens().getIDToken();
+ }
+
+ }
+
+ /**
+ * Getter for State parameter.
+ *
+ * @return state parameter
+ */
+ @Nullable public State getState() {
+ return state;
+ }
+
+ /**
+ * Setter for State parameter.
+ *
+ * @param stateParam parameter
+ */
+ public void setState(@Nullable final State stateParam) {
+ state = stateParam;
+ }
+
+ /**
+ * Getter for Nonce parameter.
+ *
+ * @return nonce parameter.
+ */
+ @Nullable public Nonce getNonce() {
+ return nonce;
+ }
+
+ /**
+ * Setter for Nonce parameter.
+ *
+ * @param newNonce nonce parameter
+ */
+ public void setNonce(@Nullable final Nonce newNonce) {
+ nonce = newNonce;
+ }
+
+ /**
+ * Returns the authentication success response or null.
+ *
+ * @return authentication success response.
+ */
+ @Nullable public AuthenticationSuccessResponse getAuthenticationSuccessResponse() {
+ return authSuccessResponse;
+ }
+
+ /**
+ * Sets the authentication success response.
+ *
+ * @param authenticationSuccessResponse response from the OpenIDConenct Provider
+ */
+ public void setAuthenticationSuccessResponse(
+ @Nonnull final AuthenticationSuccessResponse authenticationSuccessResponse) {
+ authSuccessResponse = Constraint.isNotNull(authenticationSuccessResponse,
+ "AuthenticationSuccessResponse can not be null");
+
+ }
+
+ /**
+ * Returns the authentication response URI or null.
+ *
+ * @return authentication response URI
+ */
+ @Nullable public URI getAuthenticationResponseURI() {
+ return authenticationResponseURI;
+ }
+
+ /**
+ * Parses the authentication response URI from the authenticationResponseHttpRequest.
+ *
+ * @param authenticationResponseHttpRequest request
+ *
+ * @throws URISyntaxException if request has malformed URL and/or query parameters
+ */
+ public void setAuthenticationResponseURI(@Nonnull final HttpServletRequest authenticationResponseHttpRequest)
+ throws URISyntaxException {
+
+ Constraint.isNotNull(authenticationResponseHttpRequest, "Authentication response http request can not be null");
+
+ final String temp = authenticationResponseHttpRequest.getRequestURL() + "?"
+ + authenticationResponseHttpRequest.getQueryString();
+ authenticationResponseURI = new URI(temp);
+
+ }
+
+
+
+}
diff --git a/java-idp-oidc-rp-dist/pom.xml b/java-idp-oidc-rp-dist/pom.xml
new file mode 100644
index 0000000..09e8708
--- /dev/null
+++ b/java-idp-oidc-rp-dist/pom.xml
@@ -0,0 +1,51 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>idp-plugin-oidc-rp-dist</artifactId>
+ <name>Shibboleth IdP :: Plugins :: OIDC RP Login Flow Distribution</name>
+ <description>IdP java-idp-oidc-rp plugin packaging.</description>
+ <packaging>pom</packaging>
+
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+ <dist.plugin.finalName>idp-plugin-java-idp-oidc-rp-${project.version}</dist.plugin.finalName>
+ </properties>
+
+ <build>
+ <plugins>
+ <!-- Assemble -->
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+ <descriptor>src/main/assembly/assembly-tgz.xml</descriptor>
+ <descriptor>src/main/assembly/assembly-zip.xml</descriptor>
+ </descriptors>
+ <finalName>${dist.plugin.finalName}</finalName>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+
+
+</project>
\ No newline at end of file
diff --git a/java-idp-oidc-rp-dist/src/main/assembly/assembly-tgz.xml b/java-idp-oidc-rp-dist/src/main/assembly/assembly-tgz.xml
new file mode 100644
index 0000000..679e359
--- /dev/null
+++ b/java-idp-oidc-rp-dist/src/main/assembly/assembly-tgz.xml
@@ -0,0 +1,72 @@
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>bin</id>
+ <formats>
+ <format>tar.gz</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <directory>src/main/resources/</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+
+ <!-- The plugin property file -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-impl/target/classes/net/shibboleth/idp/plugin/authn/oidc/rp/</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>plugin.properties</include>
+ </includes>
+ </fileSet>
+
+ <!-- The implementation jar -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-impl/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>idp-plugin-oidc-rp-impl-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The API jar -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-api/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>idp-plugin-oidc-rp-api-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The dependencies -->
+ <fileSet>
+ <directory>../idp-plugin-oidc-rp-impl/target/dependency</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+
+ <!-- The signing certificates -->
+ <fileSet>
+ <directory>src/main/resources/bootstrap</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>keys.txt</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+</assembly>
diff --git a/java-idp-oidc-rp-dist/src/main/assembly/assembly-zip.xml b/java-idp-oidc-rp-dist/src/main/assembly/assembly-zip.xml
new file mode 100644
index 0000000..ac4aa21
--- /dev/null
+++ b/java-idp-oidc-rp-dist/src/main/assembly/assembly-zip.xml
@@ -0,0 +1,72 @@
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>bin</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <directory>src/main/resources/</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+
+ <!-- The plugin property file -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-impl/target/classes/net/shibboleth/idp/plugin/authn/oidc/rp/</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>plugin.properties</include>
+ </includes>
+ </fileSet>
+
+ <!-- The implementation jar -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-impl/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>idp-plugin-oidc-rp-impl-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The API jar -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-api/target</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>idp-plugin-oidc-rp-api-*.jar</include>
+ </includes>
+ <excludes>
+ <exclude>*test*.jar</exclude>
+ <exclude>*javadoc.jar</exclude>
+ <exclude>*sources.jar</exclude>
+ </excludes>
+ </fileSet>
+
+ <!-- The dependencies -->
+ <fileSet>
+ <directory>../java-idp-oidc-rp-impl/target/dependency</directory>
+ <outputDirectory>webapp/WEB-INF/lib</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+
+ <!-- The signing certificates -->
+ <fileSet>
+ <directory>src/main/resources/bootstrap</directory>
+ <outputDirectory>bootstrap</outputDirectory>
+ <includes>
+ <include>keys.txt</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+</assembly>
diff --git a/java-idp-oidc-rp-dist/src/main/resources/bootstrap/keys.txt b/java-idp-oidc-rp-dist/src/main/resources/bootstrap/keys.txt
new file mode 100644
index 0000000..921c030
--- /dev/null
+++ b/java-idp-oidc-rp-dist/src/main/resources/bootstrap/keys.txt
@@ -0,0 +1 @@
+FIXME: Plugin keys
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/.checkstyle b/java-idp-oidc-rp-impl/.checkstyle
new file mode 100644
index 0000000..162e469
--- /dev/null
+++ b/java-idp-oidc-rp-impl/.checkstyle
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+ <fileset name="all" enabled="true" check-config-name="shibboleth" local="false">
+ <file-match-pattern match-pattern="." include-pattern="true"/>
+ </fileset>
+</fileset-config>
diff --git a/java-idp-oidc-rp-impl/pom.xml b/java-idp-oidc-rp-impl/pom.xml
new file mode 100644
index 0000000..6f2cd13
--- /dev/null
+++ b/java-idp-oidc-rp-impl/pom.xml
@@ -0,0 +1,118 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>idp-plugin-oidc-rp-impl</artifactId>
+ <packaging>jar</packaging>
+ <name>Shibboleth IdP :: Plugins :: OIDC RP Login Flow Impl</name>
+ <description>IdP java-idp-oidc-rp plugin implementation.</description>
+
+ <properties>
+ <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+ <automatic.module.name>net.shibboleth.idp.plugin.authn.oidc.rp.impl</automatic.module.name>
+ </properties>
+
+ <dependencies>
+
+ <!-- compile time intra project dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-api</artifactId>
+ </dependency>
+ <!-- provided dependencies -->
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.utilities</groupId>
+ <artifactId>java-support</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-crypto-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Service API and Plugin Description dependency -->
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-admin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-admin-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestSections>
+ <manifestSection>
+ <name>org/example/com/java-idp-oidc-rp/</name>
+ <manifestEntries>
+ <Implementation-Title>${project.artifactId}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>Shibboleth</Implementation-Vendor>
+ </manifestEntries>
+ </manifestSection>
+ </manifestSections>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies-test</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.target.directory}</outputDirectory>
+ <includeScope>runtime</includeScope>
+ <excludeTransitive>true</excludeTransitive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
new file mode 100644
index 0000000..ecff9a9
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
@@ -0,0 +1,25 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp;
+
+import java.io.IOException;
+
+import net.shibboleth.idp.module.ModuleException;
+import net.shibboleth.idp.module.impl.PluginIdPModule;
+
+
+/**
+ * {@link IdPModule IdP Module} implementation.
+ */
+public class OIDCRPModule extends PluginIdPModule{
+
+ /**
+ * Constructor.
+ *
+ * @throws ModuleException on error
+ * @throws IOException on error
+ */
+ public OIDCRPModule() throws IOException, ModuleException {
+ super(OIDCRPModule.class);
+ }
+
+
+}
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
new file mode 100644
index 0000000..27fd2c8
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
@@ -0,0 +1,27 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp;
+
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.IdPPlugin;
+import net.shibboleth.idp.plugin.PluginException;
+import net.shibboleth.idp.plugin.PropertyDrivenIdPPlugin;
+
+/**
+ * Plugin description about the webauthn plugin.
+ */
+public class OIDCRPPlugin extends PropertyDrivenIdPPlugin {
+
+ /**
+ * Constructor.
+ *
+ * @param claz type of plugin
+ *
+ * @throws IOException if properties can't be loaded
+ * @throws PluginException if another error occurs
+ */
+ public OIDCRPPlugin(@Nonnull final Class<? extends IdPPlugin> claz) throws IOException, PluginException {
+ super(claz);
+ }
+
+}
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
new file mode 100644
index 0000000..bb9fd43
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import net.shibboleth.idp.authn.ExternalAuthentication;
+import net.shibboleth.idp.authn.ExternalAuthenticationException;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ *
+ * Servlet compatible with the {@link ExternalAuthentication} interface that begins, by HTTP redirect, an
+ * OpenID Connect authentication request via OAuth 2.0 Authorization Code Flow to an OpenID Connect
+ * Identity Provider (an OAuth 2.0 Authorization Server that supports OpenID Connect).
+ * Although other flows could be configured, they shouldn't.
+ *
+ * <p>As this is part of an OpenID Connect authentication request, the <code>openid</code> scope is required,
+ * and as such is automatically set by the {@link SetOIDCInformation} action.</p>
+ *
+ * <p>Adds the {@link OpenIDConnectContext} to the {@link HttpSession} for extraction after the OAuth 2.0
+ * authorisation code response has been consumed, and control as returned to the IdP.</p>
+ *
+ * <p>Servlet compatible with the @link {@link ExternalAuthentication} interface that consumes the HTTP Request from
+ * an OpenID Connect provider (OAuth 2.0 authorization endpoint), which was issued in response to the Authorization
+ * Code request made by the {@link OpenIDConnectStartServlet}. </p>
+ * <p>
+ * The {@link HttpServletRequest} is placed inside the {@link OpenIDConnectContext} for interrogation later in the flow.
+ * </p>
+ *
+ * @since 4.0.0
+ */
+ at ThreadSafe
+ at Controller
+ at RequestMapping("/Authn/OIDC/RP")
+public class AuthorizationController {
+
+ /** Prefix for the session attribute ids. */
+ @Nonnull public static final String SESSION_ATTR_PREFIX =
+ "net.shibboleth.idp.authn.oidc.impl.OpenIdConnectStartServlet.";
+
+ /** Session attribute id for flow conversation key. */
+ @Nonnull public static final String SESSION_ATTR_FLOWKEY = SESSION_ATTR_PREFIX + "key";
+
+ /** Session attribute id for {@link OpenIDConnectContext}. */
+ @Nonnull public static final String SESSION_ATTR_SUCTX = SESSION_ATTR_PREFIX + "openIdConnectContext";
+
+ /** Serial UID. */
+ private static final long serialVersionUID = -3162157736238514852L;
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
+
+
+ @GetMapping("/auth")
+ public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest,
+ @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
+
+ try {
+ final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
+ httpRequest.getSession().setAttribute(SESSION_ATTR_FLOWKEY, key);
+
+ @SuppressWarnings("rawtypes") final ProfileRequestContext profileRequestContext =
+ (ProfileRequestContext) httpRequest.getAttribute(ProfileRequestContext.BINDING_KEY);
+ if (profileRequestContext == null) {
+ throw new ExternalAuthenticationException("Could not access profileRequestContext from the request");
+ }
+ final AuthenticationContext authenticationContext =
+ (AuthenticationContext) profileRequestContext.getSubcontext(AuthenticationContext.class);
+ if (authenticationContext == null) {
+ throw new ExternalAuthenticationException("Could not get AuthenticationContext from the request");
+ }
+ final OpenIDConnectContext openIDConnectContext =
+ (OpenIDConnectContext) authenticationContext
+ .getSubcontext(OpenIDConnectContext.class);
+ if (openIDConnectContext == null) {
+ throw new ExternalAuthenticationException(
+ "Could not get OpenIdConnectContext from the request");
+ }
+ //TODO do not put state in session, put in the request.
+ httpRequest.getSession().setAttribute(SESSION_ATTR_SUCTX, openIDConnectContext);
+ log.debug("Redirecting http-agent to {}", openIDConnectContext.getAuthenticationRequestURI());
+ httpResponse.sendRedirect(openIDConnectContext.getAuthenticationRequestURI().toString());
+ } catch (final ExternalAuthenticationException e) {
+ log.error("Error processing external authentication request", e);
+ throw new ServletException("Error processing external authentication request", e);
+ }
+
+ }
+
+ @GetMapping("/callback")
+ public void authorizationCallback(@Nonnull final HttpServletRequest httpRequest,
+ @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
+
+ try {
+ final HttpSession session = httpRequest.getSession();
+ if (session == null) {
+ throw new ExternalAuthenticationException("No session exists, this URL shouldn't be called directly");
+ }
+ final String key = StringSupport.trimOrNull((String) httpRequest.getSession()
+ .getAttribute(AuthorizationController.SESSION_ATTR_FLOWKEY));
+ if (key == null) {
+ throw new ExternalAuthenticationException(
+ "Could not find value for " + AuthorizationController.SESSION_ATTR_FLOWKEY);
+ }
+ final OpenIDConnectContext openIDConnectContext =
+ (OpenIDConnectContext) httpRequest.getSession()
+ .getAttribute(AuthorizationController.SESSION_ATTR_SUCTX);
+ if (openIDConnectContext == null) {
+ throw new ExternalAuthenticationException(
+ "Could not find value for " + AuthorizationController.SESSION_ATTR_SUCTX);
+ }
+ log.trace("Attempting URL {}?{}", httpRequest.getRequestURL(), httpRequest.getQueryString());
+ try {
+ openIDConnectContext.setAuthenticationResponseURI(httpRequest);
+ } catch (final URISyntaxException e) {
+ throw new ExternalAuthenticationException("Could not parse response URI", e);
+ }
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ } catch (final ExternalAuthenticationException e) {
+ log.error("Could not finish the external authentication", e);
+ throw new ServletException("Error finishing the external authentication", e);
+ }
+
+ }
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
new file mode 100644
index 0000000..d4b047d
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractExtractionAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.SerializeException;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+
+/**
+ * An action that exchanges the OAuth 2.0 authorization code inside the {@link OpenIDConnectContext} for
+ * an id_token from the OpenID Connect Provider's Token Endpoint. The ID Token is placed inside the
+ * {@link OpenIDConnectContext}.
+ *
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @post If getIDToken() !=null the method returns immediately. Otherwise, if the token endpoint returns an
+ * {@link OIDCTokenResponse} whose indicatesSuccess()==true, the token is attached to the {@link OpenIDConnectContext}.
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
+ *
+ * @since 4.0.0
+ */
+public class GetOIDCTokenResponse extends AbstractExtractionAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(GetOIDCTokenResponse.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find OIDC context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ return;
+ }
+ if (oidcCtx.getIDToken() != null) {
+ log.debug("id_token already exists, nothing to fetch from token endpoint");
+
+ return;
+ }
+ final AuthenticationSuccessResponse response = oidcCtx.getAuthenticationSuccessResponse();
+
+ if (response == null) {
+ log.info("{} Authentication success response not found in OpenIDConnectContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ return;
+ }
+ final AuthorizationCode code = response.getAuthorizationCode();
+ //TODO P.S. code could be null, should not be.
+ final AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, oidcCtx.getRedirectURI());
+ //TODO P.S. neither should be null, both could be (although not after SetOIDCInformation initilises the context)
+ final ClientAuthentication clientAuth = new ClientSecretBasic(oidcCtx.getClientID(), oidcCtx.getClientSecret());
+
+ log.trace("{} Using the following OIDC token endpoint URI: {}", getLogPrefix(),
+ oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI());
+
+ final TokenRequest tokenRequest =
+ new TokenRequest(oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI(), clientAuth, codeGrant);
+ final OIDCTokenResponse oidcTokenResponse;
+ try {
+ final TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
+ // TokenResponse can only be TokenErrorResponse or OIDCTokenResponse
+ if (tokenResponse instanceof OIDCTokenResponse) {
+
+ oidcTokenResponse = (OIDCTokenResponse) tokenResponse;
+ oidcCtx.setOidcTokenResponse(oidcTokenResponse);
+
+ if (log.isTraceEnabled() && oidcTokenResponse.getOIDCTokens().getIDToken() != null) {
+ log.trace("{} Retrieved id_token '{}'",getLogPrefix(),
+ oidcTokenResponse.getOIDCTokens().getIDToken().getParsedString());
+ }
+
+ } else {
+ final TokenErrorResponse errorResponse = (TokenErrorResponse) tokenResponse;
+ log.warn("{} Error in retrieving id_token, response error is {}", getLogPrefix(),
+ errorResponse.getErrorObject());
+ // should map error object OAuth2 Error types to new or existing event ids.
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
+ return;
+ }
+
+ } catch (final SerializeException | IOException | ParseException e) {
+ log.error("{} token exchange failed", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
+ return;
+ }
+
+
+ }
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
new file mode 100644
index 0000000..7f10122
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
@@ -0,0 +1,583 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+import com.nimbusds.openid.connect.sdk.Display;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
+import com.nimbusds.openid.connect.sdk.Prompt;
+import com.nimbusds.openid.connect.sdk.Prompt.Type;
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.PlainHeader;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ *
+ * An action that populates the {@link AuthenticationContext} with a freshly built {@link OpenIDConnectContext}.
+ *
+ * <p>A singleton instance of this class can be created and shared between authentication requests.</p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
+ * @post The AuthenticationContext is modified as above.
+ *
+ * @since 4.0.0
+ */
+ at SuppressWarnings("rawtypes")
+public class SetOIDCInformation extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SetOIDCInformation.class);
+
+ /** Context to look attributes for. */
+ @Nonnull private Function<ProfileRequestContext, AttributeContext> attributeContextLookupStrategy;
+
+ /** Redirect URI. */
+ @Nonnull private URI redirectURI;
+
+ /** Client Id. */
+ @Nonnull private ClientID clientID;
+
+ /** Client Secret. */
+ @Nonnull private Secret clientSecret;
+
+ /** Response type, default is code flow. */
+ @Nonnull private ResponseType responseType = new ResponseType(ResponseType.Value.CODE);
+
+ /** Scope. Must contain an openid scope.*/
+ @Nonnull private Scope scope = new Scope(OIDCScopeValue.OPENID);
+
+ /**
+ * OIDC Prompt. Specifies whether the Authorization Server prompts
+ * the End-User for reauthentication and consent.
+ */
+ @Nullable private Prompt prompt;
+
+ /** OIDC Authentication Class Reference values. */
+ @Nullable @NonnullElements private List<ACR> acrs;
+
+ /**
+ * OIDC Display. Value that specifies how the Authorization Server
+ * displays the authentication and consent user interface pages to the End-User.
+ */
+ @Nullable private Display display;
+
+ /** Private key for signing the request object. */
+ @Nullable private PrivateKey signPrvKey;
+
+ /** Algorithm used for signing the request object. Defaults to the required
+ * singing algorithm of RSA SHA-256*/
+ @Nonnull private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
+
+ /** key id for key used for signing the request object. */
+ @Nonnull private String keyID = "id";
+
+ /** Request object claims. */
+ @Nullable private Map<String, String> requestClaims;
+
+ /** OIDC provider metadata. */
+ @Nonnull private OIDCProviderMetadata oIDCProviderMetadata;
+
+ /** Constructor. */
+ public SetOIDCInformation() {
+ attributeContextLookupStrategy =
+ new ChildContextLookup<>(AttributeContext.class).compose(
+ new ChildContextLookup<>(RelyingPartyContext.class));
+
+ }
+
+ /**
+ * Set the private key used for signing request object.
+ *
+ * @param key signing key
+ */
+ public void setPrivKey(@Nullable final PrivateKey key) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ signPrvKey = key;
+ }
+
+ /**
+ * Set the RSA algorithm used for signing the request object. Default is RS256.
+ *
+ * @param algorithm used for signing, must not be <code>null</code>.
+ */
+ public void setJwsAlgorithm(@Nonnull final JWSAlgorithm algorithm) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ jwsAlgorithm = Constraint.isNotNull(algorithm,"OpenID Connect JWS Request algorithm can not be null");
+
+ }
+
+ /**
+ * Set the key id of the key.
+ *
+ * @param id for the key, must not be <code>null</code>.
+ */
+ public void setKeyID(@Nonnull @NotEmpty final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ keyID =Constraint.isNotNull(StringSupport.trimOrNull(id), "OpenID Connect key ID cannot be null");
+ }
+
+ /**
+ *
+ * If this is set (i.e. not <code>null</code>) request claims will be built as a
+ * JWT Request Object. See OpenID Connect Core section 6.1. If the <code>signPrvKey</code>
+ * is set, the JWT will also be signed.
+ *
+ * <p>The key of the mapping is the name of the requested claim. The value of the requested claim
+ * is either:
+ * <ul>
+ * <li>Null, if the claim is being requested in a default manor</li>
+ * <li><code>{"essential":true}</code> if the value is essential. The default is
+ * <code>{"essential":false}</code></li>
+ * <li>A specific value for the claim. This value must be a valid value for that claim.</li>
+ * <li>A specific set of values for the claim. These values must be valid value for that claim.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * If used, the OpenID Connect Provider must support it, as specified in the
+ * <code>request_parameter_supported</code> parameter of the Providers discovery metadata.
+ *
+ * @param claims map of requested claims
+ */
+ public void setRequestClaims(@Nullable final Map<String, String> claims) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ requestClaims = claims;
+ }
+
+ /**
+ * Sets the response type. Default is code.
+ *
+ * @param type space-delimited list of one or more authorization response types. Must not be <code>null</code>.
+ * @throws ParseException if response type cannot be parsed
+ */
+ public void setResponseType(@Nonnull @NotEmpty final String type) throws ParseException {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(StringSupport.trimOrNull(type), "OpenID Connect response type cannot be null or empty");
+
+ responseType = ResponseType.parse(type);
+ }
+
+ /**
+ * Setter for Oauth2 client id.
+ *
+ * @param oauth2ClientID Oauth2 Client ID, must not be <code>null</code>.
+ */
+ public void setClientID(@Nonnull @NotEmpty final String oauth2ClientID) {
+
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientID),
+ "OpenID Connect client ID cannot be null or empty");
+
+ clientID = new ClientID(oauth2ClientID);
+ }
+
+ /**
+ * Setter for OAuth2 Client secret.
+ *
+ * @param oauth2ClientSecret OAuth2 Client Secret, must not be <code>null</code>.
+ */
+ public void setClientSecret(@Nonnull @NotEmpty final String oauth2ClientSecret) {
+
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientSecret),
+ "OpenID Connect client secret cannot be null or empty");
+
+ clientSecret = new Secret(oauth2ClientSecret);
+ }
+
+ /**
+ * Setter for the OAuth 2.0 redirect URI the OIDC provider will return to. The constructed URI should not be empty.
+ *
+ * @param redirect OAuth2 redirect uri, must not be <code>null</code>.
+ */
+
+ public void setRedirectURI(@Nonnull @NotEmpty final URI redirect) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(redirect, "OpenID Connect redirect URI cannot be null");
+ Constraint.isNotEmpty(redirect.toString(), "OpenID Connect redirect URI cannot be empty");
+
+ redirectURI = redirect;
+
+ }
+
+ /**
+ * Setter for OpenId Provider Metadata resource.
+ *
+ * <p>Constructs a URI from the <code>metadataLocation</code> and attempts to connect,
+ * stream, and parse its content into an {@link OIDCProviderMetadata} instance.</p>
+ *
+ * @param metadataLocation OpenId Provider Metadata location, must not be <code>null</code>.
+ *
+ * @throws URISyntaxException if metadataLocation is not a URI
+ * @throws IOException if metadataLocation cannot be read
+ * @throws ParseException if metadataLocation has wrong content
+ */
+ //TODO: not like this? HttpClient, metadata service etc.
+ public void setProviderMetadataLocation(@Nonnull @NotEmpty final String metadataLocation)
+ throws URISyntaxException, IOException, ParseException {
+
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(StringSupport.trimOrNull(metadataLocation),
+ "OpenID Connect metadata location cannot be null or empty");
+
+ final URI issuerURI = new URI(metadataLocation);
+ final URL providerConfigurationURL = issuerURI.resolve(".well-known/openid-configuration").toURL();
+ final InputStream stream = providerConfigurationURL.openStream();
+ String providerInfo = null;
+ try (java.util.Scanner s = new java.util.Scanner(stream)) {
+ providerInfo = s.useDelimiter("\\A").hasNext() ? s.next() : "";
+ }
+ oIDCProviderMetadata = OIDCProviderMetadata.parse(providerInfo);
+
+ }
+
+ /**
+ * Setter for OpenId Scope values. New ones are be added to the {@value OIDCScopeValue#OPENID} scope.
+ *
+ * @param oidcScopes OpenID Connect Scope values, can be <code>null</code> and will be ignored.
+ */
+ public void setScope(@Nullable final List<String> oidcScopes) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ //As the OPENID scope is always set. New scopes can be null.
+ if (oidcScopes==null) {
+ return;
+ }
+
+ for (final String oidcScope : oidcScopes) {
+ switch (oidcScope.toUpperCase()) {
+ case "ADDRESS":
+ scope.add(OIDCScopeValue.ADDRESS);
+ break;
+ case "EMAIL":
+ scope.add(OIDCScopeValue.EMAIL);
+ break;
+ case "OFFLINE_ACCESS":
+ scope.add(OIDCScopeValue.OFFLINE_ACCESS);
+ break;
+ case "PHONE":
+ scope.add(OIDCScopeValue.PHONE);
+ break;
+ case "PROFILE":
+ scope.add(OIDCScopeValue.PROFILE);
+ break;
+ default:
+ }
+ }
+
+ }
+
+ /**
+ * Setter for the OpenID Connect Prompt value.
+ *
+ * @param oidcPrompt OpenID Connect Prompt value.
+ */
+ public void setPrompt(@Nullable final String oidcPrompt) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ prompt = new Prompt(oidcPrompt);
+ }
+
+ /**
+ * Setter for the OpenID Connect Authentication Context Class Reference (ACR) values.
+ *
+ * @param oidcAcrs OpenId ACR values
+ */
+ public void setAcr(@Nullable @NonnullElements final List<String> oidcAcrs) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (null != oidcAcrs) {
+ for (final String oidcAcr : oidcAcrs) {
+ final ACR acr = new ACR(oidcAcr);
+
+ if (acrs == null) {
+ acrs = new ArrayList<ACR>();
+ }
+ if (acr!=null) {
+ acrs.add(acr);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Setter for the OpenID Connect Display value.
+ *
+ * @param oidcDisplay OpenID Connect Display value.
+ */
+ public void setDisplay(@Nullable final String oidcDisplay) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ if (null != display) {
+ try {
+ display = Display.parse(oidcDisplay);
+ } catch (final ParseException e) {
+ log.error("{} Could not set display value",getLogPrefix(), e);
+ }
+ }
+
+ }
+
+ /**
+ * Set the lookup strategy for the {@link AttributeContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAttributeContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ attributeContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
+
+ }
+
+ /**
+ * Returns the first {@link StringAttributeValue} value of the {@link IdPAttribute} that has key
+ * <code>name</code> from the {@link OpenIDConnectContext#getResolvedIdPAttributes()}.
+ *
+ * @param oidcCtx context to get attributes from.
+ * @param name of the attribute to find.
+ * @return attribute value if found, null otherwise.
+ */
+ @Nullable private String attributeToString(@Nonnull final OpenIDConnectContext oidcCtx,
+ @Nullable final String name) {
+
+ if (oidcCtx.getResolvedIdPAttributes() == null) {
+ log.warn("Attribute context not available");
+ return null;
+ }
+ final IdPAttribute attribute = oidcCtx.getResolvedIdPAttributes().get(name);
+ if (attribute == null || attribute.getValues().size() == 0) {
+ log.debug("attribute " + name + " not found or has no values");
+ return null;
+ }
+ for (final IdPAttributeValue attrValue : attribute.getValues()) {
+ if (attrValue instanceof StringAttributeValue) {
+ // We set the value
+ return attrValue.getDisplayValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Build the requested claims into an JSONObject, used by
+ * {@link #getRequestObject(OpenIDConnectContext, State)}.
+ *
+ * @param oidcCtx context to extract request claims from.
+ * @return id token as a JSON Object.
+ */
+ @Nonnull private JSONObject buildIDToken(@Nonnull final OpenIDConnectContext oidcCtx) {
+
+ final JSONObject idToken = new JSONObject();
+ for (final Map.Entry<String, String> entry : requestClaims.entrySet()) {
+ final String value = entry.getValue();
+ final String claim = entry.getKey();
+ if (value == null) {
+ // 1. null value
+ log.debug("Setting claim " + claim + " to null");
+ idToken.put(entry.getKey(), value);
+ continue;
+ }
+ log.debug("locating attribute for " + value);
+ final String attrValue = attributeToString(oidcCtx, value);
+ if (attrValue != null) {
+ // 2. attribute value
+ log.debug("Setting claim " + claim + " to value " + attrValue);
+ idToken.put(claim, attrValue);
+ continue;
+ }
+ if ("essential".equals(value)) {
+ // 3. essential value
+ final JSONObject obj = new JSONObject();
+ obj.put("essential", true);
+ log.debug("Setting claim " + claim + " to value " + obj.toJSONString());
+ idToken.put(claim, obj);
+ continue;
+ }
+ // 4. string value
+ log.debug("Setting claim " + claim + " to value " + value);
+ idToken.put(claim, value);
+ }
+
+ return idToken;
+ }
+
+ /**
+ *
+ * Construct a JWT claims Request Object (see OpenID Connect core 1.0 section 6) iff claims
+ * are requested i.e. <code>requestClaims</code> is not <code>null</code>.
+ *
+ * <p>If the signing key is present, adds also state, iat claims and then signs it.</p>
+ *
+ * <p>Must be called as a last step before constructing the request.</p>
+ *
+ *
+ * @param oidcCtx context for accessing attributes.
+ * @param state to be added to the request object.
+ * @return the request object, or null if one is not constructed.
+ *
+ * @throws Exception if attribute context is not available or parsing/signing fails.
+ */
+ @Nullable private JWT getRequestObject(@Nonnull final OpenIDConnectContext oidcCtx, @Nonnull final State state)
+ throws Exception {
+
+
+ if (requestClaims == null || requestClaims.size() == 0) {
+ return null;
+ }
+ final JSONObject request = new JSONObject();
+ request.put("client_id", clientID.getValue());
+ request.put("response_type", responseType.toString());
+ if (signPrvKey != null) {
+ request.put("iss", clientID.getValue());
+ request.put("aud", oIDCProviderMetadata.getIssuer().getValue());
+ // If we sign we add also iat and state.
+ request.put("state", state.getValue());
+ request.put("iat", TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
+ }
+ // Build the id token as instructed.
+ final JSONObject idToken = buildIDToken(oidcCtx);
+ final JSONObject claims = new JSONObject();
+ claims.put("id_token", idToken);
+ request.put("claims", claims);
+ log.debug("Request object without signature "+getLogPrefix() + request.toJSONString());
+ final JWTClaimsSet claimsRequest = JWTClaimsSet.parse(request);
+ JWT requestObject = null;
+ if (signPrvKey != null) {
+ requestObject = new SignedJWT(new JWSHeader.Builder(jwsAlgorithm).keyID(keyID).build(), claimsRequest);
+ ((SignedJWT) requestObject).sign(new RSASSASigner(signPrvKey));
+ log.debug("created request object: " + requestObject.getParsedString());
+ } else {
+ requestObject = new PlainJWT(new PlainHeader(), claimsRequest);
+ }
+ return requestObject;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class, true);
+
+ // Initialize the context, if request is passive we override default prompt value
+ final Prompt ovrPrompt = authenticationContext.isPassive() ? new Prompt(Type.NONE) : prompt;
+ oidcCtx.setPrompt(ovrPrompt);
+ oidcCtx.setAcrs(acrs);
+ oidcCtx.setClientID(clientID);
+ oidcCtx.setClientSecret(clientSecret);
+ oidcCtx.setDisplay(display);
+ oidcCtx.setoIDCProviderMetadata(oIDCProviderMetadata);
+ oidcCtx.setRedirectURI(redirectURI);
+ final State state = new State();
+ oidcCtx.setState(state);
+ final Nonce nonce = new Nonce();
+ oidcCtx.setNonce(nonce);
+
+ JWT requestObject = null;
+ try {
+ // must be called as a last step
+ requestObject = getRequestObject(oidcCtx, state);
+ } catch (final Exception e) {
+ // TODO: better error id
+ log.error("{} unable to create request object", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ return;
+ }
+ if (authenticationContext.isForceAuthn()) {
+ // We set max age to 0 if forcedauth is set
+ // TODO: Currently the underlying library doesn't accept value 0, so
+ // we set it to 1
+ final int maxAge = 1;
+ oidcCtx.setAuthenticationRequestURI(
+ new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
+ .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
+ .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
+ .maxAge(maxAge).prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
+ } else {
+ oidcCtx.setAuthenticationRequestURI(
+ new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
+ .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
+ .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
+ .prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
+ }
+
+ return;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
new file mode 100644
index 0000000..f64e36e
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * An action that verifies the Authentication Context Class Reference (ACR) values contained within the
+ * id_token matches those requested.
+ *
+ * <p>ACR claims are optional</p>
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ *
+ * @since 4.0.0
+ */
+//TODO P.S should some of these functions be delegated to Nimbus IDTokenValidator?
+public class ValidateIDTokenACR extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenACR.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find OIDC context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ return;
+ }
+
+
+ final List<ACR> acrs = oidcCtx.getAcrs();
+ if (acrs != null && !acrs.isEmpty()) {
+ if (log.isTraceEnabled()) {
+ for (int i = 0; i < acrs.size(); i++) {
+ log.trace("{} ACR index {} is {}", getLogPrefix(), i, acrs.get(i));
+ }
+ }
+ final String acr;
+ try {
+ //TODO P.S. this could be null.
+ acr = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("acr");
+ } catch (final ParseException e) {
+ log.error("{} Error parsing id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (StringSupport.trimOrNull(acr) == null) {
+ log.error("{} acr requested but not received", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (!acrs.contains(new ACR(acr))) {
+ log.error("{} acr received does not match requested:" + acr, getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ }
+
+ return;
+ }
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAudience.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAudience.java
new file mode 100644
index 0000000..91cb085
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAudience.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An action that verifies the Audience (aud) claim in the id_token contains the client_id of this
+ * client (as registered at the issuer). See section 3.1.3.7 of OpenID Connect core 1.0.
+ *
+ * <p>If one of the list of audiences is untrusted, the token should be rejected - which is
+ * Currently NOT tested</p>
+ *
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIDConnectContext.getIDToken() != null</pre>
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
+ *
+ * @since 4.0.0
+ */
+//TODO P.S should some of these functions be delegated to Nimbus IDTokenValidator?
+public class ValidateIDTokenAudience extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenAudience.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Not able to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (oidcCtx.getIDToken() == null) {
+ log.error("{} Not able to find id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ try {
+ //TODO P.S. getClientID could be null, although not after SetOIDCInformation has run.
+ if (!oidcCtx.getIDToken().getJWTClaimsSet().getAudience().contains(oidcCtx.getClientID().getValue())) {
+ log.error("{} client is not the intended audience", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ } catch (final ParseException e) {
+ log.error("{} Error parsing id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ return;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthenticationTime.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthenticationTime.java
new file mode 100644
index 0000000..260e694
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthenticationTime.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.joda.time.DateTime;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that checks if auth_time (when the End-User authentication took place) stored within the id_token
+ * is within a valid expiration window. That is:
+ * <ul>
+ * <li>The auth_time when the End-User authenticated should not be in the future. More specifically, now plus
+ * some clock skew (default is 3 minutes)</li> *
+ * <li>The authentication and hence id_token has not expired. More specifically, the expiration time
+ * (the auth_time plus some clock skew plus the authnLifetime) should be after the current time.<li>
+ * </ul>
+ *
+ * <p>The auth_time is optional, and needs to be requested by a claim, or using the max_age parameter</p>
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIDConnectContext.getIDToken() != null</pre>
+ * @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ *
+ * @since 4.0.0
+ */
+//TODO P.S should some of these functions be delegated to Nimbus IDTokenValidator?
+public class ValidateIDTokenAuthenticationTime extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenAuthenticationTime.class);
+
+ /**
+ * Clock skew - milliseconds before a lower time bound, or after an upper time bound, to consider still acceptable
+ * Default value: 3 minutes.
+ */
+ //TODO change these to durations or the JWT verifier
+ @NonNegative private long clockSkew;
+
+ /**
+ * Amount of time in milliseconds for which a forced authentication is valid after it is issued. Default value: 30
+ * seconds.
+ */
+ //TODO P.S is this not just messagelifetime.
+ @NonNegative private long authnLifetime;
+
+ /**
+ * Constructor.
+ */
+ public ValidateIDTokenAuthenticationTime() {
+ super();
+ setClockSkew(60 * 3 * 1000);
+ setAuthnLifetime(30 * 1000);
+ }
+
+ /**
+ * Get the clock skew.
+ *
+ * @return the clock skew
+ */
+ @NonNegative
+ public long getClockSkew() {
+ return clockSkew;
+ }
+
+ /**
+ * Set the clock skew.
+ *
+ * @param skew clock skew to set
+ */
+ public void setClockSkew( @NonNegative final long skew) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ clockSkew = Constraint.isGreaterThanOrEqual(0, skew, "Clock skew must be greater than or equal to 0");
+ }
+
+ /**
+ * Gets the amount of time, in milliseconds, for which a forced authentication is valid.
+ *
+ * @return amount of time, in milliseconds, for which a forced authentication is valid
+ */
+ @NonNegative
+ public long getAuthnLifetime() {
+ return authnLifetime;
+ }
+
+ /**
+ * Sets the amount of time, in milliseconds, for which a forced authentication is valid.
+ *
+ * @param lifetime amount of time, in milliseconds, for which a forced authentication is valid
+ */
+ public synchronized void setAuthnLifetime( @NonNegative final long lifetime) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ authnLifetime =
+ Constraint.isGreaterThanOrEqual(0, lifetime, "Authn lifetime must be greater than or equal to 0");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ if (!authenticationContext.isForceAuthn()) {
+
+ return;
+ }
+ // If we have forced authentication, we will check for authentication age
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Not able to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ return;
+ }
+ final Date authTimeDate;
+ try {
+ authTimeDate = oidcCtx.getIDToken().getJWTClaimsSet().getDateClaim("auth_time");
+ } catch (final ParseException e) {
+ log.error("{} Error parsing id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (authTimeDate == null) {
+ // log.error("{} max age set but no auth_time received", getLogPrefix());
+ //ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ //FIXME WE ARE NOT CHECKING IF WE SET MAX_AGE HERE!!?
+ return;
+ }
+ final DateTime authTime = new DateTime(authTimeDate);
+ final DateTime now = new DateTime();
+ final DateTime latestValid = now.plus(getClockSkew());
+ final DateTime expiration = authTime.plus(getClockSkew() + getAuthnLifetime());
+
+ // Check authentication wasn't performed in the future
+ if (authTime.isAfter(latestValid)) {
+ log.warn("{} Authentication time is not yet valid: time was {}, latest valid is: {}", getLogPrefix(),
+ authTime, latestValid);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ // Check authentication time has not expired
+ if (expiration.isBefore(now)) {
+ log.warn("{} Authentication time has expired: time was '{}', expired at: '{}', current time: '{}'",
+ getLogPrefix(), authTime, expiration, now);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
new file mode 100644
index 0000000..1f2cbb3
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that verifies the Authorized Party (azp) of an id_token. That is, the party to which
+ * the id_token was issued, or the presenter of the id_token. As the IdP will always be responsible
+ * for presenting the id_token, this value should always be the same client_id as the sole audience (aud).
+ * As such, azp is not strictly required, but is checked if present.
+ *
+ * <p>The authorized party is one or more case sensitive Strings or URIs.</p>
+ *
+ * <p>See section 3.1.3.7 of the OpenID Connect core 1.0</p>
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIDConnectContext.getIDToken() != null</pre>
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ *
+ * @since 4.0.0
+ */
+//Issues of semantics exist here e.g. https://bitbucket.org/openid/connect/issues/973/
+//TODO the code does not match the spec?
+public class ValidateIDTokenAuthorizedParty extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenAuthorizedParty.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ try {
+ if (oidcCtx.getIDToken().getJWTClaimsSet().getAudience().size() > 1) {
+ final String azp = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("azp");
+ if (!oidcCtx.getClientID().getValue().equals(azp)) {
+ log.error("{} multiple audiences, client is not the azp", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ }
+ } catch (final ParseException e) {
+ log.error("{} Error parsing id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ return;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenExpirationTime.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenExpirationTime.java
new file mode 100644
index 0000000..b5c69f6
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenExpirationTime.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that verifies the expiration time (exp) of an id_token. If the expiration time has past
+ * the id_token must not be accepted.
+ *
+ * <p>Some clock skew could be specified, but is not in this implementation</p>
+ *
+ * <p>Expiration time of the id_token is required. If not present, AuthnEventIds#NO_CREDENTIALS is returned</p>
+ *
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIdConnectContext.getOidcTokenResponse() != null</pre>
+ *
+ * @since 4.0.0
+ */
+public class ValidateIDTokenExpirationTime extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenExpirationTime.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ // The current time MUST be before the time represented by the expiration time of the token
+ final Date currentDate = new Date();
+ try {
+ final Date expDate = oidcCtx.getIDToken().getJWTClaimsSet().getExpirationTime();
+ if (currentDate.after(expDate)) {
+ log.error("{} Current date {} is past exp date {}", getLogPrefix(), currentDate, expDate);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ } catch (final java.text.ParseException | NullPointerException e) {
+ log.error("{} Error parsing id token", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ return;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenIssuer.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenIssuer.java
new file mode 100644
index 0000000..1f21a4b
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenIssuer.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that verifies the issuer (iss) of the id_token exactly matches that of the configured
+ * OpenID Connect Provider.
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIdConnectContext.getOidcTokenResponse() != null</pre>
+ * @pre <pre>OpenIdConnectContext.getoIDCProviderMetadata() != null</pre>
+ *
+ * @since 4.0.0
+ */
+public class ValidateIDTokenIssuer extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenIssuer.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ final String issuer = oidcCtx.getoIDCProviderMetadata().getIssuer().getValue();
+
+ try {
+ if (!issuer.equals(oidcCtx.getIDToken().getJWTClaimsSet().getIssuer())) {
+ log.error("{} Issuer mismatch", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ } catch (final ParseException e) {
+ log.error("{} Unable to parse oidc token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ return;
+ }
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java
new file mode 100644
index 0000000..8e1b26b
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenSignature.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.shaded.json.JSONArray;
+import com.nimbusds.jose.shaded.json.JSONObject;
+import com.nimbusds.jose.util.JSONObjectUtils;
+import com.nimbusds.jwt.SignedJWT;
+
+/**
+ * An action that verifies the signature of a JWS id_token using the RSA key belonging to the keyID
+ * found in the JOSE Header.
+ *
+ * <p>The current implementation *requires* the id_token is signed, and *requires* an RSA key type</p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIdConnectContext.getoIDCProviderMetadata() != null</pre>
+ *
+ * @since 4.0.0
+ */
+public class ValidateIDTokenSignature extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenSignature.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ SignedJWT signedJWT = null;
+ try {
+ //TODO P.S. oidcCtx.getIDToken() could be null.
+ signedJWT = SignedJWT.parse(oidcCtx.getIDToken().serialize());
+ } catch (final ParseException e) {
+ log.error("{} Error when parsing signed JWT", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ RSAPublicKey providerKey = null;
+ try {
+ final JSONObject key = getProviderRSAJWK(oidcCtx.getoIDCProviderMetadata().getJWKSetURI()
+ .toURL().openStream(),signedJWT.getHeader().getKeyID());
+ if (key == null) {
+ log.error("{} Unable to find key to verify signature", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ providerKey = RSAKey.parse(key).toRSAPublicKey();
+ } catch (final IOException | java.text.ParseException | JOSEException e) {
+ log.error("{} Error when parsing key to verify signature", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ final RSASSAVerifier verifier = new RSASSAVerifier(providerKey);
+ try {
+ if (!signedJWT.verify(verifier)) {
+ log.error("{} JWT signature verification failed", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ } catch (final JOSEException e) {
+ log.error("{} JWT signature verification not performed", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ log.debug("{} ID Token signature verified",getLogPrefix());
+ return;
+ }
+
+ /**
+ * Parse JWK and RSA public key from the input stream for signature verification.
+ *
+ * @param is inputstream containing the JWK
+ * @param kid The key ID to be looked up
+ * @return RSA public key as a JSON Object. <code>Null</code> if there is no key
+ * @throws ParseException if parsing fails.
+ * @throws IOException if something unexpected happens.
+ */
+ //TODO could be cached? (or use Nimbus to do this validation as it does cache it).
+ @Nullable
+ private JSONObject getProviderRSAJWK(@Nonnull final InputStream is, @Nullable final String kid)
+ throws ParseException, IOException {
+
+ if (kid == null) {
+ log.warn("No kid defined in the JWT, no signning key can be returned");
+ }
+
+ final StringWriter writer = new StringWriter();
+ CharStreams.copy(new InputStreamReader(is, Charsets.UTF_8), writer);
+
+ final Map<String,Object> json = JSONObjectUtils.parse(writer.toString());
+ final JSONArray keyList = (JSONArray) json.get("keys");
+ if (keyList == null) {
+ return null;
+ }
+ for (final Object key : keyList) {
+ final JSONObject k = (JSONObject) key;
+ if ("sig".equals(k.get("use")) && "RSA".equals(k.get("kty"))) {
+ if (kid == null || kid.equals(k.get("kid"))) {
+ return k;
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
new file mode 100644
index 0000000..d0eac70
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.security.auth.Subject;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.authn.AbstractValidationAction;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
+import net.shibboleth.idp.authn.principal.UsernamePrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+/**
+ * An action that builds an {@link AuthenticationResult} from the subject (sub claim) of the OpenID Connect token.
+ *
+ * <p>Also builds any OpenID Connect Standard claims as {@link IdPAttributePrincipal}s, and sets them
+ * alongside the subject principal for use during attribute resolution.</p>
+ *
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @post If AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false).getIDToken()
+ * .getJWTClaimsSet().getSubject()!= null, then an {@link net.shibboleth.idp.authn.AuthenticationResult}
+ * is saved to the {@link AuthenticationContext}.
+ *
+ * @since 4.0.0
+ */
+ at SuppressWarnings({"rawtypes", "unchecked"})
+public class ValidateOIDCAuthentication extends AbstractValidationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthentication.class);
+
+ /** Avoid creating multiple principals. */
+ private boolean avoidMultiplePrincipal;
+
+ /** the subject received from id token. */
+ @Nullable private String oidcSubject;
+
+ /** The JWT Claim Set of the ID Token acquired from the token endpoint. */
+ @Nullable private JWTClaimsSet jwtClaims;
+
+ /**
+ * In MFA use case prior authentication may have created a usernameprincipal already with value not matching to MFA.
+ *
+ * @param avoid true if additional principals should be avoided.
+ */
+ public void setAvoidMultiplePrincipal(final boolean avoid) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ avoidMultiplePrincipal = avoid;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+ log.trace("{}: Prerequisities fulfilled to start doPreExecute", getLogPrefix());
+
+ final OpenIDConnectContext oidcCtx = authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.error("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return false;
+ }
+
+ if (oidcCtx.getIDToken() == null) {
+ log.error("{} No ID Token in response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return false;
+ }
+ try {
+ //TODO P.S. this will fail if not decrypted JWE.
+ oidcSubject = StringSupport.trimOrNull(oidcCtx.getIDToken().getJWTClaimsSet().getSubject());
+ jwtClaims = oidcCtx.getIDToken().getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.error("{} unable to parse ID Token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return false;
+ }
+
+ if (oidcSubject == null) {
+ log.error("{} Subject is null in ID Token response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ buildAuthenticationResult(profileRequestContext, authenticationContext);
+ return;
+ }
+
+ /**
+ * For each OIDC Standard Claim (OpenID Connect Core 1.0 section 5.1) in the JWT Claim set {@code jwtClaimSet},
+ * build a {@link IdPAttributePrincipal} using a {@link StringAttributeValue}.
+ * <p>
+ * Any claim that is not understood (not in the standard claim set) *will* be ignored.
+ * <p>
+ * All claims are strings except {@code email_verified} and {code phone_verified) (both booleans), {@code address}
+ * (JSON Object), and {@code updated_at} (number). Both booleans are represented as string attribute values, the
+ * {@code address} and {@code updated_at} claims are currently ignored.
+ *
+ *
+ * @return a list of standard OIDC claims as {@link IdPAttributePrincipal}s.
+ */
+ @Nonnull
+ @Live
+ private List<IdPAttributePrincipal> buildIdPAttributePrincipalsFromStandardClaims() {
+
+ final List<IdPAttributePrincipal> claimPrincipals = new ArrayList<>();
+ if (jwtClaims != null) {
+ // jwtClaims.getClaims() is never null
+ for (final Map.Entry<String, Object> claim : jwtClaims.getClaims().entrySet()) {
+
+ if (UserInfo.getStandardClaimNames().contains(claim.getKey())) {
+
+ String claimValue = null;
+ if (claim.getValue() instanceof String) {
+ claimValue = StringSupport.trimOrNull((String) claim.getValue());
+ }
+
+ if (claim.getValue() instanceof Boolean) {
+ claimValue = StringSupport.trimOrNull(Boolean.toString((Boolean) claim.getValue()));
+ }
+ if (claimValue == null) {
+ log.trace("{} JWT Claim [{}] is not of a supported type or is null/empty, ignored",
+ getLogPrefix(), claim);
+ continue;
+ }
+
+ final IdPAttribute idpAttr = new IdPAttribute(claim.getKey());
+ idpAttr.setValues(Collections.singletonList(new StringAttributeValue(claimValue)));
+
+ final IdPAttributePrincipal attrPrincipal = new IdPAttributePrincipal(idpAttr);
+ log.trace("{} Constructed IdPAttributePrincipal from OIDC claim [{}]", getLogPrefix(),
+ attrPrincipal);
+ claimPrincipals.add(attrPrincipal);
+ }
+
+ }
+ }
+
+ return claimPrincipals;
+
+ }
+
+ @Override
+ protected Subject populateSubject(@Nonnull final Subject subject) {
+
+ if (avoidMultiplePrincipal && subject.getPrincipals().size() > 0) {
+ log.debug("{} Subject already contains principal, not populated", getLogPrefix());
+
+ } else {
+ subject.getPrincipals().add(new UsernamePrincipal(oidcSubject));
+ subject.getPrincipals().addAll(buildIdPAttributePrincipalsFromStandardClaims());
+
+ }
+ return subject;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
new file mode 100644
index 0000000..01c9603
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.AbstractExtractionAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+/**
+ * An action that extracts the id_token from the query parameters of a successful response from
+ * an OpenID Connect provider. The id_token is attached to the {@link OpenIDConnectContext}.
+ *
+ * <p>Also checks the state returned by the provider matches that which was sent to the provider</p>
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
+ * @pre <pre>OpenIdConnectContext.getAuthenticationResponseURI() != null</pre>
+ * @post If {@link AuthenticationResponse#indicatesSuccess()} == true, the extracted id_token
+ * is extracted and placed into the existing {@link OpenIDConnectContext}.
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ *
+ * @since 4.0.0
+ */
+public class ValidateOIDCAuthenticationResponse extends AbstractExtractionAction {
+
+ /*
+ * A DRAFT PROTO CLASS!! NOT TO BE USED YET.
+ *
+ * FINAL GOAL IS TO MOVE FROM CURRENT OIDC TO MORE WEBFLOW LIKE IMPLEMENTATION.
+ */
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthenticationResponse.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final OpenIDConnectContext oidcCtx =
+ authenticationContext.getSubcontext(OpenIDConnectContext.class);
+ if (oidcCtx == null) {
+ log.info("{} Unable to find oidc context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ if (oidcCtx.getAuthenticationResponseURI() == null) {
+ log.info("{} Response URI not set", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ log.debug("Validating OIDC response {}", oidcCtx.getAuthenticationResponseURI().toString());
+ AuthenticationResponse response = null;
+ try {
+ response = AuthenticationResponseParser.parse(oidcCtx.getAuthenticationResponseURI());
+ } catch (final ParseException e) {
+ log.info("{} Response parsing failed", getLogPrefix(),e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (!response.indicatesSuccess()) {
+
+ final AuthenticationErrorResponse errorResponse = (AuthenticationErrorResponse) response;
+ String error = errorResponse.getErrorObject().getCode();
+ final String errorDescription = errorResponse.getErrorObject().getDescription();
+ if (StringSupport.trimOrNull(errorDescription) != null) {
+ error += " : " + errorDescription;
+ }
+
+ log.info("{} response indicated error: {}", getLogPrefix(), error);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ final AuthenticationSuccessResponse successResponse = (AuthenticationSuccessResponse) response;
+ // implicit and hybrid flows return id token in response.
+ oidcCtx.setIDToken(successResponse.getIDToken());
+ final State state = oidcCtx.getState();
+ if (state == null || !state.equals(successResponse.getState())) {
+ log.info("{} OIDC flow state mismatch:", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+
+ }
+
+ oidcCtx.setAuthenticationSuccessResponse(successResponse);
+
+ return;
+ }
+
+}
diff --git a/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/package-info.java b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/package-info.java
new file mode 100644
index 0000000..9cacce6
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This package has an authentication implementation for OpenID Connect authentication providers.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
new file mode 100644
index 0000000..6bf6aee
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <!--
+ System beans needed for extension to function, loaded after global.xml.
+ The default template shows an incomplete example authentication flow descriptor which can be
+ removed if not needed
+ -->
+
+ <bean id="authn/OIDCRelyingParty" parent="shibboleth.AuthenticationFlow"
+ p:order="%{idp.authn.OIDC.RP.order:1000}"
+ p:nonBrowserSupported="%{idp.authn.OIDC.RP.nonBrowserSupported:true}"
+ p:passiveAuthenticationSupported="%{idp.authn.OIDC.RP.passiveAuthenticationSupported:true}"
+ p:forcedAuthenticationSupported="%{idp.authn.OIDC.RP.forcedAuthenticationSupported:true}"
+ p:proxyRestrictionsEnforced="%{idp.authn.OIDC.RP.proxyRestrictionsEnforced:%{idp.authn.enforceProxyRestrictions:true}}"
+ p:proxyScopingEnforced="%{idp.authn.OIDC.RP.proxyScopingEnforced:false}"
+ p:discoveryRequired="%{idp.authn.OIDC.RP.discoveryRequired:false}"
+ p:lifetime="%{idp.authn.OIDC.RP.lifetime:%{idp.authn.defaultLifetime:PT1H}}"
+ p:inactivityTimeout="%{idp.authn.OIDC.RP.inactivityTimeout:%{idp.authn.defaultTimeout:PT30M}}"
+ p:reuseCondition-ref="#{'%{idp.authn.OIDC.RP.reuseCondition:shibboleth.Conditions.TRUE}'.trim()}"
+ p:activationCondition-ref="#{'%{idp.authn.OIDC.RP.activationCondition:shibboleth.Conditions.TRUE}'.trim()}">
+ <property name="supportedPrincipals">
+ <list>
+ <bean parent="shibboleth.SAML2AuthnContextClassRef"
+ c:classRef="class-ref" />
+ <bean parent="shibboleth.SAML1AuthenticationMethod"
+ c:method="auth-ref" />
+ </list>
+ </property>
+ <property name="supportedPrincipalsByString">
+ <bean parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.authn.OIDC.RP.supportedPrincipals:}'.trim()}" />
+ </property>
+ </bean>
+
+ <!-- Controller implementation -->
+ <bean id="shibboleth.OpenIDConnectStartServlet"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController" />
+
+</beans>
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-beans.xml b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-beans.xml
new file mode 100644
index 0000000..cb9040b
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-beans.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />
+ <bean class="net.shibboleth.idp.profile.impl.ProfileActionBeanPostProcessor" />
+
+ <!-- Spring controller to start the authentication request and recieve the response -->
+ <bean id="shibboleth.authn.OpenIDConnect.externalAuthnPath" class="java.lang.String"
+ c:_0="servletRelative:/Authn/OIDC/RP/auth">
+ </bean>
+
+ <!-- TODO Add user import here
+ <import resource="openidconnect-authn-config.xml" /> -->
+
+ <bean id="SetRPUIInformation" class="net.shibboleth.idp.ui.impl.SetRPUIInformation"
+ scope="prototype" p:activationCondition-ref="shibboleth.authn.oidc.rp.populateUIInfo"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest">
+ <property name="fallbackLanguages">
+ <bean parent="shibboleth.CommaDelimStringArray" c:_0="%{idp.ui.fallbackLanguages:}" />
+ </property>
+ </bean>
+
+ <!-- Populate RP UI info from metadata? -->
+ <util:constant id="shibboleth.authn.oidc.rp.populateUIInfo" static-field="java.lang.Boolean.FALSE" />
+
+ <bean id="SetOIDCInformation" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.SetOIDCInformation"
+ p:redirectURI="%{idp.oidc.rp.redirectURI:}"
+ p:clientID="%{idp.oidc.rp.clientID:null}"
+ p:clientSecret="%{idp.oidc.rp.clientSecret:null}"
+ p:providerMetadataLocation="%{idp.oidc.rp.providerConfigurationDocument:null}"
+ p:scope="%{idp.oidc.rp.scope:null}">
+
+ <!-- <property name="responseType" value="id_token token"/> -->
+ </bean>
+
+ <bean id="ValidateOIDCAuthenticationResponse"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthenticationResponse"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+
+ <bean id="ValidateIDTokenACR" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenACR" />
+
+ <bean id="ValidateIDTokenAudience" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenAudience" />
+
+ <bean id="ValidateIDTokenAuthenticationTime"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenAuthenticationTime" />
+
+ <bean id="ValidateIDTokenAuthorizedParty"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenAuthorizedParty" />
+
+ <bean id="ValidateIDTokenExpirationTime"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenExpirationTime" />
+
+ <bean id="ValidateIDTokenIssuer" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenIssuer" />
+
+ <bean id="ValidateIDTokenSignature"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenSignature" />
+
+ <bean id="ValidateOIDCAuthentication" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype" />
+
+ <bean id="GetOIDCTokenResponse" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.GetOIDCTokenResponse"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+
+
+
+</beans>
diff --git a/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-flow.xml b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-flow.xml
new file mode 100644
index 0000000..eaa7e9b
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-flow.xml
@@ -0,0 +1,60 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="authn.abstract, authn/conditions">
+
+ <action-state id="SetRPUIInformation">
+ <evaluate expression="SetRPUIInformation" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="SetOIDCInformation" />
+ </action-state>
+
+ <action-state id="SetOIDCInformation">
+ <evaluate expression="SetOIDCInformation" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="ExternalTransfer" />
+ </action-state>
+
+ <view-state id="ExternalTransfer"
+ view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.OpenIDConnect.externalAuthnPath'), flowExecutionContext.getKey().toString())}">
+ <attribute name="csrf_excluded" value="true" type="boolean" />
+ <on-render>
+ <evaluate
+ expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
+ </on-render>
+ <transition to="ValidateResponse" />
+ </view-state>
+
+ <action-state id="ValidateResponse">
+ <evaluate expression="ValidateOIDCAuthenticationResponse" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="GetToken" />
+ </action-state>
+
+ <action-state id="GetToken">
+ <evaluate expression="GetOIDCTokenResponse" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="ValidateOIDCTokenResponse" />
+ </action-state>
+
+ <action-state id="ValidateOIDCTokenResponse">
+ <evaluate expression="ValidateIDTokenACR" />
+ <evaluate expression="ValidateIDTokenAudience" />
+ <evaluate expression="ValidateIDTokenAuthorizedParty" />
+ <evaluate expression="ValidateIDTokenExpirationTime" />
+ <evaluate expression="ValidateIDTokenIssuer" />
+ <evaluate expression="ValidateIDTokenSignature" />
+ <evaluate expression="ValidateIDTokenAuthenticationTime" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="SetPrincipal" />
+ </action-state>
+
+
+ <action-state id="SetPrincipal">
+ <evaluate expression="ValidateOIDCAuthentication" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="proceed" />
+ </action-state>
+
+ <bean-import resource="oidc-relying-party-beans.xml" />
+
+</flow>
diff --git a/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule b/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule
new file mode 100644
index 0000000..5e849fd
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.module.IdPModule
@@ -0,0 +1 @@
+net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPModule
diff --git a/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin b/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin
new file mode 100644
index 0000000..bae01fe
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/META-INF/services/net.shibboleth.idp.plugin.IdPPlugin
@@ -0,0 +1,2 @@
+net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPPlugin
+
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
new file mode 100644
index 0000000..7e1b3ce
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp.properties
@@ -0,0 +1,7 @@
+idp.oidc.rp.clientID=client_id
+idp.oidc.rp.clientSecret=secret
+##does not need the .well-known/openid-configuration path.
+idp.oidc.rp.providerConfigurationDocument=https://hostname
+idp.oidc.rp.redirectURI=https://localhost:8443/idp/profile/Authn/OIDC/RP/callback
+## openid is defaulted. Other scopes could be; profile etc.
+#idp.oidc.rp.scope=openid
\ No newline at end of file
diff --git a/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/module.properties b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/module.properties
new file mode 100644
index 0000000..1926077
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/module.properties
@@ -0,0 +1,14 @@
+# Example Properties defining an authentication module.
+
+# Class to Module ID mappings
+net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPModule = idp.authn.java-idp-oidc-rp
+
+# Module Owner
+idp.authn.java-idp-oidc-rp.plugin = net.shibboleth.idp.plugin.authn.java-idp-oidc-rp
+
+idp.authn.java-idp-oidc-rp.name = java-idp-oidc-rp Authentication
+idp.authn.java-idp-oidc-rp.desc = Login flow for java-idp-oidc-rp
+idp.authn.java-idp-oidc-rp.url = /java-idp-oidc-rpConfiguration
+idp.authn.java-idp-oidc-rp.1.src = somefile
+idp.authn.java-idp-oidc-rp.1.dest = conf/to-somefile
+idp.authn.java-idp-oidc-rp.1.replace = true
diff --git a/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/plugin.properties b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/plugin.properties
new file mode 100644
index 0000000..c4407d2
--- /dev/null
+++ b/java-idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/plugin.properties
@@ -0,0 +1,9 @@
+# Example properties defining this plugin
+
+plugin.id = net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPPlugin
+# Only used when package manifest is not available
+plugin.version = 0.0.1
+#plugin.license = licence.txt
+
+# No prereqs
+#plugin.modules.required =
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a889736
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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</groupId>
+ <artifactId>parent</artifactId>
+ <version>11.2.0</version>
+ </parent>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <name>Shibboleth IdP :: Plugins :: OIDC RP Login Flow</name>
+ <packaging>pom</packaging>
+ <description>Java OIDC RP authentication plugin for the Shibboleth IdP.</description>
+
+ <properties>
+ <idp.groupId>net.shibboleth.idp</idp.groupId>
+ <idp.version>4.1.0</idp.version>
+ <opensaml.groupId>org.opensaml</opensaml.groupId>
+ <opensaml.version>4.1.0</opensaml.version>
+ <oidc.common.version>1.1.0-SNAPSHOT</oidc.common.version>
+ <checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
+ </properties>
+
+ <distributionManagement>
+ <site>
+ <id>site</id>
+ <url>dav:...</url>
+ </site>
+ </distributionManagement>
+
+ <repositories>
+ <repository>
+ <id>shib-release</id>
+ <url>https://build.shibboleth.net/nexus/content/groups/public</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>shib-snapshot</id>
+ <url>https://build.shibboleth.net/nexus/content/repositories/snapshots</url>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ </repository>
+ </repositories>
+
+ <modules>
+ <module>java-idp-oidc-rp-impl</module>
+ <module>java-idp-oidc-rp-api</module>
+ <!-- All DIST modules must come after the -api and -impl modules -->
+ <module>java-idp-oidc-rp-dist</module>
+ </modules>
+
+ <scm>
+ <connection>scm:git:...</connection>
+ <developerConnection>scm:git:...</developerConnection>
+ <url>...</url>
+ </scm>
+
+ <dependencies>
+ <!-- Project wide test Dependencies -->
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${slf4j.groupId}</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <dependencyManagement>
+ <!-- java-idp-oidc-rp project dependencies -->
+ <dependencies>
+ <dependency>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp.plugin.authn</groupId>
+ <artifactId>idp-plugin-oidc-rp-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- OIDC Common BOM when importing OIDC dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-bom</artifactId>
+ <version>${oidc.common.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <!-- Shibboleth IdP BOM for importing IdP dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-bom</artifactId>
+ <version>${idp.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <!-- Provided shibboleth support dependencies -->
+ <dependency>
+ <groupId>net.shibboleth.utilities</groupId>
+ <artifactId>java-support</artifactId>
+ <version>${java-support.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Test bom dependencies -->
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-tests-bom</artifactId>
+ <version>${idp.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <index>true</index>
+ <manifestEntries>
+ <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
+ <Sealed>true</Sealed>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list