Repository navigation
keep mtls certificate binding on refreshed access tokens #3652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,8 @@ | |
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| import org.apache.cxf.jaxrs.ext.MessageContext; | ||
| import org.apache.cxf.rs.security.jose.common.JoseConstants; | ||
| import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; | ||
| import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; | ||
| import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider; | ||
|
|
@@ -48,6 +50,8 @@ | |
| import static org.junit.Assert.assertNull; | ||
| import static org.junit.Assert.assertTrue; | ||
| import static org.junit.Assert.fail; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| abstract class AbstractOAuthDataProviderTest { | ||
| private static KeyPair keyPair; | ||
|
|
@@ -403,6 +407,26 @@ public void testCrossClientRefreshTokenRejectedWhenRecycleDisabled() { | |
| assertNotNull(getProvider().getAccessToken(atA.getTokenKey())); | ||
| } | ||
|
|
||
| @Test | ||
| public void testRefreshedAccessTokenKeepsCertificateBinding() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
try {
getProvider().setMessageContext(mc);
// ... assertions ...
} finally {
getProvider().setMessageContext(null);
}Also, the client
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each subclass builds a new provider in its |
||
| MessageContext mc = mock(MessageContext.class); | ||
| when(mc.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)).thenReturn("thumbprint"); | ||
| getProvider().setMessageContext(mc); | ||
|
|
||
| Client c = addClient("101", "bob"); | ||
|
|
||
| AccessTokenRegistration atr = new AccessTokenRegistration(); | ||
| atr.setClient(c); | ||
| atr.setApprovedScope(Arrays.asList("a", "refreshToken")); | ||
| atr.setSubject(c.getResourceOwnerSubject()); | ||
|
|
||
| ServerAccessToken at = getProvider().createAccessToken(atr); | ||
| assertEquals("thumbprint", at.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)); | ||
|
|
||
| ServerAccessToken at2 = getProvider().refreshAccessToken(c, at.getRefreshToken(), Collections.emptyList()); | ||
| assertEquals("thumbprint", at2.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)); | ||
| } | ||
|
|
||
| protected Client addClient(String clientId, String userLogin) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test only covers the happy path where the cert thumbprint is present in the message context for both the initial request and the refresh. Consider adding a second case: bind the initial token with a cert, then refresh with a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A refresh without a cert gives an unbound token, which is exactly what |
||
| Client c = new Client(); | ||
| c.setRedirectUris(Collections.singletonList("http://client/redirect")); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doRefreshAccessTokenpassesoldRefreshToken.getExtraProperties()into the lower-leveldoCreateAccessTokencall (so the new token inherits those properties), and then immediately callssetCertificateThumbprintConfirmation. If any subclass or future code ever storesx5t#S256in the refresh token's extra properties, the thumbprint would be silently overwritten only whenmessageContexthas one, and preserved otherwise. This creates an ambiguous write-order. A short inline comment noting thatsetCertificateThumbprintConfirmationintentionally supersedes any thumbprint that may have been copied fromextraPropertieswould clarify the intent.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doCreateNewRefreshTokendoesn't copy the access token's extra properties, so a refresh token never carriesx5t#S256and there's nothing to overwrite. The create path uses the same order too (registration extra properties first, then the thumbprint from the current request), so I left it without a comment.