[java-idp-oidc] 04/44: JOIDC-5 Initial draft of the authorize -flow supporting SAML metadata. WIP.

Henri Mikkonen henri.mikkonen at iki.fi
Thu Oct 22 13:08:16 UTC 2020


This is an automated email from the git hooks/post-receive script.

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=a178aed3a32d9e877dcfb159ed258bd9b3d53269

commit a178aed3a32d9e877dcfb159ed258bd9b3d53269
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Apr 15 16:21:33 2020 +0300

    JOIDC-5 Initial draft of the authorize -flow supporting SAML metadata. WIP.
    
    https://issues.shibboleth.net/jira/browse/JOIDC-5
---
 .../context/OIDCSAMLPeerEntityContext.java         |  87 ++++++++++++++++
 .../profile/impl/PopulateOIDCMetadataContext.java  | 114 +++++++++++++++++++++
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  50 +++++++++
 .../idp/flows/oidc/authorize/authorize-flow.xml    |  23 +++++
 .../oidc/profile/flow/AuthorizeFlowTest.java       |  18 ++++
 .../src/test/resources/conf/global.xml             |   4 +
 .../src/test/resources/conf/metadata-providers.xml |  28 +++++
 7 files changed, 324 insertions(+)

diff --git a/idp-oidc-extension-api/src/main/java/org/geant/idpextension/oidc/messaging/context/OIDCSAMLPeerEntityContext.java b/idp-oidc-extension-api/src/main/java/org/geant/idpextension/oidc/messaging/context/OIDCSAMLPeerEntityContext.java
new file mode 100644
index 00000000..60fbdf2c
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/org/geant/idpextension/oidc/messaging/context/OIDCSAMLPeerEntityContext.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2017 - 2020, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.idpextension.oidc.messaging.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.saml.common.messaging.context.AbstractAuthenticatableSAMLEntityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.AuthorizationRequest;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Subcontext that carries information about a OIDC peer entity.
+ * 
+ * <p>
+ * This context will often contain subcontexts, whose data is construed to be scoped to that peer entity.
+ * </p>
+ * 
+ * <p>
+ * The method {@link #getEntityId()} will attempt to dynamically resolve the appropriate data 
+ * from the OIDC message held in the message context if the data has not been set statically 
+ * by the corresponding setter method. This evaluation will be attempted only if the this 
+ * context instance is an immediate child of the message context, as returned by {@link #getParent()}.
+ * </p>
+ */
+public class OIDCSAMLPeerEntityContext extends AbstractAuthenticatableSAMLEntityContext {
+
+    /** Logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(OIDCSAMLPeerEntityContext.class);
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nullable @NotEmpty public String getEntityId() {
+        if (super.getEntityId() == null) {
+            setEntityId(resolveEntityId());
+        }
+        return super.getEntityId();
+    }
+        
+    /**
+     * Dynamically resolve the OIDC peer entity ID from the OIDC protocol message held in 
+     * {@link MessageContext#getMessage()}.
+     * 
+     * @return the entity ID, or null if it could not be resolved
+     */
+    @Nullable protected String resolveEntityId() {
+        log.debug("Resolving issuer..");
+        if (getParent() instanceof MessageContext) {
+            final MessageContext parent = (MessageContext) getParent();
+            if (parent.getMessage() instanceof AuthorizationRequest) {
+                final AuthorizationRequest authzRequest = (AuthorizationRequest) parent.getMessage();
+                log.debug("Found client ID {}", authzRequest.getClientID());
+                return authzRequest.getClientID().getValue();
+            } else if (parent.getMessage() instanceof TokenRequest) {
+                final TokenRequest tokenRequest = (TokenRequest) parent.getMessage();
+                return tokenRequest.getClientID().getValue();
+            } else if (parent.getMessage() instanceof UserInfoRequest) {
+                final UserInfoRequest userInfoRequest = (UserInfoRequest) parent.getMessage();
+                //TODO: fetch the clientID from the access token
+            } else {
+                log.debug("Unsupported message type: {}", parent.getMessage());
+            }
+        }
+        return null;
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/org/geant/idpextension/oidc/profile/impl/PopulateOIDCMetadataContext.java b/idp-oidc-extension-impl/src/main/java/org/geant/idpextension/oidc/profile/impl/PopulateOIDCMetadataContext.java
new file mode 100644
index 00000000..1ce0fd46
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/org/geant/idpextension/oidc/profile/impl/PopulateOIDCMetadataContext.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2017 - 2020, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.idpextension.oidc.profile.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.geant.idpextension.oidc.messaging.context.OIDCMetadataContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.saml.oidc.xmlobject.OAuthRPRoleDescriptorType;
+import net.shibboleth.idp.saml.profile.context.navigate.SAMLMetadataContextLookupFunction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An action that attempts to locate OIDC client information from the SAML entity descriptor containing role
+ * descriptor of type {@link OAuthRPRoleDescriptorType}. If it contains {@link OIDCClientInformation} in the
+ * object metadata, it is attached inside {@link OIDCMetadataContext} as a child of a pre-existing instance of
+ * inbound {@link MessageContext}.
+ */
+public class PopulateOIDCMetadataContext extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(PopulateOIDCMetadataContext.class);
+    
+    @Nonnull private Function<ProfileRequestContext, SAMLMetadataContext> samlMetadataContextLookupStrategy;
+    
+    /** SAML metadata context to populate from. */
+    @Nullable private SAMLMetadataContext samlMetadataCtx;
+    
+    /**
+     * Constructor.
+     */
+    public PopulateOIDCMetadataContext() {
+        samlMetadataContextLookupStrategy = new SAMLMetadataContextLookupFunction();
+    }
+    
+    /**
+     * Set the strategy used to look up the {@link SAMLMetadataContext} to draw from.
+     * 
+     * @param strategy strategy used to look up the {@link SAMLMetadataContext}
+     */
+    public void setSamlMetadataContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,SAMLMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        samlMetadataContextLookupStrategy =
+                Constraint.isNotNull(strategy, "SAMLMetadataContext lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        samlMetadataCtx = samlMetadataContextLookupStrategy.apply(profileRequestContext);
+        if (samlMetadataCtx == null) {
+            log.debug("{} Unable to locate SAMLMetadataContext", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        return super.doPreExecute(profileRequestContext);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final List<RoleDescriptor> roleDescriptors 
+            = samlMetadataCtx.getEntityDescriptor().getRoleDescriptors(OAuthRPRoleDescriptorType.TYPE_NAME);
+        for (final RoleDescriptor roleDescriptor : roleDescriptors) {
+            if (roleDescriptor instanceof OAuthRPRoleDescriptorType) {
+                final List<OIDCClientInformation> clientInformations 
+                    = roleDescriptor.getObjectMetadata().get(OIDCClientInformation.class);
+                if (clientInformations != null && clientInformations.size() > 0) {
+                    final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+                    oidcCtx.setClientInformation(clientInformations.get(0));
+                    profileRequestContext.getInboundMessageContext().addSubcontext(oidcCtx);
+                    log.debug("{} Client information found and attached.", getLogPrefix());
+                    return;
+                }   
+            }
+        }
+        log.debug("{} No client information found to be attached into OIDC metadata context.", getLogPrefix());
+    }
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index d180133e..a61bb7b9 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -17,6 +17,56 @@
         </constructor-arg>
     </bean>
 
+    <bean id="OIDCRoleDescriptorType" class="javax.xml.namespace.QName">
+        <constructor-arg value="urn:mace:shibboleth:metadata:oidc:1.0"/>
+        <constructor-arg value="OAuthRPRoleDescriptorType"/>
+        <constructor-arg value="oidcmd"/>
+    </bean>
+
+    <bean id="SAMLProtocolAndRole"
+            class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg name="messageHandler">
+            <bean class="org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler" scope="prototype"
+                p:protocol="http://openid.net/specs/openid-connect-core-1_0.html"
+                p:role-ref="OIDCRoleDescriptorType" p:entityContextClass="org.geant.idpextension.oidc.messaging.context.OIDCSAMLPeerEntityContext"/>
+        </constructor-arg>
+    </bean>
+        
+     <bean id="SAMLMetadataLookup"
+            class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+            c:executionDirection="INBOUND">
+        <constructor-arg name="messageHandler">
+            <bean class="org.opensaml.saml.common.binding.impl.SAMLMetadataLookupHandler" scope="prototype"
+                p:entityContextClass="org.geant.idpextension.oidc.messaging.context.OIDCSAMLPeerEntityContext">
+                <property name="roleDescriptorResolver">
+                    <bean class="org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver"
+                        c:mdResolver-ref="shibboleth.MetadataResolver" />
+                </property>
+            </bean>
+        </constructor-arg>
+    </bean>
+
+    <bean id="shibboleth.ChildLookup.OIDCSAMLPeerEntityContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(org.geant.idpextension.oidc.messaging.context.OIDCSAMLPeerEntityContext) }" />
+
+    <bean id="shibboleth.ChildLookup.SAMLMetadataContext"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(org.opensaml.saml.common.messaging.context.SAMLMetadataContext) }" />
+        
+    <bean id="LookupOIDCSAMLPeerEntityContext" class="com.google.common.base.Functions" factory-method="compose"
+        c:g-ref="shibboleth.ChildLookup.OIDCSAMLPeerEntityContext"
+        c:f-ref="shibboleth.MessageContextLookup.Inbound"/>
+
+    <bean id="LookupSAMLMetadataContext" class="com.google.common.base.Functions" factory-method="compose"
+        c:g-ref="shibboleth.ChildLookup.SAMLMetadataContext"
+        c:f-ref="LookupOIDCSAMLPeerEntityContext"/>
+
+    <bean id="PopulateOIDCMetadataContext"
+        class="org.geant.idpextension.oidc.profile.impl.PopulateOIDCMetadataContext" scope="prototype"
+        p:samlMetadataContextLookupStrategy-ref="LookupSAMLMetadataContext" />
+
     <bean id="InitializeAuthenticationContext"
         class="org.geant.idpextension.oidc.profile.impl.InitializeAuthenticationContext" scope="prototype" />
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 50bd03af..5fdbe912 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -12,7 +12,30 @@
     <action-state id="DecodeMessage">
         <evaluate expression="DecodeMessage" />
         <evaluate expression="PostDecodePopulateAuditContext" />
+        <evaluate expression="SAMLProtocolAndRole" />
+        <evaluate expression="SAMLMetadataLookup" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="CheckIfFoundFromSAMLMetadata" />
+    </action-state>        
+
+    <decision-state id="CheckIfFoundFromSAMLMetadata">
+        <if test="opensamlProfileRequestContext.getInboundMessageContext().getSubcontext(T(org.geant.idpextension.oidc.messaging.context.OIDCSAMLPeerEntityContext)).containsSubcontext(T(org.opensaml.saml.common.messaging.context.SAMLMetadataContext))"
+            then="PopulateOIDCMetadataContextFromSAML" else="LookupFromClientInformationService" />
+    </decision-state>
+    
+    <action-state id="PopulateOIDCMetadataContextFromSAML">
+        <evaluate expression="PopulateOIDCMetadataContext" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="SelectConfiguration" />
+    </action-state>
+
+    <action-state id="LookupFromClientInformationService">        
         <evaluate expression="OIDCMetadataLookup" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="SelectConfiguration" />
+    </action-state>
+    
+    <action-state id="SelectConfiguration">
         <evaluate expression="InitializeRelyingPartyContext" />
         <evaluate expression="SelectRelyingPartyConfiguration" />
         <evaluate expression="SelectProfileConfiguration" />
diff --git a/idp-oidc-extension-impl/src/test/java/org/geant/idpextension/oidc/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/org/geant/idpextension/oidc/profile/flow/AuthorizeFlowTest.java
index ac179a03..43ed616f 100644
--- a/idp-oidc-extension-impl/src/test/java/org/geant/idpextension/oidc/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/org/geant/idpextension/oidc/profile/flow/AuthorizeFlowTest.java
@@ -74,6 +74,24 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
     }
+    
+    @Test
+    public void testWithAuthorizationCodeFlowUsingSAMLMetadata() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockSamlClientId&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri);
+        storeMetadata(storageService, clientId, clientSecret, redirectUri);
+
+        initializeThreadLocals();
+        
+        FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
 
     @AfterMethod
     public void removeMetadata() throws IOException {
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
index dc37270f..44ff8e8a 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
@@ -15,6 +15,10 @@
     <!-- Use this file to define any custom beans needed globally. -->
     
     <import resource="global-oidc.xml" />
+
+    <bean id="exampleMetadata-saml-oidc" class="org.springframework.core.io.ClassPathResource">
+        <constructor-arg value="/org/geant/idpextension/oidc/metadata/impl/EntitiesDescriptor-with-oidcmd.xml"/>
+    </bean>
     
     <util:set id="testbed.MetadataIndexes">
         <bean class="org.opensaml.saml.metadata.resolver.index.impl.SAMLArtifactMetadataIndex" />
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml b/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml
new file mode 100644
index 00000000..40f30fb2
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/conf/metadata-providers.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This file is an EXAMPLE metadata configuration file. -->
+<MetadataProvider id="ShibbolethMetadata" xsi:type="ChainingMetadataProvider"
+        xmlns="urn:mace:shibboleth:2.0:metadata" xmlns:resource="urn:mace:shibboleth:2.0:resource"
+        xmlns:security="urn:mace:shibboleth:2.0:security"
+        xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:oidcext="urn:mace:shibboleth:2.0:metadata:oidc"
+        xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd
+                        urn:mace:shibboleth:2.0:resource http://shibboleth.net/schema/idp/shibboleth-resource.xsd 
+                        urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd
+                        urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd
+                        urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd
+                        urn:mace:shibboleth:2.0:metadata:oidc classpath:/schema/idp-oidc-extension-metadata-ext.xsd">
+                        
+        <!-- ========================================== -->
+        <!-- Metadata Configuration -->
+        <!-- ========================================== -->
+
+    <MetadataProvider id="SP123MD" xsi:type="ResourceBackedMetadataProvider" maxRefreshDelay="PT5M" indexesRef="testbed.MetadataIndexes" resourceRef="exampleMetadata-saml-oidc">
+        <MetadataFilter xsi:type="NodeProcessing">
+            <MetadataNodeProcessor xsi:type="oidcext:ClientInformation"/>
+        </MetadataFilter>
+    </MetadataProvider>
+
+</MetadataProvider>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list