[java-idp-oidc] branch main updated: JOIDC-61 Support metadata policies in the dyn. reg. profile configuration
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Jan 7 13:30:13 UTC 2022
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=70e41bb8fbb6aad340228afc417f34586ba5b48a
The following commit(s) were added to refs/heads/main by this push:
new 70e41bb8 JOIDC-61 Support metadata policies in the dyn. reg. profile configuration
70e41bb8 is described below
commit 70e41bb8fbb6aad340228afc417f34586ba5b48a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Jan 7 15:25:37 2022 +0200
JOIDC-61 Support metadata policies in the dyn. reg. profile configuration
https://shibboleth.atlassian.net/browse/JOIDC-61
Initial, still incomplete support for metadata policies with dynamic
client registration. Some remarks:
- RegistrationClaimsSet overlaps with the other claims
set classes in the same package, but is still independent.
- ValidateRegistrationRequestMetadata doesn't store the enforced request
metadata anywhere yet.
- Default metadata policy resolver (via file) doesn't handle non-existing
files yet: flow tests work as the file exists, but real deployment doesn't.
---
idp-oidc-extension-api/pom.xml | 15 +
...IDCClientRegistrationMetadataPolicyContext.java | 64 ++++
.../OIDCClientRegistrationTokenClaimsContext.java | 63 +++
...gistrationTokenClaimsContextLookupFunction.java | 42 ++
...istrationTokenMetadataPolicyLookupFunction.java | 51 +++
.../oidc/op/storage/RevocationCacheContexts.java | 10 +-
.../op/token/support/RegistrationClaimsSet.java | 424 +++++++++++++++++++++
...nitializeRegistrationMetadataPolicyContext.java | 207 ++++++++++
.../impl/ValidateRegistrationAccessToken.java | 242 ++++++++++++
.../impl/ValidateRegistrationRequestMetadata.java | 180 +++++++++
.../DefaultMetadataPolicyMergingStrategy.java | 104 +++++
.../idp/flows/oidc/register/register-beans.xml | 16 +
.../idp/flows/oidc/register/register-flow.xml | 3 +
.../idp/service/relying-party/postconfig.xml | 74 +++-
.../idp/plugin/oidc/op/conf/oidc.properties | 4 +
.../oidc/op/profile/flow/RegistrationFlowTest.java | 72 +++-
.../src/test/resources/conf/metadata-policy1.json | 21 +
.../src/test/resources/conf/oidc.properties | 2 +
.../src/test/resources/conf/relying-party.xml | 1 +
19 files changed, 1590 insertions(+), 5 deletions(-)
diff --git a/idp-oidc-extension-api/pom.xml b/idp-oidc-extension-api/pom.xml
index 2736f711..59be2ebd 100644
--- a/idp-oidc-extension-api/pom.xml
+++ b/idp-oidc-extension-api/pom.xml
@@ -58,6 +58,11 @@
<artifactId>opensaml-xmlsec-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-metadata-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
@@ -68,6 +73,16 @@
<artifactId>jakarta.mail-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- Test Dependencies -->
<dependency>
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java
new file mode 100644
index 00000000..987b3afc
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationMetadataPolicyContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidc.op.messaging.context;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Subcontext carrying information on the metadata policy related to the dynamic client registration.
+ *
+ * This context appears as a subcontext of the (inbound) {@link org.opensaml.messaging.context.MessageContext}.
+ *
+ * @since 3.1.0
+ */
+public class OIDCClientRegistrationMetadataPolicyContext extends BaseContext {
+
+ /** The metadata policy related to dynamic client registration. */
+ private Map<String, MetadataPolicy> metadataPolicy;
+
+ /**
+ * Constructor.
+ */
+ public OIDCClientRegistrationMetadataPolicyContext() {
+ // no op
+ }
+
+ /**
+ * Set the metadata policy related to dynamic client registration.
+ *
+ * @param policy What to set.
+ */
+ public void setMetadataPolicy(@Nullable final Map<String, MetadataPolicy> policy) {
+ metadataPolicy = policy;
+ }
+
+ /**
+ * Get the metadata policy related to dynamic client registration.
+ *
+ * @return The metadata policy related to dynamic client registration.
+ */
+ @Nullable public Map<String, MetadataPolicy> getMetadataPolicy() {
+ return metadataPolicy;
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
new file mode 100644
index 00000000..62e94440
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCClientRegistrationTokenClaimsContext.java
@@ -0,0 +1,63 @@
+/*
+ * 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.oidc.op.messaging.context;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+
+/**
+ * Subcontext carrying information on the claims included in the initial access token used for accessing the dynamic
+ * client registration endpoint.
+ *
+ * This context appears as a subcontext of the (inbound) {@link org.opensaml.messaging.context.MessageContext}.
+ *
+ * @since 3.1.0
+ */
+public class OIDCClientRegistrationTokenClaimsContext extends BaseContext {
+
+ /** The claims set in the initial access token. */
+ @Nullable private RegistrationClaimsSet claimsSet;
+
+ /**
+ * Constructor.
+ */
+ public OIDCClientRegistrationTokenClaimsContext() {
+ // no op
+ }
+
+ /**
+ * Set the claims set in the initial access token.
+ *
+ * @param set What to set.
+ */
+ public void setClaimsSet(@Nullable final RegistrationClaimsSet set) {
+ claimsSet = set;
+ }
+
+ /**
+ * Get the claims set in the initial access token.
+ *
+ * @return The claims set in the initial access token.
+ */
+ @Nullable public RegistrationClaimsSet getClaimsSet() {
+ return claimsSet;
+ }
+}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
new file mode 100644
index 00000000..382b7eaa
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.oidc.op.profile.context.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+
+/**
+ * A function that returns a {@link OIDCClientRegistrationTokenClaimsContext} from the inbound message context, located
+ * as a subcontext for the profile request context.
+ */
+public class DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction
+ implements ContextDataLookupFunction<ProfileRequestContext, OIDCClientRegistrationTokenClaimsContext> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public OIDCClientRegistrationTokenClaimsContext apply(@Nullable final ProfileRequestContext input) {
+ if (input == null || input.getInboundMessageContext() == null) {
+ return null;
+ }
+ return input.getInboundMessageContext().getSubcontext(OIDCClientRegistrationTokenClaimsContext.class, false);
+ }
+}
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
new file mode 100644
index 00000000..d41bc52f
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.oidc.op.profile.context.navigate;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * A function that returns a map of metadata policies from the {@link RegistrationClaimsSet}. The claims set is fetched
+ * via {@link DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction}.
+ */
+public class DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction implements
+ ContextDataLookupFunction<ProfileRequestContext, Map<String, MetadataPolicy>> {
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public Map<String, MetadataPolicy> apply(@Nullable ProfileRequestContext profileRequestContext) {
+ final Function<ProfileRequestContext, OIDCClientRegistrationTokenClaimsContext> regTokenCtxLookup =
+ new DefaultOIDCClientRegistrationTokenClaimsContextLookupFunction();
+ final OIDCClientRegistrationTokenClaimsContext regTokenCtx = regTokenCtxLookup.apply(profileRequestContext);
+ if (regTokenCtx == null) {
+ return null;
+ }
+ final RegistrationClaimsSet claimsSet = regTokenCtx.getClaimsSet();
+ return claimsSet == null ? null : claimsSet.getMetadata();
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
index 742e7172..6416ed8e 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/storage/RevocationCacheContexts.java
@@ -25,12 +25,20 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
public final class RevocationCacheContexts {
/**
- * ID of context for revoking authorization codes.
+ * ID of context for revoking authorization codes (and access/refresh tokens based on the authorization codes).
*/
@Nonnull
@NotEmpty
public static final String AUTHORIZATION_CODE = RevocationCacheContexts.class.getName() + ".AUTHORIZATION_CODE";
+ /**
+ * ID of context for revoking access tokens issued for the dynamic client registration.
+ */
+ @Nonnull
+ @NotEmpty
+ public static final String REGISTRATION_ACCESS_TOKEN = RevocationCacheContexts.class.getName()
+ + ".REGISTRATION_ACCESS_TOKEN";
+
/** Private constructor. */
private RevocationCacheContexts() {
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
new file mode 100644
index 00000000..54bbb5b4
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
@@ -0,0 +1,424 @@
+/*
+ * 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.oidc.op.token.support;
+
+import java.time.Instant;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+
+/**
+ * Class wrapping claims for the initial registration access token.
+ */
+public class RegistrationClaimsSet {
+
+ /** Value of initial registration access token claims set type. */
+ private static final String VALUE_TYPE_RT = "rt";
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(RegistrationClaimsSet.class);
+
+ /** Identifier for the token. */
+ @JsonProperty("jti")
+ private String jti;
+
+ /** Type of the token. */
+ @JsonProperty("type")
+ private String keyType;
+
+ /** OP issuer. */
+ @JsonProperty("iss")
+ private String issuer;
+
+ /** Expiration time of the token. */
+ @JsonProperty("exp")
+ private Instant expiration;
+
+ /** Issuance time of the token. */
+ @JsonProperty("iat")
+ private Instant issuedAt;
+
+ /** The principal who issued the token. */
+ @JsonProperty("prncpl")
+ private String principal;
+
+ /** Authentication context class reference value of the performed authentication. */
+ @JsonProperty("acr")
+ private String authContext;
+
+ /** Authentication time of the performed authentication. */
+ @JsonProperty("auth_time")
+ private Instant authTime;
+
+ /** Allowed metadata values to be issued with the token. */
+ @JsonProperty("metadata")
+ private Map<String, MetadataPolicy> metadata;
+
+ /** Relying party identifier. */
+ @JsonProperty("rp_id")
+ private String relyingPartyId;
+
+ /**
+ * Constructor.
+ */
+ private RegistrationClaimsSet() {
+ }
+
+ /**
+ * Get the identifier for the token.
+ *
+ * @return The identifier for the token.
+ */
+ public String getJti() {
+ return jti;
+ }
+
+ /**
+ * Set the identifier for the token.
+ *
+ * @param id What to set.
+ */
+ public void setJti(final String id) {
+ this.jti = id;
+ }
+
+ /**
+ * Get the type of the token.
+ *
+ * @return The type of the token.
+ */
+ public String getKeyType() {
+ return keyType;
+ }
+
+ /**
+ * Set the type of the token.
+ *
+ * @param type What to set.
+ */
+ public void setKeyType(final String type) {
+ this.keyType = type;
+ }
+
+ /**
+ * Get the issuer of the token.
+ *
+ * @return The issuer of the token.
+ */
+ public String getIssuer() {
+ return issuer;
+ }
+
+ /**
+ * Set the issuer of the token.
+ *
+ * @param iss What to set.
+ */
+ public void setIssuer(final String iss) {
+ this.issuer = iss;
+ }
+
+ /**
+ * Get the expiration time of the token.
+ *
+ * @return The expiration time of the token.
+ */
+ public Instant getExpiration() {
+ return expiration;
+ }
+
+ /**
+ * Set the expiration time of the token.
+ *
+ * @param exp What to set.
+ */
+ public void setExpiration(final Instant exp) {
+ this.expiration = exp;
+ }
+
+ /**
+ * Get the issuance time of the token.
+ *
+ * @return The issuance time of the token.
+ */
+ public Instant getIssuedAt() {
+ return issuedAt;
+ }
+
+ /**
+ * Set the issuance time of the token.
+ *
+ * @param iat What to set.
+ */
+ public void setIssuedAt(final Instant iat) {
+ this.issuedAt = iat;
+ }
+
+ /**
+ * Get the principal who issued the token.
+ *
+ * @return The principal who issued the token.
+ */
+ public String getPrincipal() {
+ return principal;
+ }
+
+ /**
+ * Set the principal who issued the token.
+ *
+ * @param prncpl What to set.
+ */
+ public void setPrincipal(final String prncpl) {
+ this.principal = prncpl;
+ }
+
+ /**
+ * Get the authentication context class reference value of the performed authentication.
+ *
+ * @return The authentication context class reference value of the performed authentication.
+ */
+ public String getAuthContext() {
+ return authContext;
+ }
+
+ /**
+ * Set the authentication context class reference value of the performed authentication.
+ *
+ * @param acr What to set.
+ */
+ public void setAuthContext(final String acr) {
+ this.authContext = acr;
+ }
+
+ /**
+ * Get the authentication time of the performed authentication.
+ *
+ * @return The authentication time of the performed authentication.
+ */
+ public Instant getAuthTime() {
+ return authTime;
+ }
+
+ /**
+ * Set the authentication time of the performed authentication.
+ *
+ * @param time What to set.
+ */
+ public void setAuthTime(final Instant time) {
+ this.authTime = time;
+ }
+
+ /**
+ * Get the allowed metadata values to be issued with the token.
+ *
+ * @return The allowed metadata values to be issued with the token.
+ */
+ public Map<String, MetadataPolicy> getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * Set the allowed metadata values to be issued with the token.
+ *
+ * @param data What to set.
+ */
+ public void setMetadata(final Map<String, MetadataPolicy> data) {
+ this.metadata = data;
+ }
+
+ /**
+ * Get the relying party identifier.
+ *
+ * @return The relying party identifier.
+ */
+ public String getRelyingPartyId() {
+ return relyingPartyId;
+ }
+
+ /**
+ * Set the relying party identifier.
+ *
+ * @param rpId What to set.
+ */
+ public void setRelyingPartyId(final String rpId) {
+ this.relyingPartyId = rpId;
+ }
+
+ /**
+ * The builder for {@link RegistrationClaimsSet}.
+ */
+ public static class Builder {
+
+ /** Identifier for the token. */
+ private String jti;
+
+ /** Type of the token. */
+ private String keyType;
+
+ /** Issuer of the token. */
+ private String issuer;
+
+ /** Expiration time of the token. */
+ private Instant expiration;
+
+ /** Issuance time of the token. */
+ private Instant issuedAt;
+
+ /** The principal who issued the token. */
+ private String principal;
+
+ /** The authentication context class reference value of the performed authentication. */
+ private String authContext;
+
+ /** The authentication time of the performed authentication. */
+ private Instant authTime;
+
+ /** Allowed metadata values to be issued with the token. */
+ private Map<String, MetadataPolicy> metadata;
+
+ /** Relying party identifier. */
+ private String relyingPartyId;
+
+ /**
+ * Constructor.
+ *
+ * @param id The identifier for the token.
+ */
+ public Builder(final String id) {
+ this.jti = id;
+ this.keyType = VALUE_TYPE_RT;
+ }
+
+ /**
+ * Sets the type of the token.
+ * @param type What to set.
+ * @return The builder instance.
+ */
+ public Builder withType(final String type) {
+ this.keyType = type;
+ return this;
+ }
+
+ /**
+ * Sets the issuer of the token.
+ * @param iss What to set.
+ * @return The builder instance.
+ */
+ public Builder withIssuer(final String iss) {
+ this.issuer = iss;
+ return this;
+ }
+
+ /**
+ * Set the expiration time of the token.
+ * @param exp What to set.
+ * @return The builder instance.
+ */
+ public Builder withExpiration(final Instant exp) {
+ this.expiration = exp;
+ return this;
+ }
+
+ /**
+ * Set the issuance time of the token.
+ * @param iat What to set.
+ * @return The builder instance.
+ */
+ public Builder withIssuedAt(final Instant iat) {
+ this.issuedAt = iat;
+ return this;
+ }
+
+ /**
+ * Set the principal who issued the token.
+ * @param prncpl What to set.
+ * @return The builder instance.
+ */
+ public Builder withPrincipal(final String prncpl) {
+ this.principal = prncpl;
+ return this;
+ }
+
+ /**
+ * Set the authentication context class reference value of the performed authentication.
+ * @param acr What to set.
+ * @return The builder instance.
+ */
+ public Builder withAcr(final String acr) {
+ this.authContext = acr;
+ return this;
+ }
+
+ /**
+ * Set the authentication time of the performed authentication.
+ * @param time What to set.
+ * @return The builder instance.
+ */
+ public Builder withAuthTime(final Instant time) {
+ this.authTime = time;
+ return this;
+ }
+
+ /**
+ * Set the allowed metadata values to be issued with the token.
+ * @param data What to set.
+ * @return The builder instance.
+ */
+ public Builder withMetadata(final Map<String, MetadataPolicy> data) {
+ this.metadata = data;
+ return this;
+ }
+
+ /**
+ * Set the relying party identifier.
+ * @param rpId What to set.
+ * @return The builder instance.
+ */
+ public Builder withRelyingPartyId(final String rpId) {
+ this.relyingPartyId = rpId;
+ return this;
+ }
+
+ /**
+ * Build the claims set object.
+ * @return The claims set object.
+ */
+ public RegistrationClaimsSet build() {
+ final RegistrationClaimsSet claimsSet = new RegistrationClaimsSet();
+ claimsSet.setJti(jti);
+ claimsSet.setKeyType(keyType);
+ claimsSet.setIssuer(issuer);
+ claimsSet.setExpiration(expiration);
+ claimsSet.setIssuedAt(issuedAt);
+ claimsSet.setPrincipal(principal);
+ claimsSet.setAuthContext(authContext);
+ claimsSet.setAuthTime(authTime);
+ claimsSet.setMetadata(metadata);
+ claimsSet.setRelyingPartyId(relyingPartyId);
+ return claimsSet;
+ }
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
new file mode 100644
index 00000000..a6b15be9
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
@@ -0,0 +1,207 @@
+/*
+ * 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.oidc.op.profile.impl;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationMetadataPolicyContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyMergingStrategy;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator;
+import net.shibboleth.oidc.profile.config.navigate.RegistrationMetadataPolicyLookupFunction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Initializes the {@link OIDCClientRegistrationMetadataPolicyContext} and attaches it as a subcontext for the incoming
+ * {@link MessageContext}. The context contains the merged metadata policy, containing the policy items defined in the
+ * profile configuration and initial registration access token (if involved).
+ */
+public class InitializeRegistrationMetadataPolicyContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(InitializeRegistrationMetadataPolicyContext.class);
+
+ /** The strategy used to locate the request metadata policy configured for the profile. */
+ @NonnullAfterInit private Function<ProfileRequestContext, Map<String, MetadataPolicy>>
+ profileMetadataPolicyLookupStrategy;
+
+ /** The strategy used to locate the request metadata policy encoded in the access token. */
+ @NonnullAfterInit private Function<ProfileRequestContext, Map<String, MetadataPolicy>>
+ tokenMetadataPolicyLookupStrategy;
+
+ /** The strategy used to create or locate the metadata policy context. */
+ @NonnullAfterInit private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
+ registrationPolicyContextCreationStrategy;
+
+ /** The strategy used for merging profile and token based metadata policies. */
+ @NonnullAfterInit private Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+ Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
+
+ /** The strategy used for validating token and merged metadata policies. */
+ @NonnullAfterInit private Predicate<Map<String, MetadataPolicy>> metadataPolicyValidationStrategy;
+
+ /** The metadata policy context to operate on. */
+ private OIDCClientRegistrationMetadataPolicyContext metadataPolicyContext;
+
+ /**
+ * Constructor.
+ */
+ public InitializeRegistrationMetadataPolicyContext() {
+ profileMetadataPolicyLookupStrategy = new RegistrationMetadataPolicyLookupFunction();
+ tokenMetadataPolicyLookupStrategy = new DefaultOIDCClientRegistrationTokenMetadataPolicyLookupFunction();
+ registrationPolicyContextCreationStrategy =
+ new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, true);
+ metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
+ metadataPolicyValidationStrategy = new DefaultMetadataPolicyValidator();
+ }
+
+ /**
+ * Set the strategy used to locate the request metadata policy configured for the profile.
+ *
+ * @param strategy What to set.
+ */
+ public void setProfileMetadataPolicyLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ profileMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
+ "Profile metadata policy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the request metadata policy encoded in the access token.
+ *
+ * @param strategy What to set.
+ */
+ public void setTokenMetadataPolicyLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, Map<String, MetadataPolicy>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ tokenMetadataPolicyLookupStrategy = Constraint.isNotNull(strategy,
+ "Token metadata policy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to create or return the {@link OIDCClientRegistrationMetadataPolicyContext}.
+ *
+ * @param strategy What to set.
+ */
+ public void setRegistrationPolicyContextCreationStrategy(
+ @Nonnull final Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ registrationPolicyContextCreationStrategy = Constraint.isNotNull(strategy,
+ "OIDCClientRegistrationMetadataPolicyContext creation strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used for merging profile and token based metadata policies.
+ *
+ * @param strategy What to set.
+ */
+ public void setMetadataPolicyMergingStrategy(@Nonnull final Function<Pair<Map<String, MetadataPolicy>,
+ Map<String, MetadataPolicy>>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ metadataPolicyMergingStrategy = Constraint.isNotNull(strategy,
+ "Metadata policy merging strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used for validating token and merged metadata policies.
+ *
+ * @param strategy What to set.
+ */
+ public void setMetadataPolicyValidationStrategy(@Nonnull final Predicate<Map<String, MetadataPolicy>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ metadataPolicyValidationStrategy = Constraint.isNotNull(strategy,
+ "Metadata policy validation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ metadataPolicyContext =
+ registrationPolicyContextCreationStrategy.apply(profileRequestContext.getInboundMessageContext());
+ if (metadataPolicyContext == null) {
+ log.error("{} The registration metadata policy context could not be created, invalid profile context",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final Map<String, MetadataPolicy> profileMetadataPolicy =
+ profileMetadataPolicyLookupStrategy.apply(profileRequestContext);
+
+ final Map<String, MetadataPolicy> tokenMetadataPolicy =
+ tokenMetadataPolicyLookupStrategy.apply(profileRequestContext);
+
+ if (!metadataPolicyValidationStrategy.test(tokenMetadataPolicy)) {
+ log.warn("{} The token metadata policy is not valid", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final Pair<Map<String, MetadataPolicy>, Boolean> mergedResult =
+ metadataPolicyMergingStrategy.apply(new Pair<>(profileMetadataPolicy, tokenMetadataPolicy));
+
+ if (!mergedResult.getSecond()) {
+ log.warn("{} The metadata policies from profile and token could not be merged", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final Map<String, MetadataPolicy> mergedPolicy = mergedResult.getFirst();
+ if (!metadataPolicyValidationStrategy.test(mergedPolicy)) {
+ log.warn("{} The merged metadata policy is not valid", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ metadataPolicyContext.setMetadataPolicy(mergedPolicy);
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
new file mode 100644
index 00000000..342e4a8e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
@@ -0,0 +1,242 @@
+/*
+ * 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.oidc.op.profile.impl;
+
+import java.time.Instant;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientRegistrationRequest;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationTokenClaimsContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds;
+import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCache;
+import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.security.DataSealer;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+
+/**
+ * Action that validates registration access token is a valid one. Token is valid if it is successfully unwrapped,
+ * parsed as access token, is not expired, has not been revoked and contains relying party identifier. Validated
+ * token is stored to under incoming message context {@link OIDCClientRegistrationTokenClaimsContext#getClaimsSet()}.
+ */
+public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<OIDCClientRegistrationRequest> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ValidateRegistrationAccessToken.class);
+
+ /** Strategy that will return a {@link RelyingPartyContext}. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /** Strategy to create or return a {@link OIDCClientRegistrationTokenClaimsContext}. */
+ @Nonnull private Function<MessageContext, OIDCClientRegistrationTokenClaimsContext>
+ registrationClaimsContextCreationStrategy;
+
+ /** Data sealer for unwrapping authorization code. */
+ @NonnullAfterInit private DataSealer dataSealer;
+
+ /** Message revocation cache instance to use. */
+ @NonnullAfterInit private RevocationCache revocationCache;
+
+ /** JSON object mapper. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** The relying party context to operate on. */
+ private RelyingPartyContext relyingPartyContext;
+
+ /** The registration token claims context to operate on. */
+ private OIDCClientRegistrationTokenClaimsContext registrationClaimsContext;
+
+ /** The registration access token to be validated. */
+ private String accessToken;
+
+ /**
+ * Constructor.
+ */
+ public ValidateRegistrationAccessToken() {
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class, false);
+ registrationClaimsContextCreationStrategy =
+ new ChildContextLookup<>(OIDCClientRegistrationTokenClaimsContext.class, true);
+ }
+
+ /**
+ * Set the strategy used to return the {@link RelyingPartyContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, RelyingPartyContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to create or return the {@link OIDCClientRegistrationTokenClaimsContext}.
+ *
+ * @param strategy creation strategy
+ */
+ public void setRegistrationClaimsContextCreationStrategy(
+ @Nonnull final Function<MessageContext, OIDCClientRegistrationTokenClaimsContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ registrationClaimsContextCreationStrategy = Constraint.isNotNull(strategy,
+ "OIDCClientRegistrationTokenClaimsContext creation strategy cannot be null");
+ }
+
+ /**
+ * Set the data sealer for handling access token.
+ *
+ * @param sealer data sealer.
+ */
+ public void setSealer(@Nonnull final DataSealer sealer) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ dataSealer = Constraint.isNotNull(sealer, "Data sealer cannot be null");
+ }
+
+ /**
+ * Set the JSON {@link ObjectMapper}.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /**
+ * Set the revocation cache instance to use.
+ *
+ * @param cache The revocationCache to set.
+ */
+ public void setRevocationCache(@Nonnull final RevocationCache cache) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (dataSealer == null) {
+ throw new ComponentInitializationException("DataSealer cannot be null");
+ }
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("ObjectMapper cannot be null");
+ }
+
+ if (revocationCache == null) {
+ throw new ComponentInitializationException("RevocationCache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ relyingPartyContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
+ if (relyingPartyContext == null) {
+ log.error("{} No relying party context found, invalid profile context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ if (getRequest().getAccessToken() == null || getRequest().getAccessToken().getValue() == null) {
+ log.debug("{} No access token in the request, nothing to do", getLogPrefix());
+ return false;
+ }
+ accessToken = getRequest().getAccessToken().getValue();
+
+ registrationClaimsContext =
+ registrationClaimsContextCreationStrategy.apply(profileRequestContext.getInboundMessageContext());
+ if (registrationClaimsContext == null) {
+ log.error("{} The registration token claims context could not be created, invalid profile context",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final RegistrationClaimsSet claimsSet;
+ try {
+ final String unwrapped = dataSealer.unwrap(accessToken);
+ log.debug("{} access token unwrapped into {}", getLogPrefix(), unwrapped);
+ claimsSet = objectMapper.readValue(unwrapped, RegistrationClaimsSet.class);
+ } catch (final DataSealerException | JsonProcessingException e) {
+ log.error("{} Decoding access token failed: {}", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ return;
+ }
+
+ log.debug("{} registration access token decoded into {}", getLogPrefix(), claimsSet);
+
+ if (Instant.now().isAfter(claimsSet.getExpiration())) {
+ log.error("{} registration access token exp is in the past {}", getLogPrefix(), claimsSet.getExpiration());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ return;
+ }
+ if (revocationCache.isRevoked(RevocationCacheContexts.REGISTRATION_ACCESS_TOKEN, claimsSet.getJti())) {
+ log.error("{} registration access token {} has been revoked", getLogPrefix(),
+ claimsSet.getJti());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ return;
+ }
+ final String relyingPartyId = StringSupport.trimOrNull(claimsSet.getRelyingPartyId());
+ if (relyingPartyId == null) {
+ log.error("{} registration access token {} didn't contain relying party identifier", getLogPrefix(),
+ claimsSet.getJti());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ return;
+ }
+ log.debug("{} registration access token {} successfully validated", getLogPrefix(), claimsSet.getJti());
+ relyingPartyContext.setVerified(true);
+ relyingPartyContext.setRelyingPartyId(relyingPartyId);
+ registrationClaimsContext.setClaimsSet(claimsSet);
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
new file mode 100644
index 00000000..7883fb2d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
@@ -0,0 +1,180 @@
+/*
+ * 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.oidc.op.profile.impl;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientRegistrationRequest;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationMetadataPolicyContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Validates the incoming dynamic client registration request against the metadata policy stored in the
+ * {@link OIDCClientRegistrationMetadataPolicyContext}.
+ *
+ * TODO: where to store the result
+ */
+public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(ValidateRegistrationRequestMetadata.class);
+
+ /** Strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}. */
+ @NonnullAfterInit private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
+ registrationMetadataPolicyContextLookupStrategy;
+
+ /** Function used for enforcing the metadata policy. */
+ @NonnullAfterInit private Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+ /** The OIDCClientRegistrationRequest to validate. */
+ @Nullable private OIDCClientRegistrationRequest request;
+
+ /** The metadata policy used for validation. */
+ private Map<String, MetadataPolicy> metadataPolicy;
+
+ /**
+ * Constructor.
+ */
+ public ValidateRegistrationRequestMetadata() {
+ registrationMetadataPolicyContextLookupStrategy =
+ new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, false);
+
+ metadataPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
+ }
+
+ /**
+ * Set the strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}.
+ *
+ * @param strategy Strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}.
+ */
+ public void setRegistrationMetadataPolicyContextLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ registrationMetadataPolicyContextLookupStrategy = Constraint.isNotNull(strategy,
+ "Registration metadata policy context lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the function used for enforcing the metadata policy.
+ *
+ * @param function Function used for enforcing the metadata policy.
+ */
+ public void setMetadataPolicyEnforcer(
+ @Nonnull final Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> function) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ metadataPolicyEnforcer = Constraint.isNotNull(function, "The metadata policy enforcer cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final MessageContext messageContext = profileRequestContext.getInboundMessageContext();
+
+ if (messageContext == null) {
+ log.debug("{} No inbound message context associated with this profile request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ final Object message = messageContext.getMessage();
+ if (message == null || !(message instanceof OIDCClientRegistrationRequest)) {
+ log.debug("{} No inbound message associated with this profile request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ request = (OIDCClientRegistrationRequest) message;
+
+
+ final OIDCClientRegistrationMetadataPolicyContext registrationMetadataPolicyContext =
+ registrationMetadataPolicyContextLookupStrategy.apply(messageContext);
+
+ if (registrationMetadataPolicyContext == null) {
+ log.debug("{} No metadata policy context associated with this request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ metadataPolicy = registrationMetadataPolicyContext.getMetadataPolicy();
+ if (metadataPolicy == null || metadataPolicy.isEmpty()) {
+ log.debug("{} No metadata policy found, nothing to do", getLogPrefix());
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ log.debug("{} Metadata policy to be validated: {}", getLogPrefix(), metadataPolicy);
+ boolean compliant = true;
+
+ for (final String claim : metadataPolicy.keySet()) {
+ log.trace("{} Analysing claim {}", getLogPrefix(), claim);
+ final JSONObject requestMetadata = request.getOIDCClientMetadata().toJSONObject();
+ if (requestMetadata.containsKey(claim)) {
+ final Object value = requestMetadata.get(claim);
+ log.debug("{} The claim {} set in policy included in the request", getLogPrefix(), claim);
+ final MetadataPolicy policy = metadataPolicy.get(claim);
+ final Pair<Object, MetadataPolicy> candidate = new Pair<>(value, policy);
+ final Pair<Object, Boolean> result = metadataPolicyEnforcer.apply(candidate);
+
+ if (!result.getSecond()) {
+ log.warn("{} the metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+ compliant = false;
+ } else {
+ log.trace("{} validation result is OK for claim {}", getLogPrefix(), claim);
+ }
+ }
+
+ //TODO: store the result
+ }
+
+ if (!compliant) {
+ log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ }
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
new file mode 100644
index 00000000..8822e6a8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
@@ -0,0 +1,104 @@
+/*
+ * 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.oidc.op.profile.logic;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyHelper;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+
+/**
+ * <p>A function that merges two maps of metadata policies according to the rules specified in the OIDC federation spec
+ * (draft 17), section 5.1.3.1:</p>
+ * <ul>
+ * <li>
+ * subset_of: The result of merging the values of two subset_of operators is the intersection of the operator values.
+ * </li>
+ * <li>
+ * one_of: The result of merging the values of two one_of operators is the intersection of the operator values.
+ * </li>
+ * <li>
+ * superset_of: The result of merging the values of two superset_of operators is the union of the operator values.
+ * </li>
+ * <li>
+ * add: The result of merging the values of two add operators is the union of the values.
+ * </li>
+ * <li>
+ * value: Merging two value operators is NOT allowed unless the two operator values are equal.
+ * </li>
+ * <li>
+ * default: Merging two default operators is NOT allowed unless the two operator values are equal.
+ * </li>
+ * <li>
+ * essential: If a superior has specified essential=true, then a subordinate cannot change that. If a superior has
+ * specified essential=false, then a subordinate is allowed to change that to essential=true. If a superior has not
+ * specified essential, then a subordinate can set essential to true or false.
+ * </li>
+ * <p>The function returns a pair of map of merged metadata policies and a boolean indicating if the merging operators
+ * in the policies were compliant.
+ * </ul>
+ */
+public class DefaultMetadataPolicyMergingStrategy implements
+ Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+ Pair<Map<String, MetadataPolicy>, Boolean>> {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyMergingStrategy.class);
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Pair<Map<String, MetadataPolicy>, Boolean> apply(
+ @Nullable final Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>> input) {
+ final Map<String, MetadataPolicy> first = input.getFirst();
+ final Map<String, MetadataPolicy> second = input.getSecond();
+ if (first == null) {
+ return new Pair<>(second, Boolean.TRUE);
+ } else if (second == null || second.isEmpty()) {
+ return new Pair<>(first, Boolean.TRUE);
+ }
+ final Set<String> combinedKeys = Stream.concat(first.keySet().stream(),
+ second.keySet().stream()).collect(Collectors.toSet());
+ final Map<String, MetadataPolicy> result = new HashMap<>();
+ boolean valid = true;
+ for (final String key : combinedKeys) {
+ try {
+ result.put(key, MetadataPolicyHelper.mergeMetadataPolicies(first.get(key), second.get(key)));
+ } catch (final ConstraintViolationException e) {
+ log.warn("Incompatible metadata policies for claim '{}' that cannot be merged.", key);
+ valid = false;
+ }
+ }
+ return new Pair<>(result, Boolean.valueOf(valid));
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
index 0f8f398c..1a305cd0 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
@@ -24,6 +24,19 @@
</constructor-arg>
</bean>
+ <bean id="ValidateRegistrationAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationAccessToken" scope="prototype"
+ p:revocationCache-ref="shibboleth.RevocationCache"
+ p:sealer-ref="#{'%{idp.oidc.dynreg.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+ p:objectMapper-ref="shibboleth.JSONObjectMapper" />
+
+ <bean id="ValidateRegistrationRequestMetadata"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
+
+ <bean id="InitializeRegistrationMetadataPolicyContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRegistrationMetadataPolicyContext"
+ scope="prototype" />
+
<bean id="InitializeRelyingPartyContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext"
scope="prototype" />
@@ -172,6 +185,9 @@
<entry
key="#{T(org.opensaml.profile.action.EventIds).UNABLE_TO_DECODE}"
value="#{T(com.nimbusds.oauth2.sdk.client.RegistrationError).INVALID_CLIENT_METADATA}" />
+ <entry
+ key="#{T(org.opensaml.profile.action.EventIds).INVALID_MESSAGE}"
+ value="#{T(com.nimbusds.oauth2.sdk.client.RegistrationError).INVALID_CLIENT_METADATA}" />
</map>
</property>
</bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
index 0727f8e7..14bc0bb6 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-flow.xml
@@ -16,6 +16,7 @@
<evaluate expression="DecodeMessage" />
<evaluate expression="PostDecodePopulateAuditContext" />
<evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="ValidateRegistrationAccessToken" />
<evaluate expression="SelectRelyingPartyConfiguration" />
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="'proceed'" />
@@ -24,6 +25,8 @@
</action-state>
<action-state id="OutboundContextsAndSecurityParameters">
+ <evaluate expression="InitializeRegistrationMetadataPolicyContext" />
+ <evaluate expression="ValidateRegistrationRequestMetadata" />
<evaluate expression="CheckRedirectURIs" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="BuildResponse" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 913f806b..d1d4e920 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -51,8 +51,9 @@
p:registrationValidityPeriod="%{idp.oidc.dynreg.defaultRegistrationValidity:PT24H}"
p:secretExpirationPeriod="%{idp.oidc.dynreg.defaultSecretExpiration:P12M}"
p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
- p:claimsValidator-ref="DefaultJWTClaimsValidator" />
-
+ p:claimsValidator-ref="DefaultJWTClaimsValidator"
+ p:metadataPolicyLookupStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyLookupStrategy" />
+
<bean id="OIDC.Configuration" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.config.OIDCProviderInformationConfiguration"
p:issuer-ref="issuer" />
@@ -315,6 +316,11 @@
<bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="secretExpirationPeriod"
p:defaultValue="%{idp.oidc.dynreg.defaultSecretExpiration:P12M}" />
</property>
+ <property name="metadataPolicyLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="metadataPolicy"
+ p:propertyType="#{T(java.util.Map)}"
+ p:defaultValue-ref="%{idp.oidc.dynreg.defaultMetadataPolicy:shibboleth.oidc.dynreg.DefaultMetadataPolicy}" />
+ </property>
</bean>
<bean id="OIDC.Configuration.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
@@ -636,5 +642,67 @@
<!-- Encryption keys to publish. -->
<bean id="shibboleth.oidc.PublishKeySetRequestObjectDecryptionConfiguration" parent="shibboleth.BasicEncryptionConfiguration"
p:keyTransportEncryptionCredentials="#{getObject('shibboleth.oidc.EncryptionCredentialsToPublish') ?: getObject('shibboleth.oidc.EncryptionCredentials')}"/>
-
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyLookupStrategy"
+ class="net.shibboleth.oidc.profile.config.navigate.ResolverBasedRegistrationMetadataPolicyLookupFunction"
+ p:metadataPolicyResolver-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver" />
+
+ <bean id="shibboleth.oidc.dynreg.BaseMetadataPolicyResolver" abstract="true"
+ class="net.shibboleth.oidc.metadata.policy.impl.OIDCMetadataPolicyResolver" />
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver"
+ parent="shibboleth.oidc.dynreg.BaseMetadataPolicyResolver">
+ <constructor-arg>
+ <bean parent="shibboleth.oidc.dynreg.BatchCacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultPolicyFileCache" parent="shibboleth.oidc.dynreg.BatchMetadataCacheBuilderSpec">
+ <property name="loadingStrategy">
+ <bean class="net.shibboleth.oidc.metadata.cache.impl.DefaultFileLoadingStrategy"
+ c:metadata="%{idp.oidc.dynreg.defaultMetadataPolicyFile:}" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+ <!-- Batch OIDC provider cache builder factory and template specification -->
+ <bean id="batchCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilder$Builder"/>
+
+ <bean id="shibboleth.oidc.dynreg.BatchCacheBuilder" factory-bean="batchCacheFactory" factory-method="build"
+ abstract="true"/>
+
+ <bean class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec"
+ id="shibboleth.oidc.dynreg.BatchMetadataCacheBuilderSpec" abstract="true"
+ p:parsingStrategy-ref="shibboleth.oidc.dynreg.DefaultJSONMapParsingStrategy"
+ p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataCriteriaToIdentifierStrategy"
+ p:sourceMetadataExpiryStrategy-ref="shibboleth.oidc.dynreg.DefaultExpirationTimeStrategy"
+ p:identifierExtractionStrategy-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyIdentifierExtractionStrategy"
+ p:metadataValidPredicate-ref="shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator"/>
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyValidator"
+ class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyValidator" />
+
+ <bean id="shibboleth.oidc.dynreg.DefaultJSONMapParsingStrategy"
+ class="net.shibboleth.oidc.metadata.cache.impl.DefaultJSONMapParsingStrategy"
+ c:valueClass="net.shibboleth.oidc.metadata.policy.MetadataPolicy"/>
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataCriteriaToIdentifierStrategy"
+ parent="shibboleth.Functions.Constant" c:target="DefaultMetadataPolicy" />
+
+ <bean id="shibboleth.oidc.dynreg.DefaultExpirationTimeStrategy"
+ class="net.shibboleth.oidc.metadata.cache.impl.DefaultSourceMetadataExpirationTimeStrategy" c:duration="PT10M"/>
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicyIdentifierExtractionStrategy"
+ parent="shibboleth.Functions.Constant" c:target="DefaultMetadataPolicy" />
+
+ <bean id="shibboleth.oidc.dynreg.DefaultMetadataPolicy"
+ factory-bean="shibboleth.oidc.dynreg.DefaultMetadataPolicyResolver"
+ factory-method="resolveSingle">
+ <constructor-arg>
+ <!-- Empty criteria set is assumed to be ok for the resolver -->
+ <bean class="net.shibboleth.utilities.java.support.resolver.CriteriaSet" />
+ </constructor-arg>
+ </bean>
+
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index f3f23600..387f3ec9 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -16,6 +16,10 @@ idp.oidc.issuer = https://your.issuer.example.org
#idp.oidc.dynreg.allowNoneForRequestSigning = true
# Bean to determine whether dynamic registration should validate the remote JWK set if it's defined in the request
#idp.oidc.dynreg.validateRemoteJwks = shibboleth.Conditions.TRUE
+# Full path to the file containing default metadata policy used for dynamic client registration
+#idp.oidc.dynreg.defaultMetadataPolicyFile =
+# Bean to determine the default metadata policy used for dynamic client registration
+#idp.oidc.dynreg.defaultMetadataPolicy = shibboleth.oidc.dynreg.DefaultMetadataPolicy
# Storage for storing remote jwk sets.
#idp.oidc.jwk.StorageService = shibboleth.StorageService
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
index d25d10d4..3c9b1810 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
@@ -19,6 +19,8 @@ package net.shibboleth.idp.plugin.oidc.op.profile.flow;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,13 +30,17 @@ import org.testng.Assert;
import org.testng.annotations.Test;
import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformationResponse;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
/**
* Some tests for the dynamic registration flow.
@@ -68,7 +74,21 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
}
@Test
- public void testSuccess() throws ParseException, IOException, net.minidev.json.parser.ParseException {
+ public void testUnauthenticated_nonCompliantWithProfilePolicy1() {
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"https://not.compliant.org/cb\"] }");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_client_metadata");
+ }
+
+ @Test
+ public void testUnauthenticated_nonCompliantWithProfilePolicy2() {
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"id_token_signed_response_alg\":\"HS256\" }");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_client_metadata");
+ }
+
+ @Test
+ public void testUnauthenticated_success() throws ParseException, IOException, net.minidev.json.parser.ParseException {
setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
OIDCClientInformationResponse parsedResponse = parseSuccessResponse(result, OIDCClientInformationResponse.class);
@@ -85,4 +105,54 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
}
+ @Test
+ public void testAccessToken_nonCompliantWithProfilePolicy1() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
+ request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://invalid.domain.org/cb\"]")
+ .toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_client_metadata");
+ }
+
+ @Test
+ public void testAccessToken_nonCompliantWithProfilePolicy2() throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"id_token_signed_response_alg\":\"HS256\" }");
+ request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
+ .toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, "invalid_client_metadata");
+ }
+
+ @Test
+ public void testAccessToken_success() throws ParseException, IOException, net.minidev.json.parser.ParseException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"] }");
+ request.addHeader("Authorization", buildRegistrationAccessToken("[\"https://example.org/cb\"]")
+ .toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ OIDCClientInformationResponse parsedResponse = parseSuccessResponse(result, OIDCClientInformationResponse.class);
+ OIDCClientInformation clientInfo = parsedResponse.getOIDCClientInformation();
+ OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+ String record = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME,
+ clientInfo.getID().toString()).getValue();
+ Assert.assertNotNull(record);
+ JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
+ OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(record));
+ Assert.assertEquals(storedInfo.getID(), clientInfo.getID());
+ Assert.assertEquals(storedInfo.getSecret(), clientInfo.getSecret());
+ Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(), metadata.getRedirectionURIStrings());
+ Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
+ }
+
+ protected BearerAccessToken buildRegistrationAccessToken(final String redirectUriSubset) throws NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ final String json = "{" +
+ "\"prncpl\":\"jdoe\"," +
+ "\"type\":\"rat\"," +
+ "\"exp\":" + Instant.now().plusSeconds(30).getEpochSecond() + "," +
+ "\"iat\":" + Instant.now().getEpochSecond() + "," +
+ "\"jti\":\"" + idGenerator.generateIdentifier() + "\"," +
+ "\"rp_id\":\"mockRpId\"," +
+ "\"metadata\":{\"redirect_uris\":{\"subset_of\":" + redirectUriSubset + "}}}";
+ return new BearerAccessToken(BaseOIDCResponseActionTest.initializeDataSealer().wrap(json,
+ Instant.now().plusSeconds(30)));
+ }
}
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json b/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json
new file mode 100644
index 00000000..c5945dfb
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/conf/metadata-policy1.json
@@ -0,0 +1,21 @@
+{
+ "grant_types": {
+ "one_of": ["authorization_code","implicit"]
+ },
+ "client_name": {
+ "default": "A known test application"
+ },
+ "organization_name": {
+ "value": "A trusted organization"
+ },
+ "redirect_uris": {
+ "regexp": "^https:\/\/(?:([^.]+).)?example.org\/(.*)",
+ "essential": true
+ },
+ "id_token_signed_response_alg": {
+ "subset_of": ["RS256", "RS384", "RS512"]
+ },
+ "scopes": {
+ "subset_of": ["openid", "profile", "email", "phone"]
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
index 8dd2e620..0d00d608 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/conf/oidc.properties
@@ -5,3 +5,5 @@ idp.oidc.issuer = https://op.example.org
idp.oidc.subject.sourceAttribute = uid
idp.oidc.subject.salt = isfd07fsddfs70sdf9d99s8
idp.oidc.discovery.template = src/test/resources/conf/openid-configuration.json
+
+idp.oidc.dynreg.defaultMetadataPolicyFile = src/test/resources/conf/metadata-policy1.json
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index 4de25ce5..5eb74533 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -46,6 +46,7 @@
<ref bean="OIDC.SSO.MDDriven" />
<ref bean="OIDC.Token.MDDriven" />
<ref bean="OIDC.UserInfo.MDDriven" />
+ <ref bean="OIDC.Registration.MDDriven" />
<ref bean="OAUTH2.Revocation.MDDriven" />
<ref bean="OAUTH2.Introspection.MDDriven" />
</list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list