[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