[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant
Scott Cantor
cantor.2 at osu.edu
Thu Jan 13 21:29:46 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor 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=80578f441eb6ac9a16026e69bbcf4b0270c39abf
The following commit(s) were added to refs/heads/main by this push:
new 80578f44 JOIDC-11 - Support for client_credentials grant
80578f44 is described below
commit 80578f441eb6ac9a16026e69bbcf4b0270c39abf
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Jan 13 16:29:42 2022 -0500
JOIDC-11 - Support for client_credentials grant
https://shibboleth.atlassian.net/browse/JOIDC-11
Draft of initial implementation minus tests.
---
.../idp/plugin/oidc/op/audit/AuditFields.java | 6 +
.../op/messaging/context/AccessTokenContext.java | 114 ++++++
.../context/OIDCAuthenticationResponseContext.java | 207 ++++++----
.../context/OAuthAttributeResolutionContext.java | 67 +++
.../AccessTokenClaimsSetLookupFunction.java | 72 ++++
.../navigate/ValidatedAudienceLookupFunction.java | 47 +++
.../navigate/ValidatedScopeLookupFunction.java | 47 +++
.../oidc/op/profile/context/package-info.java | 21 +
.../oidc/op/token/support/TokenClaimsSet.java | 88 +++-
.../op/oauth2/profile/impl/BuildAccessToken.java | 447 +++++++++++++++++++++
.../OAuthAttributeResolutionContextDecorator.java | 70 ++++
.../impl/SetAccessTokenToResponseContext.java | 116 ++++++
.../op/oauth2/profile/impl/SignAccessToken.java | 129 ++++++
.../op/profile/impl/AddAttributesToClaimsSet.java | 41 +-
.../impl/SetAccessTokenToResponseContext.java | 4 +-
.../META-INF/net.shibboleth.idp/postconfig.xml | 21 +
.../flows/oidc/abstract/oidc-abstract-beans.xml | 13 +
.../idp/flows/oidc/authorize/authorize-beans.xml | 1 -
.../idp/flows/oidc/token/token-beans.xml | 122 ++++--
.../shibboleth/idp/flows/oidc/token/token-flow.xml | 33 +-
.../idp/flows/oidc/userinfo/userinfo-beans.xml | 12 +-
.../profile/impl/AddAttributesToClaimsSetTest.java | 1 -
22 files changed, 1505 insertions(+), 174 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/audit/AuditFields.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/audit/AuditFields.java
index 394e56d4..474c837e 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/audit/AuditFields.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/audit/AuditFields.java
@@ -47,6 +47,12 @@ public final class AuditFields {
/** The subject format (public/pairwise). */
@Nonnull @NotEmpty public static final String SUB_FORMAT = SAMLAuditFields.NAMEID_FORMAT;
+ /** Token scope. */
+ @Nonnull @NotEmpty public static final String SCOPE = "scope";
+
+ /** Token audience. */
+ @Nonnull @NotEmpty public static final String AUDIENCE = "aud";
+
/** The flag whether the id_token is encrypted. */
@Nonnull @NotEmpty public static final String ENCRYPTED_ID_TOKEN = SAMLAuditFields.ENCRYPTION;
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/AccessTokenContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/AccessTokenContext.java
new file mode 100644
index 00000000..46121510
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/AccessTokenContext.java
@@ -0,0 +1,114 @@
+/*
+ * 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.time.Duration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Subcontext carrying information used to produce access tokens.
+ *
+ * @since 3.1.0
+ */
+public final class AccessTokenContext extends BaseContext {
+
+ /** Lifetime of the token. */
+ @Nullable private Duration lifetime;
+
+ /** Opaque token value if JWT format is not used. */
+ @Nullable @NotEmpty private String opaque;
+
+ /** The signed/encrypted token in the case of JWT format. */
+ @Nullable private JWT jwt;
+
+ /**
+ * Get the token string in the case of an opaque token.
+ *
+ * @return the token value
+ */
+ @Nullable @NotEmpty public String getOpaque() {
+ return opaque;
+ }
+
+ /**
+ * Set the token string in the case of an opaque token.
+ *
+ * @param token the token string
+ *
+ * @return this context
+ */
+ @Nonnull public AccessTokenContext setOpaque(@Nullable @NotEmpty final String token) {
+ opaque = StringSupport.trimOrNull(token);
+ return this;
+ }
+
+ /**
+ * Get the JWT in the case of a token in that form.
+ *
+ * <p>May be in various states prior to signing/encryption.</p>
+ *
+ * @return the JWT
+ */
+ @Nullable public JWT getJWT() {
+ return jwt;
+ }
+
+ /**
+ * Set the JWT in the case of a token in that form.
+ *
+ * <p>May be in various states prior to signing/encryption.</p>
+ *
+ * @param token the JWT
+ *
+ * @return this context
+ */
+ @Nonnull public AccessTokenContext setJWT(@Nullable final JWT token) {
+ jwt = token;
+ return this;
+ }
+
+ /**
+ * Get the token lifetime.
+ *
+ * @return lifetime
+ */
+ @Nullable public Duration getLifetime() {
+ return lifetime;
+ }
+
+ /**
+ * Set the token lifetime.
+ *
+ * @param lt lifetime
+ *
+ * @return this context
+ */
+ @Nonnull public AccessTokenContext setLifetime(@Nullable final Duration lt) {
+ lifetime = lt;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
index 0e01d22a..94e54224 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/OIDCAuthenticationResponseContext.java
@@ -20,6 +20,8 @@ package net.shibboleth.idp.plugin.oidc.op.messaging.context;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -33,11 +35,14 @@ import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
import com.nimbusds.openid.connect.sdk.claims.ACR;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import net.shibboleth.idp.attribute.AttributesMapContainer;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
/**
* Subcontext carrying information to form authentication, token and userinfo responses for relying party. This context
@@ -57,6 +62,10 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
@Nullable
private UserInfo userInfo;
+ /** The access token claim set. */
+ @Nullable
+ private ClaimsSet accessTokenClaimsSet;
+
/** The signed/encrypted id token / user info response formed. */
@Nullable
private JWT processedToken;
@@ -75,7 +84,11 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
/** Validated scope values. */
@Nullable
- private Scope requestedScope;
+ private Scope validatedScope;
+
+ /** Validated audience values. */
+ @Nonnull @NonnullElements
+ private List<String> validatedAudience;
/** Requested sub value. */
@Nullable
@@ -115,6 +128,11 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
/** Mapped requested claims from the Userinfo set. */
@Nullable private AttributesMapContainer mappedUserinfoRequestedClaims;
+ /** Constructor. */
+ public OIDCAuthenticationResponseContext() {
+ validatedAudience = new ArrayList<>();
+ }
+
/**
* Get requested claims.
*
@@ -241,60 +259,6 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
authorizationCode = code == null ? null : new AuthorizationCode(code);
}
- /**
- * Get access token.
- *
- * @return access token
- */
- @Nullable
- public AccessToken getAccessToken() {
- return accessToken;
- }
-
- /**
- * Set access token.
- *
- * @param token String to form access token
- * @param lifeTime lifetime of the access token
- */
- public void setAccessToken(@Nullable final String token, @Nonnull final Duration lifeTime) {
- setAccessToken(token, lifeTime, null);
- }
-
-
- /**
- * Set access token.
- *
- * @param token string to form access token
- * @param lifeTime lifetime of the access token
- * @param scope scope of the access token
- *
- * @since 3.1.0
- */
- public void setAccessToken(@Nullable final String token, @Nonnull final Duration lifeTime,
- @Nonnull final Scope scope) {
- accessToken = token == null ? null : new BearerAccessToken(token, lifeTime.getSeconds(), scope);
- }
-
- /**
- * Get refresh token.
- *
- * @return refresh token
- */
- @Nullable
- public RefreshToken getRefreshToken() {
- return refreshToken;
- }
-
- /**
- * Set refresh token.
- *
- * @param token String to form refresh token
- */
- public void setRefreshToken(@Nullable final String token) {
- refreshToken = token == null ? null : new RefreshToken(token);
- }
-
/**
* Gets requested sub value.
*
@@ -311,7 +275,7 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
* @param sub requested sub value.
*/
public void setRequestedSubject(@Nullable final String sub) {
- this.requestedSubject = sub;
+ requestedSubject = sub;
}
/**
@@ -319,8 +283,7 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
*
* @return Name ID generated for response
*/
- @Nullable
- public String getSubject() {
+ @Nullable public String getSubject() {
return subject;
}
@@ -338,8 +301,7 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
*
* @return generated subject type.
*/
- @Nullable
- public String getSubjectType() {
+ @Nullable public String getSubjectType() {
return subjectType;
}
@@ -357,9 +319,8 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
*
* @return validated scope values
*/
- @Nullable
- public Scope getScope() {
- return requestedScope;
+ @Nullable public Scope getScope() {
+ return validatedScope;
}
/**
@@ -368,7 +329,18 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
* @param scope scope values
*/
public void setScope(@Nullable final Scope scope) {
- requestedScope = scope;
+ validatedScope = scope;
+ }
+
+ /**
+ * Get modifiable collection of token audience values.
+ *
+ * @return audience collection
+ *
+ * @since 3.1.0
+ */
+ @Nonnull @NonnullElements @Live public List<String> getAudience() {
+ return validatedAudience;
}
/**
@@ -434,9 +406,9 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
}
/**
- * Get the id token.
+ * Get the {@link IDTokenClaimsSet} object that will source the ID token.
*
- * @return The id token.
+ * @return ID token claims set
*/
@Nullable
public IDTokenClaimsSet getIDToken() {
@@ -444,18 +416,18 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
}
/**
- * Set the id token.
+ * Set the {@link IDTokenClaimsSet} object that will source the ID token.
*
- * @param token The id token.
+ * @param token ID token claims set
*/
public void setIDToken(@Nullable final IDTokenClaimsSet token) {
idToken = token;
}
/**
- * Get the user info.
+ * Get the {@link UserInfo} claims set that will source the UserInfo response.
*
- * @return The user info.
+ * @return UserInfo claims set
*/
@Nullable
public UserInfo getUserInfo() {
@@ -463,18 +435,94 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
}
/**
- * Set the user info.
+ * Set the {@link UserInfo} claims set that will source the UserInfo response.
*
- * @param info The user info.
+ * @param info UserInfo claims set
*/
public void setUserInfo(@Nullable final UserInfo info) {
userInfo = info;
}
-
- /**
- * Get the signed/encrypted id token / user info response.
- *
- * @return The signed id token / user info response
+
+ /**
+ * Get the access token claims set (used when prepping OAuth-only access tokens).
+ *
+ * @return access token claims set
+ *
+ * @since 3.1.0
+ */
+ @Nullable
+ public ClaimsSet getAccessTokenClaimSet() {
+ return accessTokenClaimsSet;
+ }
+
+ /**
+ * Set the access token claims set (used when prepping OAuth-only access tokens).
+ *
+ * @param claims access token claims set
+ *
+ * @since 3.1.0
+ */
+ public void setAccessTokenClaimsSet(@Nullable final ClaimsSet claims) {
+ accessTokenClaimsSet = claims;
+ }
+
+ /**
+ * Get access token.
+ *
+ * @return access token
+ */
+ @Nullable
+ public AccessToken getAccessToken() {
+ return accessToken;
+ }
+
+ /**
+ * Set access token.
+ *
+ * @param token String to form access token
+ * @param lifeTime lifetime of the access token
+ */
+ public void setAccessToken(@Nullable final String token, @Nonnull final Duration lifeTime) {
+ setAccessToken(token, lifeTime, null);
+ }
+
+ /**
+ * Set access token.
+ *
+ * @param token string to form access token
+ * @param lifeTime lifetime of the access token
+ * @param scope scope of the access token
+ *
+ * @since 3.1.0
+ */
+ public void setAccessToken(@Nullable final String token, @Nonnull final Duration lifeTime,
+ @Nonnull final Scope scope) {
+ accessToken = token == null ? null : new BearerAccessToken(token, lifeTime.getSeconds(), scope);
+ }
+
+ /**
+ * Get refresh token.
+ *
+ * @return refresh token
+ */
+ @Nullable
+ public RefreshToken getRefreshToken() {
+ return refreshToken;
+ }
+
+ /**
+ * Set refresh token.
+ *
+ * @param token String to form refresh token
+ */
+ public void setRefreshToken(@Nullable final String token) {
+ refreshToken = token == null ? null : new RefreshToken(token);
+ }
+
+ /**
+ * Get the signed/encrypted ID token / UserInfo response JWT.
+ *
+ * @return ID token / UserInfo response JWT
*/
@Nullable
public JWT getProcessedToken() {
@@ -482,9 +530,9 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
}
/**
- * Set the signed/encrypted id token / user info response.
+ * Set the signed/encrypted ID token / UserInfo response JWT.
*
- * @param token The signed id token / user info response
+ * @param token ID token / UserInfo response JWT
*/
public void setProcessedToken(@Nullable final JWT token) {
processedToken = token;
@@ -508,4 +556,5 @@ public class OIDCAuthenticationResponseContext extends BaseContext {
public void setRequestObject(@Nullable final JWT obj) {
requestObject = obj;
}
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/OAuthAttributeResolutionContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/OAuthAttributeResolutionContext.java
new file mode 100644
index 00000000..0bcf6d2f
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/OAuthAttributeResolutionContext.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+/**
+ * A supplemental context adding OAuth- and OIDC-related input to the attribute resolution process.
+ *
+ * @since 3.1.0
+ */
+public final class OAuthAttributeResolutionContext extends BaseContext {
+
+ /** Requested and validated scopes. */
+ @Nonnull @NonnullElements private final Collection<String> scopes;
+
+ /** Requested resources. */
+ @Nonnull @NonnullElements private final Collection<String> resources;
+
+ /** Constructor. */
+ public OAuthAttributeResolutionContext() {
+ scopes = new ArrayList<>();
+ resources = new ArrayList<>();
+ }
+
+ /**
+ * Get the requested and metadata-validated scope collection for this request.
+ *
+ * @return modifiable collection of scope values
+ */
+ @Nonnull @NonnullElements @Live public Collection<String> getScope() {
+ return scopes;
+ }
+
+ /**
+ * Get the requested audience collection for this request.
+ *
+ * @return modifiable collection of audience values
+ */
+ @Nonnull @NonnullElements @Live public Collection<String> getResources() {
+ return resources;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AccessTokenClaimsSetLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AccessTokenClaimsSetLookupFunction.java
new file mode 100644
index 00000000..7340d847
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AccessTokenClaimsSetLookupFunction.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+
+/**
+ * A function that returns the access token claims set from response context.
+ *
+ * <p>An option exists to create it if it does not already exist.</p>
+ *
+ * @since 3.1.0
+ */
+public class AccessTokenClaimsSetLookupFunction
+ implements ContextDataLookupFunction<ProfileRequestContext,ClaimsSet> {
+
+ /** Whether to create the claims set if it is absent. */
+ private boolean autocreate;
+
+ /**
+ * Sets whether to create the {@link ClaimsSet} if absent.
+ *
+ * <p>Defaults to false.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setAutoCreate(final boolean flag) {
+ autocreate = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable
+ public ClaimsSet apply(@Nullable final ProfileRequestContext input) {
+ if (input == null || input.getOutboundMessageContext() == null) {
+ return null;
+ }
+
+ final OIDCAuthenticationResponseContext ctx =
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
+ if (ctx == null) {
+ return null;
+ }
+
+ if (ctx.getAccessTokenClaimSet() == null && autocreate) {
+ ctx.setAccessTokenClaimsSet(new ClaimsSet());
+ }
+
+ return ctx.getAccessTokenClaimSet();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedAudienceLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedAudienceLookupFunction.java
new file mode 100644
index 00000000..444acd45
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedAudienceLookupFunction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Collection;
+
+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.OIDCAuthenticationResponseContext;
+
+/** A function that returns {@link OIDCAuthenticationResponseContext#getAudience()}. */
+public class ValidatedAudienceLookupFunction
+ implements ContextDataLookupFunction<ProfileRequestContext,Collection<String>> {
+
+ /** {@inheritDoc} */
+ @Nullable
+ public Collection<String> apply(@Nullable final ProfileRequestContext input) {
+ if (input == null || input.getOutboundMessageContext() == null) {
+ return null;
+ }
+ final OIDCAuthenticationResponseContext ctx =
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
+ if (ctx == null) {
+ return null;
+ }
+ return ctx.getAudience();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedScopeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedScopeLookupFunction.java
new file mode 100644
index 00000000..3c344da6
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/ValidatedScopeLookupFunction.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.nimbusds.oauth2.sdk.Scope;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+
+/** A function that returns {@link OIDCAuthenticationResponseContext#getScope()}. */
+public class ValidatedScopeLookupFunction
+ implements ContextDataLookupFunction<ProfileRequestContext,Scope> {
+
+ /** {@inheritDoc} */
+ @Nullable
+ public Scope apply(@Nullable final ProfileRequestContext input) {
+ if (input == null || input.getOutboundMessageContext() == null) {
+ return null;
+ }
+ final OIDCAuthenticationResponseContext ctx =
+ input.getOutboundMessageContext().getSubcontext(OIDCAuthenticationResponseContext.class);
+ if (ctx == null) {
+ return null;
+ }
+ return ctx.getScope();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/package-info.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/package-info.java
new file mode 100644
index 00000000..7fa5371a
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contexts related to OP profile actions.
+ */
+package net.shibboleth.idp.plugin.oidc.op.profile.context;
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
index cbbf4196..ea9b6e5e 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/TokenClaimsSet.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.plugin.oidc.op.token.support;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -82,7 +83,7 @@ public class TokenClaimsSet {
@Nonnull @NotEmpty public static final String KEY_EXPIRATION_TIME = "exp";
/** Not before time of the token. */
- @Nonnull @NotEmpty public static final String KEY_NOTBEFORE_TIME = "nbt";
+ @Nonnull @NotEmpty public static final String KEY_NOTBEFORE_TIME = "nbf";
/** Issue time of the token. */
@Nonnull @NotEmpty public static final String KEY_ISSUED_AT = "iat";
@@ -173,9 +174,6 @@ public class TokenClaimsSet {
if (tokenClaimsSet.getStringClaim(KEY_ISSUER) == null) {
throw new ParseException("claim iss must exist and not be null", 0);
}
- if (tokenClaimsSet.getStringClaim(KEY_USER_PRINCIPAL) == null) {
- throw new ParseException("claim prncpl must exist and not be null", 0);
- }
if (tokenClaimsSet.getStringClaim(KEY_SUBJECT) == null) {
throw new ParseException("claim sub must exist and not be null", 0);
}
@@ -194,9 +192,6 @@ public class TokenClaimsSet {
if (tokenClaimsSet.getDateClaim(KEY_AUTH_TIME) == null) {
throw new ParseException("claim auth_time must exist and not be null", 0);
}
- if (tokenClaimsSet.getStringClaim(KEY_REDIRECT_URI) == null) {
- throw new ParseException("claim redirect_uri must exist and not be null", 0);
- }
if (tokenClaimsSet.getStringClaim(KEY_SCOPE) == null) {
throw new ParseException("claim scope must exist and not be null", 0);
}
@@ -306,8 +301,8 @@ public class TokenClaimsSet {
public boolean isTimeValid() {
final Instant now = Instant.now();
if (getExp().isAfter(now)) {
- final Instant nbt = getNotBefore();
- return nbt == null || now == nbt || now.isAfter(nbt);
+ final Instant nbf = getNotBefore();
+ return nbf == null || now == nbf || now.isAfter(nbf);
}
return false;
@@ -654,9 +649,13 @@ public class TokenClaimsSet {
/** Code challenge. */
@Nullable protected String codeChallenge;
+ /** Extends the token with custom claims. */
+ @Nonnull protected Map<String,Object> customClaims;
+
/** Default constructor. */
protected Builder() {
audience = Collections.emptyList();
+ customClaims = new HashMap<>();
}
// Checkstyle:CyclomaticComplexity OFF
@@ -673,13 +672,13 @@ public class TokenClaimsSet {
*/
@Nonnull protected JWTClaimsSet buildJWTClaimsSet(@Nonnull @NotEmpty final String tokenType) {
- if (tokenType == null || jwtid == null || rpId == null || iss == null || principal == null
- || iat == null || exp == null || authTime == null || redirect == null || reqScope == null
+ if (tokenType == null || jwtid == null || rpId == null || iss == null
+ || iat == null || exp == null || authTime == null || reqScope == null
|| sub == null) {
throw new RuntimeException("Invalid parameters, programming error");
}
- return new JWTClaimsSet.Builder()
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
.claim(KEY_TYPE, tokenType)
.jwtID(jwtid)
.claim(KEY_CLIENTID, rpId.getValue())
@@ -701,8 +700,15 @@ public class TokenClaimsSet {
.claim(KEY_DELIVERY_CLAIMS_USERINFO, dlClaimsUI == null ? null : dlClaimsUI.toJSONObject())
.claim(KEY_CONSENTED_CLAIMS, consentedClaims)
.claim(KEY_CODE_CHALLENGE, codeChallenge)
- .claim(KEY_CONSENT_ENABLED, consentEnabled)
- .build();
+ .claim(KEY_CONSENT_ENABLED, consentEnabled);
+
+ customClaims.forEach((n,v) -> {
+ if (n != null) {
+ builder.claim(n, v);
+ }
+ });
+
+ return builder.build();
}
// Checkstyle:CyclomaticComplexity ON
@@ -922,12 +928,29 @@ public class TokenClaimsSet {
* @param claimsRequest claims request of the authentication request.
*
* @return the builder
+ *
+ * @deprecated
*/
+ @Deprecated(since="3.1.0", forRemoval=true)
public Builder<T> setClaims(@Nullable final OIDCClaimsRequest claimsRequest) {
reqClaims = claimsRequest;
return this;
}
+ /**
+ * Set claims request of the authentication request.
+ *
+ * @param claimsRequest claims request of the authentication request.
+ *
+ * @return the builder
+ *
+ * @since 3.1.0
+ */
+ public Builder<T> setClaimsRequest(@Nullable final OIDCClaimsRequest claimsRequest) {
+ reqClaims = claimsRequest;
+ return this;
+ }
+
/**
* Set token delivery claims delivered both for id token and userinfo response.
*
@@ -999,7 +1022,44 @@ public class TokenClaimsSet {
codeChallenge = challenge;
return this;
}
+
+ /**
+ * Add a custom claim.
+ *
+ * <p>This method does NOT check for overlap with existing claim names.</p>
+ *
+ * @param name claim name
+ * @param value claim value
+ *
+ * @return this builder
+ *
+ * @since 3.1.0
+ */
+ public Builder<T> addCustomClaim(@Nonnull @NotEmpty final String name, @Nullable final Object value) {
+ customClaims.put(name, value);
+ return this;
+ }
+ /**
+ * Sets a batch of custom claim from a {@link JSONObject}.
+ *
+ * <p>This method does NOT check for overlap with existing claim names.</p>
+ *
+ * @param claims the claims
+ *
+ * @return this builder
+ *
+ * @since 3.1.0
+ */
+ public Builder<T> setCustomClaims(@Nonnull final JSONObject claims) {
+ claims.forEach((n,v) -> {
+ if (n != null) {
+ customClaims.put(n, v);
+ }
+ });
+ return this;
+ }
+
/**
* Builds claims set.
*
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
new file mode 100644
index 00000000..fa170cb6
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
@@ -0,0 +1,447 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.AccessTokenContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet.Builder;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.oidc.profile.config.navigate.AccessTokenLifetimeLookupFunction;
+import net.shibboleth.oidc.profile.config.navigate.AccessTokenTypeLookupFunction;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.security.DataSealer;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy;
+import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy;
+
+/**
+ * Action that creates an Access Token, and stores it to an {@link AccessTokenContext}.
+ *
+ * <p>This supports arbitrary ("generic") OAuth access tokens requested by the "client_credentials" grant type.
+ * This is a "pure" OAuth use case and does not involve any OIDC behavior.</p>
+ *
+ * <p>The action supports either fully opaque access tokens sealed under the IdP's secret key, or
+ * the RFC 9068 standard for JWT-based tokens.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#MESSAGE_PROC_ERROR}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link IdPEventIds#INVALID_ATTRIBUTE_CTX}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ * @event {@link IdPEventIds#INVALID_SUBJECT_CTX}
+ *
+ * @since 3.1.0
+ */
+public class BuildAccessToken extends AbstractOIDCResponseAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(BuildAccessToken.class);
+
+ /** Sealer to use for opaque tokens. */
+ @Nullable private DataSealer dataSealer;
+
+ /** Strategy used to obtain the response issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Strategy used to obtain the access token type to issue. */
+ @Nonnull private Function<ProfileRequestContext,String> accessTokenTypeLookupStrategy;
+
+ /** Strategy used to obtain the access token lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> accessTokenLifetimeLookupStrategy;
+
+ /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
+ @Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+
+ /** Strategy used to create the subcontext to hold the token. */
+ @Nonnull private Function<ProfileRequestContext,AccessTokenContext> accessTokenContextCreationStrategy;
+
+ /** Strategy used to locate the attribute source for scope/audience. */
+ @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
+
+ /** Source scope/audience from unfiltered attributes. */
+ private boolean useUnfilteredAttributes;
+
+ /** ID of attribute to populate scope from. */
+ @Nullable @NotEmpty private String scopeAttribute;
+
+ /** ID of attribute to populate audience from. */
+ @Nullable @NotEmpty private String audienceAttribute;
+
+ /** Use a JWT for the token. */
+ private boolean jwtTokenType;
+
+ /** Access token context. */
+ @Nullable private AccessTokenContext accessTokenCtx;
+
+ /** Attribute context. */
+ @Nullable private AttributeContext attributeCtx;
+
+ /** Subject context. */
+ @Nullable private SubjectContext subjectCtx;
+
+ /** The generator to use. */
+ @Nullable private IdentifierGenerationStrategy idGenerator;
+
+ /** Token request. */
+ @Nullable private TokenRequest tokenRequest;
+
+ /** Constructor. */
+ public BuildAccessToken() {
+ accessTokenTypeLookupStrategy = new AccessTokenTypeLookupFunction();
+ accessTokenLifetimeLookupStrategy = new AccessTokenLifetimeLookupFunction();
+ issuerLookupStrategy = new ResponderIdLookupFunction();
+ idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+
+ // PRC -> inbound message context -> OIDC response context -> ATC
+ accessTokenContextCreationStrategy = new ChildContextLookup<>(AccessTokenContext.class, true).compose(
+ new ChildContextLookup<>(OIDCAuthenticationResponseContext.class).compose(
+ new InboundMessageContextLookup()));
+
+ // PRC -> RPC -> AC
+ attributeContextLookupStrategy = new ChildContextLookup<>(AttributeContext.class).compose(
+ new ChildContextLookup<>(RelyingPartyContext.class));
+
+ useUnfilteredAttributes = true;
+ scopeAttribute = "scope";
+ audienceAttribute = "audience";
+ }
+
+ /**
+ * Set {@lik DataSealer} to use for opaque tokens.
+ *
+ * @param sealer sealer to use for opaque tokens
+ */
+ public void setDataSealer(@Nullable final DataSealer sealer) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ dataSealer = sealer;
+ }
+
+ /**
+ * Set the strategy used to obtain the access token type.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAccessTokenTypeLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ accessTokenTypeLookupStrategy =
+ Constraint.isNotNull(strategy, "Access token type lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to obtain the access token lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAccessTokenLifetimeLookupStrategy(@Nonnull final Function<ProfileRequestContext,Duration> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ accessTokenLifetimeLookupStrategy =
+ Constraint.isNotNull(strategy, "Access token lifetime lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIdentifierGeneratorLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, IdentifierGenerationStrategy> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ idGeneratorLookupStrategy =
+ Constraint.isNotNull(strategy, "Identifier generation strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to create the {@link AccessTokenContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setAccessTokenContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,AccessTokenContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ accessTokenContextCreationStrategy =
+ Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link AttributeContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAttributeContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ attributeContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the issuer value to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /**
+ * Set whether to source the scope and audience claims from unfiltered attributes.
+ *
+ * <p>Default is true.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setUseUnfilteredAttributes(final boolean flag) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ useUnfilteredAttributes = flag;
+ }
+
+ /**
+ * Set the ID of an {@link IdPAttribute} to source the scope claim.
+ *
+ * <p>If unset or no values can be extracted, the validated scopes from the request are used.</p>
+ *
+ * @param id attribute ID
+ */
+ public void setScopeAttribute(@Nullable @NotEmpty final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ scopeAttribute = StringSupport.trimOrNull(id);
+ }
+
+ /**
+ * Set the ID of an {@link IdPAttribute} to source the audience claim.
+ *
+ * <p>If set, an attribute must be available to pull the values from. If unset, the requested resource
+ * values are used.</p>
+ *
+ * @param id attribute ID
+ */
+ public void setAudienceAttribute(@Nullable @NotEmpty final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ audienceAttribute = StringSupport.trimOrNull(id);
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ if (profileRequestContext.getInboundMessageContext().getMessage() instanceof TokenRequest) {
+ tokenRequest = (TokenRequest) profileRequestContext.getInboundMessageContext().getMessage();
+ } else {
+ log.error("{} No inbound TokenRequest message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ final String tokenType = accessTokenTypeLookupStrategy.apply(profileRequestContext);
+ jwtTokenType = tokenType != null && "JWT".equals(tokenType);
+
+
+ subjectCtx = profileRequestContext.getSubcontext(SubjectContext.class);
+ if (subjectCtx == null) {
+ log.error("{} No SubjectContext located", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_SUBJECT_CTX);
+ return false;
+ }
+
+ idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
+ if (idGenerator == null) {
+ log.error("{} No identifier generation strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ accessTokenCtx = accessTokenContextCreationStrategy.apply(profileRequestContext);
+ if (accessTokenCtx == null) {
+ log.error("{} Unable to create AccessTokenContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ attributeCtx = attributeContextLookupStrategy.apply(profileRequestContext);
+ if (attributeCtx == null && (scopeAttribute != null || audienceAttribute != null)) {
+ log.error("{} Unable to locate AttributeContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_ATTRIBUTE_CTX);
+ return false;
+ }
+
+ final Duration lifetime = accessTokenLifetimeLookupStrategy.apply(profileRequestContext);
+ if (lifetime == null) {
+ log.error("{} No lifetime supplied for access token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return false;
+ }
+ accessTokenCtx.setLifetime(lifetime);
+
+ return true;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final OIDCAuthenticationResponseContext ctx = getOidcResponseContext();
+
+ final Collection<String> audience = getAudience();
+ if (audience == null || audience.isEmpty()) {
+ log.warn("{} Unable to determine audience value, failing request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ }
+ ctx.getAudience().addAll(audience);
+ log.debug("{} Building access token with audience: {}", getLogPrefix(), audience);
+
+ final Scope scope = getScope(ctx.getScope());
+ log.debug("{} Building access token with scope: {}", getLogPrefix(), scope);
+
+ final Instant dateExp = Instant.now().plus(accessTokenCtx.getLifetime());
+
+ final AccessTokenClaimsSet.Builder builder = (Builder) new AccessTokenClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(tokenRequest.getClientID())
+ .setIssuer(issuerLookupStrategy.apply(profileRequestContext))
+ .setSubject(subjectCtx.getPrincipalName())
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(dateExp)
+ .setACR(ctx.getAcr())
+ .setAuthenticationTime(ctx.getAuthTime())
+ .setScope(scope)
+ .setAudience(audience);
+
+ if (ctx.getAccessTokenClaimSet() != null) {
+ builder.setCustomClaims(ctx.getAccessTokenClaimSet().toJSONObject());
+ }
+
+ final AccessTokenClaimsSet claimsSet = builder.build();
+
+ try {
+ if (jwtTokenType) {
+ accessTokenCtx.setJWT(new PlainJWT(claimsSet.getClaimsSet()));
+ log.debug("{} Claims stored to JWT access token: {}", getLogPrefix(), claimsSet.serialize(),
+ accessTokenCtx.getJWT());
+ } else {
+ accessTokenCtx.setOpaque(claimsSet.serialize(dataSealer));
+ log.debug("{} Claims '{}' converted to opaque access token: {}", getLogPrefix(), claimsSet.serialize(),
+ accessTokenCtx.getOpaque());
+ }
+ } catch (final DataSealerException e) {
+ log.error("{} Access Token wrapping failed: {}", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+ }
+ }
+
+ /**
+ * Produce the desired {@link Scope} claim for the access token.
+ *
+ * @param validatedScope requested scope(s) that are valid according to client's metadata
+ *
+ * @return derived {@link Scope} to use or null
+ */
+ @Nullable private Scope getScope(@Nullable final Scope validatedScope) {
+
+ if (scopeAttribute != null) {
+ final IdPAttribute source = (useUnfilteredAttributes ? attributeCtx.getUnfilteredIdPAttributes()
+ : attributeCtx.getIdPAttributes()).get(scopeAttribute);
+ if (source != null) {
+ return Scope.parse(
+ source.getValues().stream()
+ .filter(StringAttributeValue.class::isInstance)
+ .map(StringAttributeValue.class::cast)
+ .map(StringAttributeValue::getValue)
+ .collect(Collectors.toUnmodifiableList()));
+ }
+
+ log.warn("{} No source attribute {} available to produce scope claim", getLogPrefix(), scopeAttribute);
+ }
+
+ log.debug("{} Using originally requested/validated scope", getLogPrefix());
+ return validatedScope;
+ }
+
+ @Nullable @NonnullElements private Collection<String> getAudience() {
+ if (audienceAttribute != null) {
+ final IdPAttribute source = (useUnfilteredAttributes ? attributeCtx.getUnfilteredIdPAttributes()
+ : attributeCtx.getIdPAttributes()).get(audienceAttribute);
+ if (source != null) {
+ return source.getValues().stream()
+ .filter(StringAttributeValue.class::isInstance)
+ .map(StringAttributeValue.class::cast)
+ .map(StringAttributeValue::getValue)
+ .collect(Collectors.toUnmodifiableList());
+ }
+ }
+
+ log.debug("{} Using originally requested resource(s) as audience", getLogPrefix());
+ return tokenRequest.getResources().stream().map(URI::toString).collect(Collectors.toUnmodifiableList());
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/OAuthAttributeResolutionContextDecorator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/OAuthAttributeResolutionContextDecorator.java
new file mode 100644
index 00000000..b031350f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/OAuthAttributeResolutionContextDecorator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.TokenRequest;
+
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.OAuthAttributeResolutionContext;
+
+/**
+ * Adds OAuth-specific details about the resolution request to the tree.
+ *
+ * @since 3.1.0
+ */
+public class OAuthAttributeResolutionContextDecorator implements Consumer<AttributeResolutionContext> {
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(@Nullable final AttributeResolutionContext input) {
+
+ final OAuthAttributeResolutionContext oauthCtx =
+ input.getSubcontext(OAuthAttributeResolutionContext.class, true);
+
+ if (input != null && input.getParent() instanceof ProfileRequestContext) {
+ final MessageContext inbound = ((ProfileRequestContext) input.getParent()).getInboundMessageContext();
+ if (inbound != null && inbound.getMessage() instanceof TokenRequest) {
+ final Collection<URI> resources = ((TokenRequest) inbound.getMessage()).getResources();
+ if (resources != null) {
+ oauthCtx.getResources().addAll(
+ resources.stream().map(URI::toString).collect(Collectors.toUnmodifiableList()));
+ }
+ }
+
+ final MessageContext outbound = ((ProfileRequestContext) input.getParent()).getOutboundMessageContext();
+ if (outbound != null) {
+ final OIDCAuthenticationResponseContext responseCtx =
+ outbound.getSubcontext(OIDCAuthenticationResponseContext.class);
+ if (responseCtx != null && responseCtx.getScope() != null) {
+ oauthCtx.getScope().addAll(responseCtx.getScope().toStringList());
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetAccessTokenToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetAccessTokenToResponseContext.java
new file mode 100644
index 00000000..a18660b8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetAccessTokenToResponseContext.java
@@ -0,0 +1,116 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.AccessTokenContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that creates a Access Token, and sets it to work context
+ * {@link OIDCAuthenticationResponseContext#getAccessToken()} located under
+ * {@link ProfileRequestContext#getOutboundMessageContext()}.
+ */
+public class SetAccessTokenToResponseContext extends AbstractOIDCResponseAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(SetAccessTokenToResponseContext.class);
+
+ /** Strategy used to locate the subcontext with the token. */
+ @Nonnull private Function<ProfileRequestContext,AccessTokenContext> accessTokenContextLookupStrategy;
+
+ /** Token context. */
+ @Nullable private AccessTokenContext tokenCtx;
+
+ /** Constructor. */
+ public SetAccessTokenToResponseContext() {
+ // PRC -> inbound message context -> OIDC response context -> ATC
+ accessTokenContextLookupStrategy = new ChildContextLookup<>(AccessTokenContext.class, true).compose(
+ new ChildContextLookup<>(OIDCAuthenticationResponseContext.class).compose(
+ new InboundMessageContextLookup()));
+ }
+
+ /**
+ * Set the strategy used to create the {@link AccessTokenContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setAccessTokenContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,AccessTokenContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ accessTokenContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ tokenCtx = accessTokenContextLookupStrategy.apply(profileRequestContext);
+ if (tokenCtx == null) {
+ log.error("{} AccessTokenContext is missing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ if (tokenCtx.getJWT() == null && tokenCtx.getOpaque() == null) {
+ log.debug("{} Access token is missing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String token;
+ if (tokenCtx.getJWT() != null) {
+ token = tokenCtx.getJWT().serialize();
+ } else {
+ token = tokenCtx.getOpaque();
+ }
+
+ getOidcResponseContext().setAccessToken(token, tokenCtx.getLifetime(), getOidcResponseContext().getScope());
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SignAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SignAccessToken.java
new file mode 100644
index 00000000..ddbebd8a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SignAccessToken.java
@@ -0,0 +1,129 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.AccessTokenContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractSignJWTAction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that signs {@link AccessTokenContext#getJWT()} and overwrites it with the signed version.
+ *
+ * <p>The action exits gracefully if no signing parameters exist or the JWT does not exist.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#MESSAGE_PROC_ERROR}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ */
+public class SignAccessToken extends AbstractSignJWTAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(SignAccessToken.class);
+
+ /** Strategy used to locate the subcontext with the token. */
+ @Nonnull private Function<ProfileRequestContext,AccessTokenContext> accessTokenContextLookupStrategy;
+
+ /** Token context. */
+ @Nullable private AccessTokenContext tokenCtx;
+
+ /** Source of claims set to sign. */
+ @Nullable private JWTClaimsSet claimsSet;
+
+ /** Constructor. */
+ public SignAccessToken() {
+ // PRC -> inbound message context -> OIDC response context -> ATC
+ accessTokenContextLookupStrategy = new ChildContextLookup<>(AccessTokenContext.class, true).compose(
+ new ChildContextLookup<>(OIDCAuthenticationResponseContext.class).compose(
+ new InboundMessageContextLookup()));
+ }
+
+ /**
+ * Set the strategy used to create the {@link AccessTokenContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setAccessTokenContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,AccessTokenContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ accessTokenContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ tokenCtx = accessTokenContextLookupStrategy.apply(profileRequestContext);
+ if (tokenCtx == null) {
+ log.error("{} AccessTokenContext is missing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ if (tokenCtx.getJWT() == null) {
+ log.debug("{} JWT is absent, nothing to do", getLogPrefix());
+ return false;
+ }
+
+ try {
+ claimsSet = tokenCtx.getJWT().getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.error("{} Access token JWT parsing failed: {}", getLogPrefix(), e.getMessage());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull protected JWTClaimsSet getClaimsSetToSign() {
+ return claimsSet;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void setSignedJWT(@Nonnull final SignedJWT jwt) {
+ tokenCtx.setJWT(jwt);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
index 59585a94..5b325fb3 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSet.java
@@ -63,6 +63,8 @@ import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
import com.nimbusds.openid.connect.sdk.OIDCResponseTypeValue;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
/**
* Action that adds claims to a {@link ClaimsSet}. Claims are formed of resolved attributes having OIDC encoder. Action
@@ -110,9 +112,6 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
/** Claims Set to use. */
@Nullable private ClaimsSet claimsSet;
- /** Whether we are adding claims to ID Token. */
- private boolean targetIDToken;
-
/** Whether we can add claims to IDToken by default i.e. response type is "id_token". */
private boolean addToIDTokenByDefault;
@@ -174,16 +173,6 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
reservedClaimNames = claimNames;
}
- /**
- * Set whether target is id token claims set. If this flag is set addToIDTokenByDefault flag is active.
- *
- * @param flag whether target is id token claims set
- */
- public void setTargetIDToken(final boolean flag) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- targetIDToken = flag;
- }
-
/**
* Set the strategy used to locate the response {@link ClaimsSet} associated with a given
* {@link ProfileRequestContext}.
@@ -282,7 +271,7 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
return false;
}
- if (targetIDToken) {
+ if (claimsSet instanceof IDTokenClaimsSet) {
final Object msg = profileRequestContext.getInboundMessageContext().getMessage();
if (msg instanceof AuthenticationRequest) {
final ResponseType responseType = ((AuthenticationRequest) msg).getResponseType();
@@ -295,7 +284,7 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
alwaysIncludedAttributes = Collections.emptySet();
}
deniedUserInfoAttributes = Collections.emptySet();
- } else {
+ } else if (claimsSet instanceof UserInfo) {
deniedUserInfoAttributes = deniedUserInfoAttributesLookupStrategy.apply(profileRequestContext);
if (deniedUserInfoAttributes == null) {
deniedUserInfoAttributes = Collections.emptySet();
@@ -305,8 +294,7 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
return true;
}
- // Checkstyle: CyclomaticComplexity OFF
-
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -346,7 +334,7 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
for (final JSONObject claim : claims) {
for (final String name : claim.keySet()) {
if (reservedClaimNames != null && reservedClaimNames.contains(name)) {
- log.debug("{} claim has a reserved name {}. Not added to claims set", getLogPrefix(), name);
+ log.debug("{} Claim has a reserved name ({}), not added to claims set", getLogPrefix(), name);
continue;
}
@@ -355,7 +343,7 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
}
}
- log.debug("{} claims set after mapping attributes to claims {}", getLogPrefix(),
+ log.debug("{} Claims set after mapping attributes to claims {}", getLogPrefix(),
claimsSet.toJSONObject().toJSONString());
}
@@ -383,12 +371,12 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
for (final TranscodingRule rule : transcodingRules) {
try {
- if (targetIDToken) {
- if (!addToIDTokenByDefault && !alwaysIncludedAttributes.contains(attribute.getId())) {
- log.debug("{} Attribute {} not targeted for ID Token", getLogPrefix(), attribute.getId());
- continue;
- }
- } else if (deniedUserInfoAttributes.contains(attribute.getId())) {
+ // Check for claims to skip based on token type.
+ if (claimsSet instanceof IDTokenClaimsSet && !addToIDTokenByDefault
+ && !alwaysIncludedAttributes.contains(attribute.getId())) {
+ log.debug("{} Attribute {} not targeted for ID Token", getLogPrefix(), attribute.getId());
+ continue;
+ } else if (claimsSet instanceof UserInfo && deniedUserInfoAttributes.contains(attribute.getId())) {
log.debug("{} Attribute {} not targeted for Userinfo Token", getLogPrefix(), attribute.getId());
continue;
}
@@ -407,7 +395,6 @@ public class AddAttributesToClaimsSet extends AbstractOIDCResponseAction {
}
}
}
-
- // Checkstyle: CyclomaticComplexity ON
+// Checkstyle: CyclomaticComplexity ON
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAccessTokenToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAccessTokenToResponseContext.java
index 672365ad..c4d143fd 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAccessTokenToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAccessTokenToResponseContext.java
@@ -144,7 +144,7 @@ public class SetAccessTokenToResponseContext extends AbstractOIDCResponseAction
}
/**
- * Set the strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext} associated with a given
+ * Set the strategy used to locate the {@link OIDCAuthenticationResponseConsentContext} associated with a given
* {@link ProfileRequestContext}.
*
* @param strategy lookup strategy
@@ -292,7 +292,7 @@ public class SetAccessTokenToResponseContext extends AbstractOIDCResponseAction
.setScope(getOidcResponseContext().getScope())
.setACR(getOidcResponseContext().getAcr())
.setNonce(authenticationRequest.getNonce())
- .setClaims(authenticationRequest.getOIDCClaims())
+ .setClaimsRequest(authenticationRequest.getOIDCClaims())
.setDlClaims(claims)
.setDlClaimsUI(claimsUI)
.setConsentedClaims(consented)
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 51a73a33..34cab869 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -117,7 +117,9 @@
<value>sub</value>
<value>iat</value>
<value>exp</value>
+ <value>nbf</value>
<value>acr</value>
+ <value>amr</value>
<value>auth_time</value>
<value>at_hash</value>
<value>c_hash</value>
@@ -125,6 +127,25 @@
</list>
</property>
</bean>
+
+ <bean id="shibboleth.oidc.DefaultAccessTokenReservedClaimNames" lazy-init="true"
+ class="org.springframework.beans.factory.config.ListFactoryBean">
+ <property name="sourceList">
+ <list>
+ <value>jti</value>
+ <value>aud</value>
+ <value>iss</value>
+ <value>prn</value>
+ <value>sub</value>
+ <value>iat</value>
+ <value>exp</value>
+ <value>nbf</value>
+ <value>acr</value>
+ <value>amr</value>
+ <value>auth_time</value>
+ </list>
+ </property>
+ </bean>
<bean id="shibboleth.oidc.DefaultUserInfoReservedClaimNames" lazy-init="true"
class="org.springframework.beans.factory.config.ListFactoryBean">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
index 7101c04a..09510db0 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract/oidc-abstract-beans.xml
@@ -40,6 +40,7 @@
<bean id="ResolveAttributes" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
c:resolverService-ref="shibboleth.AttributeResolverService"
p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
+ p:resolutionContextDecorator="#{getObject('OAuthResolutionContextDecorator')}"
p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}" />
<bean id="FilterAttributes" class="net.shibboleth.idp.profile.impl.FilterAttributes" scope="prototype"
@@ -237,6 +238,18 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.audit.impl.SubjectTypeAuditExtractor">
</bean>
</entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.AUDIENCE"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ValidatedAudienceLookupFunction" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.SCOPE"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ValidatedAudienceLookupFunction" />
+ </entry>
<entry>
<key>
<util:constant static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.ACR"/>
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 db8a5960..0785ebe9 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
@@ -275,7 +275,6 @@
<bean id="AddAttributeClaimsToIDToken"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
- p:targetIDToken="true"
p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
p:reservedClaimNames="#{getObject('shibboleth.oidc.IDTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultIDTokenReservedClaimNames')}">
<property name="activationCondition">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 15f446c9..d5d44758 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -24,6 +24,9 @@
class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestClientIDLookupFunction" />
<bean id="ResolveAttributesPredicate" class="net.shibboleth.oidc.profile.config.logic.ResolveAttributesPredicate" />
+
+ <bean id="OAuthResolutionContextDecorator"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.OAuthAttributeResolutionContextDecorator" />
<bean id="InitializeOutboundMessageContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundTokenResponseMessageContext"
@@ -32,10 +35,32 @@
<bean id="ValidateGrantType" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantType"
scope="prototype" />
- <bean id="ValidateGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrant" scope="prototype"
- c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
- p:replayCache-ref="shibboleth.ReplayCache"
- p:revocationCache-ref="shibboleth.RevocationCache" />
+ <bean id="PopulateTokenEndpointJwtSignatureValidationParameters"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters "
+ scope="prototype"
+ p:configurationLookupStrategy-ref="shibboleth.oidc.SignatureValidationConfigurationLookup"
+ p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenEndpointJwtSignatureValidationParametersResolver">
+ <property name="securityParametersContextLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookupOrCreate.SecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ <property name="existingParametersContextLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.SecurityParameters"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidc.SignatureValidationConfigurationLookup"
+ class="net.shibboleth.oidc.profile.config.navigate.TokenEndpointJwtSignatureValidationConfigurationLookupFunction"
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+
+ <bean id="shibboleth.oidc.TokenEndpointJwtSignatureValidationParametersResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationSignatureValidationParametersResolver"
+ p:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache"
+ p:keyFetchInterval="%{idp.oidc.jwksuri.fetchInterval:PT30M}"
+ p:parameterType="#{T(net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationSignatureValidationParametersResolver.ParameterType).TOKEN_ENDPOINT_JWT_VALIDATION}" />
<!-- Condition signaling that request was NOT for client_credentials grant. -->
<bean id="NotClientCredentialsGrantCondition" parent="shibboleth.Conditions.NOT">
@@ -49,6 +74,13 @@
<bean id="AuthorizationCodeGrantCondition"
class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.RequestedGrantTypesCondition"
p:grantTypes="T(com.nimbusds.oauth2.sdk.GrantType).AUTHORIZATION_CODE" />
+
+ <!-- Traditional third-party grant handling. -->
+
+ <bean id="ValidateGrant" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrant" scope="prototype"
+ c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+ p:replayCache-ref="shibboleth.ReplayCache"
+ p:revocationCache-ref="shibboleth.RevocationCache" />
<bean id="ValidatePKCE" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidatePKCE" scope="prototype" />
@@ -76,7 +108,7 @@
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationContextClassReferenceFromAuthzCodeToResponseContext"
scope="prototype" />
- <bean id="SetAuthenticationTimeToResponseContext"
+ <bean id="SetAuthenticationTimeFromAuthzCodeToResponseContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationTimeToResponseContext" scope="prototype"
p:authTimeLookupStrategy-ref="shibboleth.TokenRequestAuthTimeLookupFunction" />
@@ -93,17 +125,33 @@
<bean id="InitializeSubjectContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeSubjectContext" scope="prototype" />
- <bean id="SetSubjectToResponseContext"
+ <bean id="SetSubjectFromAuthzCodeToResponseContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetSubjectToResponseContext" scope="prototype" />
+ <!-- client_credentials grant handling. -->
+
+ <bean id="SetAuthenticationContextClassReferenceToResponseContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationContextClassReferenceToResponseContext"
+ scope="prototype" />
+
+ <bean id="SetAuthenticationTimeToResponseContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAuthenticationTimeToResponseContext" scope="prototype" />
+
+ <!-- Common grant handling. -->
+
<bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype"
p:requestedScopesLookupStrategy-ref="shibboleth.TokenRequestScopeLookupStrategy" />
+ <bean id="shibboleth.TokenRequestScopeLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestScopeLookupFunction" />
+
<!--
TODO: May need to eventually conditionalize this and the encryption action following.
With opaque access tokens, this wouldn't be used for the client_credentials grant, but
with JWT access tokens it will be used for ID and access tokens. For now leaving enabled.
+ Encryption has to be redone anyway since the recipient isn't the client but the resource server.
-->
+
<bean id="PopulateTokenSignatureSigningParameters"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters " scope="prototype"
p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
@@ -130,10 +178,9 @@
p:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache"
p:keyFetchInterval="%{idp.oidc.jwksuri.fetchInterval:PT30M}" />
- <bean id="shibboleth.TokenRequestScopeLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestScopeLookupFunction" />
+ <!-- Traditional third-party grant response handling. -->
- <bean id="SetAccessTokenToResponseContext"
+ <bean id="SetOIDCAccessTokenToResponseContext"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SetAccessTokenToResponseContext" scope="prototype"
c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
@@ -146,7 +193,6 @@
<bean id="AddAttributeClaimsToIDToken"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
- p:targetIDToken="true"
p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
p:reservedClaimNames="#{getObject('shibboleth.oidc.IDTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultIDTokenReservedClaimNames')}" />
@@ -163,6 +209,10 @@
<bean id="AddNonceToIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddNonceToIDToken"
scope="prototype" p:requestNonceLookupStrategy-ref="shibboleth.TokenRequestNonceLookupStrategy" />
+ <bean id="shibboleth.TokenRequestNonceLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestNonceLookupFunction"
+ scope="prototype" />
+
<bean id="AddAccessTokenHashToIDToken"
class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAccessTokenHashToIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
@@ -172,9 +222,6 @@
</property>
</bean>
- <bean id="shibboleth.TokenRequestNonceLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestNonceLookupFunction" scope="prototype" />
-
<bean id="SignIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
@@ -198,30 +245,32 @@
</property>
</bean>
- <bean id="PopulateTokenEndpointJwtSignatureValidationParameters"
- class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters " scope="prototype"
- p:configurationLookupStrategy-ref="shibboleth.oidc.SignatureValidationConfigurationLookup"
- p:signatureSigningParametersResolver-ref="shibboleth.oidc.TokenEndpointJwtSignatureValidationParametersResolver">
- <property name="securityParametersContextLookupStrategy">
- <bean parent="shibboleth.Functions.Compose"
- c:g-ref="shibboleth.ChildLookupOrCreate.SecurityParameters"
- c:f-ref="shibboleth.ChildLookup.RelyingParty" />
- </property>
- <property name="existingParametersContextLookupStrategy">
- <bean parent="shibboleth.Functions.Compose"
- c:g-ref="shibboleth.ChildLookup.SecurityParameters"
- c:f-ref="shibboleth.MessageContextLookup.Outbound" />
- </property>
- </bean>
+ <!-- client_credentials grant response actions. -->
- <bean id="shibboleth.oidc.SignatureValidationConfigurationLookup"
- class="net.shibboleth.oidc.profile.config.navigate.TokenEndpointJwtSignatureValidationConfigurationLookupFunction"
- p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+ <bean id="AddAttributeClaimsToAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.AddAttributesToClaimsSet" scope="prototype"
+ p:transcoderRegistry-ref="shibboleth.AttributeRegistryService"
+ p:responseClaimsSetLookupStrategy-ref="shibboleth.AccessTokenClaimsSetLookupStrategy"
+ p:reservedClaimNames="#{getObject('shibboleth.oidc.AccessTokenReservedClaimNames') ?: getObject('shibboleth.oidc.DefaultAccessTokenReservedClaimNames')}" />
+
+ <bean id="shibboleth.AccessTokenClaimsSetLookupStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.AccessTokenClaimsSetLookupFunction" />
+
+ <bean id="BuildAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.BuildAccessToken" scope="prototype"
+ p:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+ p:useUnfilteredAttributes="%{idp.oauth.accessToken.useUnfilteredAttributes:true}"
+ p:scopeAttribute="#{'%{idp.oauth.accessToken.scopeAttribute:scope}'.trim()}"
+ p:audienceAttribute="#{'%{idp.oauth.accessToken.audienceAttribute:audience}'.trim()}" />
+
+ <bean id="SignAccessToken"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SignAccessToken" scope="prototype" />
+
+ <bean id="SetOAuthAccessTokenToResponseContext"
+ class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetAccessTokenToResponseContext"
+ scope="prototype" />
- <bean id="shibboleth.oidc.TokenEndpointJwtSignatureValidationParametersResolver"
- class="net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationSignatureValidationParametersResolver"
- p:remoteJwkSetCache-ref="shibboleth.oidc.RemoteJwkSetCache" p:keyFetchInterval="%{idp.oidc.jwksuri.fetchInterval:PT30M}"
- p:parameterType="#{T(net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationSignatureValidationParametersResolver.ParameterType).TOKEN_ENDPOINT_JWT_VALIDATION}" />
+ <!-- Audit extractors. -->
<bean id="shibboleth.oidc.DefaultPostResponseAuditExtractorsForFlow"
parent="shibboleth.oidc.DefaultPostResponseAuditExtractors"
@@ -230,8 +279,7 @@
<map merge="true">
<entry>
<key>
- <util:constant
- static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.NONCE"/>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.NONCE"/>
</key>
<bean class="net.shibboleth.idp.plugin.oidc.op.audit.impl.IdTokenClaimsAuditExtractor"
c:claim="nonce" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index e8888e9e..5bbb78f1 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -39,7 +39,7 @@
<decision-state id="BranchOnGrantType">
<if test="NotClientCredentialsGrantCondition.test(opensamlProfileRequestContext)"
then="TraditionalGrantProcessing"
- else="CommonProcessing" />
+ else="ClientCredentialsGrantProcessing" />
</decision-state>
<!-- These steps apply to grants that rely on the authorization endpoint to "prime" the token request. -->
@@ -49,11 +49,19 @@
<evaluate expression="ValidateRedirectURI" />
<evaluate expression="SetRequestedClaimsToResponseContext" />
<evaluate expression="SetAuthenticationContextClassReferenceFromAuthzCodeToResponseContext" />
- <evaluate expression="SetAuthenticationTimeToResponseContext" />
+ <evaluate expression="SetAuthenticationTimeFromAuthzCodeToResponseContext" />
<evaluate expression="SetTokenDeliveryAttributesFromTokenToResponseContext" />
<evaluate expression="SetConsentToResponseContext" />
<evaluate expression="InitializeSubjectContext" />
- <evaluate expression="SetSubjectToResponseContext" />
+ <evaluate expression="SetSubjectFromAuthzCodeToResponseContext" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="CommonGrantProcessing" />
+ </action-state>
+
+ <!-- These steps apply to grants that are self-contained on this endpoint. -->
+ <action-state id="ClientCredentialsGrantProcessing">
+ <evaluate expression="SetAuthenticationContextClassReferenceToResponseContext" />
+ <evaluate expression="SetAuthenticationTimeToResponseContext" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="CommonGrantProcessing" />
</action-state>
@@ -82,8 +90,14 @@
<!-- Consent check is not applicable to some grant types but does no harm. -->
- <action-state id="BuildResponse">
- <evaluate expression="SetAccessTokenToResponseContext" />
+ <decision-state id="BuildResponse">
+ <if test="NotClientCredentialsGrantCondition.test(opensamlProfileRequestContext)"
+ then="TraditionalGrantResponse"
+ else="ClientCredentialsGrantResponse" />
+ </decision-state>
+
+ <action-state id="TraditionalGrantResponse">
+ <evaluate expression="SetOIDCAccessTokenToResponseContext" />
<evaluate expression="SetRefreshTokenToResponseContext" />
<evaluate expression="AddIDTokenShell" />
<evaluate expression="AddAttributeClaimsToIDToken" />
@@ -98,6 +112,15 @@
<transition on="proceed" to="BuildResponseMessage" />
</action-state>
+ <action-state id="ClientCredentialsGrantResponse">
+ <evaluate expression="AddAttributeClaimsToAccessToken" />
+ <evaluate expression="BuildAccessToken" />
+ <evaluate expression="SignAccessToken" />
+ <evaluate expression="SetOAuthAccessTokenToResponseContext" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="BuildResponseMessage" />
+ </action-state>
+
<bean-import resource="token-beans.xml" />
</flow>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 3441930a..e9c95406 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -27,12 +27,10 @@
p:revocationCache-ref="shibboleth.RevocationCache" />
<bean id="shibboleth.ClientIDLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction"
- scope="prototype" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction" />
<bean id="shibboleth.UserInfoRequestClientIDLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction"
- scope="prototype" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoRequestClientIDLookupFunction" />
<bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype">
<property name="requestedScopesLookupStrategy">
@@ -88,8 +86,7 @@
p:parameterType="#{T(net.shibboleth.idp.plugin.oidc.op.security.impl.OIDCClientInformationEncryptionParametersResolver.ParameterType).USERINFO_ENCRYPTION}" />
<bean id="shibboleth.TokenRequestRequestedClaimsLookupFunction"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestRequestedClaimsLookupFunction"
- scope="prototype" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestRequestedClaimsLookupFunction" />
<bean id="InitializeSubjectContext" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeSubjectContext"
scope="prototype" />
@@ -111,8 +108,7 @@
p:responseClaimsSetLookupStrategy-ref="shibboleth.UserInfoResponseClaimsSetLookupStrategy" scope="prototype" />
<bean id="shibboleth.UserInfoResponseClaimsSetLookupStrategy"
- class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoResponseClaimsSetLookupFunction"
- scope="prototype" />
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.UserInfoResponseClaimsSetLookupFunction" />
<bean id="SignUserInfoResponse" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignUserInfoResponse"
scope="prototype">
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSetTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSetTest.java
index 633311cf..02fc13da 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSetTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddAttributesToClaimsSetTest.java
@@ -108,7 +108,6 @@ public class AddAttributesToClaimsSetTest extends BaseOIDCResponseActionTest {
registry.initialize();
action = new AddAttributesToClaimsSet();
- action.setTargetIDToken(true);
action.setTranscoderRegistry(new MockReloadableService<>(registry));
action.initialize();
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list