[java-idp-oidc] branch main updated: JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
Henri Mikkonen
henri.mikkonen at iki.fi
Fri May 24 14:35:54 UTC 2024
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=0462bc97811919424c3abb96ae396e607c8ae4b5
The following commit(s) were added to refs/heads/main by this push:
new 0462bc97 JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
0462bc97 is described below
commit 0462bc97811919424c3abb96ae396e607c8ae4b5
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 24 17:35:32 2024 +0300
JOIDC-201 - Support for OAuth 2.0 Demonstrating Proof of Possession (DPoP)
https://shibboleth.atlassian.net/browse/JOIDC-201
- Handle invalid DPoP access token event (when token cannot be parsed)
---
.../op/encoding/impl/NimbusResponseEncoder.java | 2 +-
.../op/userinfo/profile/impl/ParseAccessToken.java | 45 ++++++++++++++++------
.../META-INF/net.shibboleth.idp/postconfig.xml | 2 +
.../idp/flows/oidc/userinfo/userinfo-beans.xml | 7 +++-
.../plugin/oidc/op/profile/flow/UserInfoTest.java | 14 +++++--
5 files changed, 54 insertions(+), 16 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
index 0d5bcbdf..1af2b5a8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/encoding/impl/NimbusResponseEncoder.java
@@ -199,11 +199,11 @@ public class NimbusResponseEncoder extends AbstractHttpServletResponseMessageEnc
return;
}
final HTTPResponse resp = ((Response) message).toHTTPResponse();
+ JakartaServletUtils.applyHTTPResponse(resp, response);
for (final String header : response.getHeaderNames()) {
resp.setHeader(header, response.getHeader(header));
}
getProtocolMessageLogger().trace("Outbound response {}", ResponseUtil.toString(resp, objectMapper));
- JakartaServletUtils.applyHTTPResponse(resp, response);
} catch (final IOException e) {
throw new MessageEncodingException("Problem encoding response", e);
}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessToken.java
index 0b6bc1bf..19376af8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ParseAccessToken.java
@@ -17,6 +17,7 @@ package net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -60,7 +61,8 @@ import net.shibboleth.shared.security.DataSealerException;
* lookup to allow for pluggable validation, an overridden OP/issuer name, etc.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link OidcEventIds#INVALID_GRANT}
+ * @event {@link OidcEventIds#INVALID_ACCESS_TOKEN}
+ * @event {@link OidcEventIds#INVALID_DPOP_ACCESS_TOKEN}
*
* @since 3.2.0
*/
@@ -74,10 +76,13 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
/** Source of signing keys. */
@Nullable private CredentialResolver credentialResolver;
-
+
+ /** Predicate for deciding to refer to DPoP in error responses. */
+ @NonnullAfterInit private Predicate<ProfileRequestContext> dpopAccessTokenCondition;
+
/** Copy of signed JWT for non-opaque access tokens. */
@Nullable private SignedJWT signedJWT;
-
+
/**
* Set the data sealer instance to use.
*
@@ -97,7 +102,19 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
ifInitializedThrowUnmodifiabledComponentException();
credentialResolver = resolver;
}
-
+
+ /**
+ * Set the predicate for deciding to refer to DPoP in error responses.
+ *
+ * @param condition predicate to set
+ *
+ * @since 4.2.0
+ */
+ public void setDpopAccessTokenCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ ifInitializedThrowUnmodifiabledComponentException();
+ dpopAccessTokenCondition = Constraint.isNotNull(condition, "Condition cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -106,6 +123,9 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
if (dataSealer == null) {
throw new ComponentInitializationException("DataSealer cannot be null");
}
+ if (dpopAccessTokenCondition == null) {
+ throw new ComponentInitializationException("DPoP access token condition cannot be null");
+ }
}
// Checkstyle: CyclomaticComplexity OFF
@@ -114,17 +134,20 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final String invalidTokenId = dpopAccessTokenCondition.test(profileRequestContext) ?
+ OidcEventIds.INVALID_DPOP_ACCESS_TOKEN : OidcEventIds.INVALID_ACCESS_TOKEN;
final AccessToken token = getUserInfoRequest().getAccessToken();
+
if (token == null) {
log.error("{} Token missing from request", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ ActionSupport.buildEvent(profileRequestContext, invalidTokenId);
return;
}
-
+
final AccessTokenClaimsSet accessTokenClaimsSet = parseAccessToken(token);
if (accessTokenClaimsSet == null) {
log.warn("{} Unable to parse/decode token for validation", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ ActionSupport.buildEvent(profileRequestContext, invalidTokenId);
return;
}
@@ -135,13 +158,13 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
final JOSEObjectType typ = signedJWT.getHeader().getType();
if (typ == null || !"at+jwt".equals(typ.getType())) {
log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ ActionSupport.buildEvent(profileRequestContext, invalidTokenId);
return;
}
if (credentialResolver == null) {
log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ ActionSupport.buildEvent(profileRequestContext, invalidTokenId);
return;
}
@@ -154,12 +177,12 @@ public class ParseAccessToken extends AbstractOIDCUserInfoValidationResponseActi
creds.forEach(credList::add);
} catch (final ResolverException e) {
log.error("{} Failure resolving signing credentials, can't verify JWT signature", getLogPrefix(), e);
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+ ActionSupport.buildEvent(profileRequestContext, invalidTokenId);
return;
}
assert signedJWT != null;
final String errorEventId = JWTSignatureValidationUtil.validateSignatureEx(credList, signedJWT,
- OidcEventIds.INVALID_GRANT);
+ invalidTokenId);
if (errorEventId != null) {
log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), accessTokenClaimsSet.getID());
ActionSupport.buildEvent(profileRequestContext, errorEventId);
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 db5ed8f8..8ad10c2b 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
@@ -678,6 +678,8 @@
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).INVALID_REQUEST}" />
<entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_ACCESS_TOKEN}"
value="#{T(com.nimbusds.oauth2.sdk.token.BearerTokenError).INVALID_TOKEN}" />
+ <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_DPOP_ACCESS_TOKEN}"
+ value="#{T(com.nimbusds.oauth2.sdk.token.DPoPTokenError).INVALID_TOKEN}" />
<entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).REVOCATION_FAILED}"
value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).SERVER_ERROR}" />
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 940b6b7c..e60025e0 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
@@ -38,7 +38,12 @@
<bean id="ParseAccessToken"
class="net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl.ParseAccessToken" scope="prototype"
p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
- p:credentialResolver-ref="RelyingPartyCredentialResolver"/>
+ p:credentialResolver-ref="RelyingPartyCredentialResolver">
+ <property name="dpopAccessTokenCondition">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DPoPAccessTokenInRequestCondition"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ </property>
+ </bean>
<bean id="RelyingPartyCredentialResolver" class="net.shibboleth.profile.relyingparty.RelyingPartyCredentialResolver"
c:_0-ref="shibboleth.RelyingPartyResolverService" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index d1730cce..2e16c2bc 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -47,6 +47,7 @@ import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.BearerTokenError;
import com.nimbusds.oauth2.sdk.token.DPoPAccessToken;
+import com.nimbusds.oauth2.sdk.token.DPoPTokenError;
import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
@@ -101,12 +102,19 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
}
@Test
- public void testUnparseableAccessToken() {
+ public void testUnparseableBearerAccessToken() {
request.addHeader("Authorization", "Bearer mockAccessToken");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
- assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
+ assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
}
-
+
+ @Test
+ public void testUnparseableDPoPAccessToken() {
+ request.addHeader("Authorization", "Bearer mockAccessToken");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, DPoPTokenError.INVALID_TOKEN.getCode());
+ }
+
@Test
public void testFailsUntrustedClient() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
ComponentInitializationException {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list