diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACMTCodeDataProvider.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACMTCodeDataProvider.java index 094e64c39a6..9c96938d8c2 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACMTCodeDataProvider.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACMTCodeDataProvider.java @@ -19,12 +19,14 @@ package org.apache.cxf.rs.security.oauth2.grants.code; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityTransaction; import jakarta.persistence.LockModeType; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; +import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken; /** @@ -79,6 +81,7 @@ public void setEntityManager(EntityManager entityManager) { public class JPACMTCodeDataProvider extends JPACodeDataProvider { private static final int DEFAULT_PESSIMISTIC_LOCK_TIMEOUT = 10000; + private static final String JPA_LOCK_TIMEOUT_HINT = "jakarta.persistence.lock.timeout"; private int pessimisticLockTimeout = DEFAULT_PESSIMISTIC_LOCK_TIMEOUT; private boolean useJpaLockForExistingRefreshToken = true; @@ -129,12 +132,29 @@ protected RefreshToken updateExistingRefreshToken(RefreshToken rt, ServerAccessT return super.updateExistingRefreshToken(rt, at); } + @Override + protected ServerAuthorizationCodeGrant removeCodeGrant(String code, EntityManager em, + LockModeType lockModeType) + throws OAuthServiceException { + final Map options = new HashMap<>(); + options.put(JPA_LOCK_TIMEOUT_HINT, pessimisticLockTimeout); + ServerAuthorizationCodeGrant grant = + em.find(ServerAuthorizationCodeGrant.class, code, LockModeType.PESSIMISTIC_WRITE, options); + try { + if (grant != null) { + em.remove(grant); + } + } catch (jakarta.persistence.EntityNotFoundException e) { + } + return grant; + } + protected void lockRefreshTokenForUpdate(final RefreshToken refreshToken) { try { execute(em -> { final Map options; if (pessimisticLockTimeout > 0) { - options = Collections.singletonMap("jakarta.persistence.lock.timeout", pessimisticLockTimeout); + options = Collections.singletonMap(JPA_LOCK_TIMEOUT_HINT, pessimisticLockTimeout); } else { options = Collections.emptyMap(); } diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProvider.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProvider.java index 082cacf2bbc..54188e0b3be 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProvider.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProvider.java @@ -22,6 +22,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.LockModeType; import jakarta.persistence.TypedQuery; import org.apache.cxf.rs.security.oauth2.common.Client; import org.apache.cxf.rs.security.oauth2.common.UserSubject; @@ -96,7 +97,12 @@ public ServerAuthorizationCodeGrant removeCodeGrant(final String code) throws OA } private ServerAuthorizationCodeGrant removeCodeGrant(String code, EntityManager em) throws OAuthServiceException { - ServerAuthorizationCodeGrant grant = em.find(ServerAuthorizationCodeGrant.class, code); + return removeCodeGrant(code, em, LockModeType.PESSIMISTIC_WRITE); + } + + protected ServerAuthorizationCodeGrant removeCodeGrant(String code, EntityManager em, + LockModeType lockModeType) throws OAuthServiceException { + ServerAuthorizationCodeGrant grant = em.find(ServerAuthorizationCodeGrant.class, code, lockModeType); try { if (grant != null) { em.remove(grant); diff --git a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProviderTest.java b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProviderTest.java index b75e78099df..011c4221373 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProviderTest.java +++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/grants/code/JPACodeDataProviderTest.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; public class JPACodeDataProviderTest { @@ -102,6 +103,30 @@ public void testAddGetDeleteCodeGrants() { assertEquals(0, grants.size()); } + @Test + public void testRemoveCodeGrantTwiceReturnsNullOnSecondCall() { + Client c = addClient("222", "alice"); + + AuthorizationCodeRegistration atr = new AuthorizationCodeRegistration(); + atr.setClient(c); + atr.setApprovedScope(Collections.singletonList("a")); + atr.setSubject(c.getResourceOwnerSubject()); + + try { + ServerAuthorizationCodeGrant grant = getProvider().createCodeGrant(atr); + + ServerAuthorizationCodeGrant first = getProvider().removeCodeGrant(grant.getCode()); + assertNotNull(first); + assertEquals(grant.getCode(), first.getCode()); + + // second remove must not return the grant (single-use enforcement) + ServerAuthorizationCodeGrant second = getProvider().removeCodeGrant(grant.getCode()); + assertNull(second); + } finally { + getProvider().removeClient(c.getClientId()); + } + } + @Test public void testResetClient() { Client c = addClient("111", "bob");