[java-idp-plugin-oidc-rp] branch main updated: Integrate force-authn and scope into authn request
Phil Smart
philip.smart at jisc.ac.uk
Wed Jun 15 14:25:40 UTC 2022
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=c55aa6b7f2ba42873ae433051b797b2062c35d88
The following commit(s) were added to refs/heads/main by this push:
new c55aa6b Integrate force-authn and scope into authn request
c55aa6b is described below
commit c55aa6b7f2ba42873ae433051b797b2062c35d88
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jun 15 15:25:34 2022 +0100
Integrate force-authn and scope into authn request
---
.../rp/context/ResponseTypeAndModeContext.java | 33 +++++-
.../oidc/rp/impl/AddOIDCAuthenticationRequest.java | 40 ++++---
.../impl/PopulateResponseTypeAndModeContext.java | 128 +++++++++++++--------
.../idp/service/relying-party/postconfig.xml | 8 +-
4 files changed, 142 insertions(+), 67 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
index b7113a8..d919db8 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
@@ -15,9 +15,18 @@ public class ResponseTypeAndModeContext extends BaseContext {
/** The chosen response type.*/
@Nullable private ResponseType responseType;
- /** The chosen response mode.*/
+ /**
+ * The chosen response mode.
+ */
@Nullable private ResponseMode responseMode;
+ /**
+ * The default response mode for the given response_type. Can be used
+ * to determine if the response_mode should be included in an authentication
+ * request.
+ */
+ @Nullable private ResponseMode defaultResponseMode;
+
/**
* Set the response type to be used with the ongoing authentication request.
*
@@ -38,8 +47,10 @@ public class ResponseTypeAndModeContext extends BaseContext {
/**
* Set the response mode to be used with the ongoing authentication request.
+ * This can be {@literal null} if the default response_mode is
+ * to be used by the OP.
*
- * @param mode the response mode.
+ * @param mode the response mode. {@literal null} if default mode is to be used.
*/
public void setResponseMode(@Nullable final ResponseMode mode) {
responseMode = mode;
@@ -53,5 +64,23 @@ public class ResponseTypeAndModeContext extends BaseContext {
@Nullable public ResponseMode getResponseMode() {
return responseMode;
}
+
+ /**
+ * Set the default response_mode for the given response_type.
+ *
+ * @param mode the default response_mode for the given response_type
+ */
+ public void setDefaultResponseMode(@Nullable final ResponseMode mode) {
+ defaultResponseMode = mode;
+ }
+
+ /**
+ * Get the response_mode to use with this authentication request.
+ *
+ * @return the default response_mode
+ */
+ @Nullable public ResponseMode getDefaultResponseMode() {
+ return defaultResponseMode;
+ }
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java
index 078d132..787f302 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java
@@ -17,6 +17,7 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -33,10 +34,13 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.Prompt;
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.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
@@ -233,24 +237,32 @@ public class AddOIDCAuthenticationRequest extends AbstractAuthenticationAction {
new OIDCAuthenticationRequest(new ClientID(oauth2ClientContext.getClientId()));
request.setResponseType(responseTypeAndModeContext.getResponseType());
- //TODO spec says response mode not recommended if the default type for response_type. Check here?
- request.setResponseMode(responseTypeAndModeContext.getResponseMode());
+ // Only set the response_mode if not equal to the default for that response_type
+ if (!responseTypeAndModeContext.getDefaultResponseMode().equals(responseTypeAndModeContext.getResponseMode())){
+ request.setResponseMode(responseTypeAndModeContext.getResponseMode());
+ }
request.setEndpointURI(providerMetadata.getProviderInformation().getAuthorizationEndpointURI());
- // Add scopes
-// final List<String> scopes =
-// clientMetadata.getClientInformation().getMetadata().getScope().toStringList();
-// scopes.forEach(s -> request.getScope().add(s));
-
- //TODO use strategy with injectable secure random implementation?
+ final Set<String> scopes = profileConfiguration.getScopes(profileRequestContext);
+ if (scopes != null && !scopes.isEmpty()) {
+ scopes.forEach(s -> request.getScope().add(s));
+ }
+
request.setNonce(new Nonce(OIDCProxySupport.generateNonce(16)));
- //TODO if force-authn
-// try {
-// request.setPrompt(Prompt.parse("none"));
-// } catch (ParseException e) {
-// log.error("{} Unable to set prompt", e);
-// }
+ // ForceAuthn comes from configuration, which by default will take into account the
+ // AuthenticationContext parent's state (but may be overridden by deployer).
+ if (profileConfiguration.isForceAuthn(profileRequestContext)) {
+ log.debug("{} Setting prompt=login (ForceAuthn) for OIDC AuthnRequest", getLogPrefix());
+ try {
+ request.setPrompt(Prompt.parse(Prompt.Type.LOGIN.toString()));
+ } catch (final ParseException e) {
+ // This should never happen
+ log.error("{} Unable to honour force-authn, setting prompt to 'login' failed", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+ return;
+ }
+ }
log.debug("{} Built authorization request for endpoint '{}' for client '{}'",getLogPrefix(),
request.getEndpointURI(), oauth2ClientContext.getClientId());
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
index 27aed36..80f672d 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
@@ -138,71 +138,99 @@ public class PopulateResponseTypeAndModeContext extends AbstractProfileAction {
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- final String responseTypeFromProfile = profileConfiguration.getResponseType(profileRequestContext);
-
- if (responseTypeFromProfile == null) {
- log.error("{} Response_type was null, must specify a response_type", getLogPrefix());
+ final String responseTypeFromProfile = profileConfiguration.getResponseType(profileRequestContext);
+ final ResponseType responseType = parseResponseType(responseTypeFromProfile);
+ if (responseType == null){
+ log.error("{} Response_type '{}' is not supported", getLogPrefix(), responseTypeFromProfile);
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
return;
}
+
+ final String responseModeFromProfile = profileConfiguration.getResponseMode(profileRequestContext);
+ final ResponseMode responseModeOverride = parseResponseMode(responseModeFromProfile);
+
+ final ResponseMode compatibleMode = ResponseMode.resolve(null, responseType);
+ log.trace("{} Compatible response mode '{}' resolved from response type '{}'", getLogPrefix(), compatibleMode,
+ responseTypeFromProfile);
+ responseTypeAndModeContext.setDefaultResponseMode(compatibleMode);
+
+ if (responseModeOverride != null && !responseModeOverride.equals(compatibleMode)) {
+ log.debug("{} Response_mode override '{}' exists in the profile configuration and is different than the"
+ + " default mode '{}' for response_type '{}'",
+ getLogPrefix(), responseModeFromProfile, compatibleMode, responseType);
+
+ responseTypeAndModeContext.setResponseMode(responseModeOverride);
+
+ } else {
+ responseTypeAndModeContext.setResponseMode(compatibleMode);
+ }
- ResponseType responseType = null;
+ responseTypeAndModeContext.setResponseType(responseType);
+ log.debug("{} Response_type '{}' selected", getLogPrefix(), responseTypeAndModeContext.getResponseType());
+ log.debug("{} Response_mode '{}' selected", getLogPrefix(), responseTypeAndModeContext.getResponseMode());
+
+ }
+
+ /**
+ * Parse the response_type into a known {@link ResponseType}.
+ *
+ * @param responseTypeFromProfile the response_type as a string
+ *
+ * @return the parsed {@link ResponseType}, or {@literal null} if the input type is unknown
+ */
+ @Nullable private ResponseType parseResponseType(@Nullable final String responseTypeFromProfile) {
+
+ if (responseTypeFromProfile == null) {
+ return null;
+ }
+
if (responseTypeFromProfile.equals(ResponseType.CODE.toString())) {
- responseType = ResponseType.CODE;
+ return ResponseType.CODE;
} else if (responseTypeFromProfile.equals(ResponseType.CODE_IDTOKEN.toString())) {
- responseType = ResponseType.CODE_IDTOKEN;
+ return ResponseType.CODE_IDTOKEN;
} else if (responseTypeFromProfile.equals(ResponseType.CODE_IDTOKEN_TOKEN.toString())) {
- responseType = ResponseType.CODE_IDTOKEN_TOKEN;
+ return ResponseType.CODE_IDTOKEN_TOKEN;
} else if (responseTypeFromProfile.equals(ResponseType.CODE_TOKEN.toString())) {
- responseType = ResponseType.CODE_TOKEN;
+ return ResponseType.CODE_TOKEN;
} else if (responseTypeFromProfile.equals(ResponseType.IDTOKEN.toString())) {
- responseType = ResponseType.IDTOKEN;
+ return ResponseType.IDTOKEN;
} else if (responseTypeFromProfile.equals(ResponseType.IDTOKEN_TOKEN.toString())) {
- responseType = ResponseType.IDTOKEN_TOKEN;
+ return ResponseType.IDTOKEN_TOKEN;
} else {
- log.error("{} Response_type '{}' is not supported", getLogPrefix(), responseTypeFromProfile);
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
- return;
+ return null;
}
-
- final String responseModeFromProfile = profileConfiguration.getResponseMode(profileRequestContext);
-
- final ResponseMode compatibleMode = ResponseMode.resolve(null, responseType);
- log.trace("{} Compatible response mode '{}' resolved from response type '{}'", getLogPrefix(), compatibleMode,
- responseTypeFromProfile);
+ }
+
+ /**
+ * Parse the response_mode into a known {@link ResponseMode}.
+ *
+ * @param responseModeFromProfile the response_mode as a string
+ *
+ * @return the parsed {@link ResponseMode}, or {@literal null} if the input type is unknown
+ */
+ @Nullable private ResponseMode parseResponseMode(@Nullable final String responseModeFromProfile) {
- ResponseMode responseModeOverride = null;
- if (responseModeFromProfile != null) {
-
- if (responseModeFromProfile.equals(ResponseMode.FORM_POST.getValue())) {
- responseModeOverride = ResponseMode.FORM_POST;
- } else if (responseModeFromProfile.equals(ResponseMode.FORM_POST_JWT.getValue())) {
- responseModeOverride = ResponseMode.FORM_POST_JWT;
- } else if (responseModeFromProfile.equals(ResponseMode.QUERY.getValue())) {
- responseModeOverride = ResponseMode.QUERY;
- } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT.getValue())) {
- responseModeOverride = ResponseMode.FRAGMENT;
- } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT_JWT.getValue())) {
- responseModeOverride = ResponseMode.FRAGMENT_JWT;
- } else if (responseModeFromProfile.equals(ResponseMode.JWT.getValue())) {
- responseModeOverride = ResponseMode.JWT;
- } else if (responseModeFromProfile.equals(ResponseMode.QUERY_JWT.getValue())) {
- responseModeOverride = ResponseMode.QUERY_JWT;
- }
-
- if (responseModeOverride != null && !responseModeOverride.equals(compatibleMode)) {
- log.debug("{} Response_mode override '{}' exists in the profile configuration and is different than the"
- + " default mode '{}' for response_type '{}'",
- getLogPrefix(), responseModeFromProfile, compatibleMode, responseType);
- }
+ if (responseModeFromProfile == null) {
+ return null;
}
- responseTypeAndModeContext.setResponseType(responseType);
- responseTypeAndModeContext.setResponseMode(
- responseModeOverride == null ? compatibleMode : responseModeOverride);
-
- log.debug("{} Response_type '{}' selected", getLogPrefix(), responseTypeAndModeContext.getResponseType());
- log.debug("{} Response_mode '{}' selected", getLogPrefix(), responseTypeAndModeContext.getResponseMode());
+ if (responseModeFromProfile.equals(ResponseMode.FORM_POST.getValue())) {
+ return ResponseMode.FORM_POST;
+ } else if (responseModeFromProfile.equals(ResponseMode.FORM_POST_JWT.getValue())) {
+ return ResponseMode.FORM_POST_JWT;
+ } else if (responseModeFromProfile.equals(ResponseMode.QUERY.getValue())) {
+ return ResponseMode.QUERY;
+ } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT.getValue())) {
+ return ResponseMode.FRAGMENT;
+ } else if (responseModeFromProfile.equals(ResponseMode.FRAGMENT_JWT.getValue())) {
+ return ResponseMode.FRAGMENT_JWT;
+ } else if (responseModeFromProfile.equals(ResponseMode.JWT.getValue())) {
+ return ResponseMode.JWT;
+ } else if (responseModeFromProfile.equals(ResponseMode.QUERY_JWT.getValue())) {
+ return ResponseMode.QUERY_JWT;
+ } else {
+ return null;
+ }
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 682f61c..fa28105 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -33,7 +33,13 @@
p:deniedUserInfoAttributes="%{idp.authn.oidc.rp.deniedUserInfoAttributes:}"
p:clientId="#{%{idp.authn.oidc.rp.discoveryRequired:false} == true ? null : '%{idp.authn.oidc.rp.client.clientId:}'}"
p:clientCredential="#{%{idp.authn.oidc.rp.discoveryRequired:false} == true ? {null} : getObject('shibboleth.authn.oidc.rp.DefaultCredential')}"
- p:tokenEndpointAuthMethods="%{idp.authn.oidc.rp.clientAuthenticationMethod:client_secret_basic}" />
+ p:tokenEndpointAuthMethods="%{idp.authn.oidc.rp.clientAuthenticationMethod:client_secret_basic}"
+ p:responseMode="%{idp.authn.oidc.rp.responseMode:#{null}}"
+ p:scopes="%{idp.authn.oidc.rp.scopes:#{null}}">
+ <property name="forceAuthnPredicate">
+ <bean class="net.shibboleth.idp.saml.profile.config.logic.ProxyAwareForceAuthnPredicate" />
+ </property>
+ </bean>
<!-- Security Configuration Defaults. These settings establish the default security configurations for signatures and
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list