[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