Skip to content

Commit 7103c50

Browse files
committed
[Prod/Test] Implement /oauth/token/revoke zone path
1 parent 203c920 commit 7103c50

File tree

4 files changed

+55
-23
lines changed

4 files changed

+55
-23
lines changed

docs/path-based-zones-endpoints-without-z-support.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,20 @@ This document lists endpoints that do **not** yet have a dual path mapping for `
142142

143143
## 11. OAuth / token / client admin (API – not yet covered by /z/)
144144

145-
| Endpoint(s) | Controller / Class | Controller has /z/? | Security has /z/*/? | Tests that touch these endpoints |
146-
|------------------------------------------|--------------------|---------------------|----------------------|-----------------------------------|
147-
|`/oauth/confirm_access` | AccessController | No | No ||
148-
|`/oauth/error` | AccessController | No | No ||
149-
| `/oauth/token/revoke/user/{userId}` etc. | TokenRevocationEndpoint | No | No (OauthEndpointSecurityConfiguration /oauth/token/revoke/** has no /z/) ||
150-
|`/check_token` | CheckTokenEndpoint | No | No ||
151-
|`/introspect` | IntrospectEndpoint | No | No ||
152-
|`/oauth/clients/**` | ClientAdminEndpoints, ClientMetadataAdminEndpoints | No | No (ClientAdminSecurityConfiguration has no /z/) ||
153-
| `/identity-providers/**` | IdentityProviderEndpoints | No | No (IdentityZoneSecurityConfiguration has no /z/) ||
154-
| `/identity-zones/**` || No | No | IdentityZoneEndpointsMockMvcTests (already parameterized for zone path in tests) |
155-
| `/Codes/**` | CodeStoreEndpoints | No | No ||
156-
| `/email_verifications`, `/email_changes` | ChangeEmailEndpoints (SCIM) | No | No ||
157-
| `/RateLimitingStatus/**` | RateLimitStatusController | No | No ||
158-
|`/saml/metadata`, `/saml/metadata/` | SamlMetadataEndpoint | No | No (secFilterOpenSamlEndPoints has no /z/) ||
145+
| Endpoint(s) | Controller / Class | Controller has /z/? | Security has /z/*/? | Tests that touch these endpoints |
146+
|--------------------------------------------|--------------------|---------------------|----------------------|-----------------------------------|
147+
|`/oauth/confirm_access` | AccessController | No | No ||
148+
|`/oauth/error` | AccessController | No | No ||
149+
| `/oauth/token/revoke/user/{userId}` etc.| TokenRevocationEndpoint | No | No (OauthEndpointSecurityConfiguration /oauth/token/revoke/** has no /z/) ||
150+
|`/check_token` | CheckTokenEndpoint | No | No ||
151+
|`/introspect` | IntrospectEndpoint | No | No ||
152+
|`/oauth/clients/**` | ClientAdminEndpoints, ClientMetadataAdminEndpoints | No | No (ClientAdminSecurityConfiguration has no /z/) ||
153+
| `/identity-providers/**` | IdentityProviderEndpoints | No | No (IdentityZoneSecurityConfiguration has no /z/) ||
154+
| `/identity-zones/**` || No | No | IdentityZoneEndpointsMockMvcTests (already parameterized for zone path in tests) |
155+
| `/Codes/**` | CodeStoreEndpoints | No | No ||
156+
| `/email_verifications`, `/email_changes` | ChangeEmailEndpoints (SCIM) | No | No ||
157+
| `/RateLimitingStatus/**` | RateLimitStatusController | No | No ||
158+
|`/saml/metadata`, `/saml/metadata/` | SamlMetadataEndpoint | No | No (secFilterOpenSamlEndPoints has no /z/) ||
159159

160160
---
161161

server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public TokenRevocationEndpoint(
6464
this.generator = new RandomValueStringGenerator(8);
6565
}
6666

67-
@RequestMapping("/oauth/token/revoke/user/{userId}")
67+
@RequestMapping({"/oauth/token/revoke/user/{userId}", "/z/{subdomain}/oauth/token/revoke/user/{userId}"})
6868
public ResponseEntity<Void> revokeTokensForUser(@PathVariable String userId) {
6969
logger.debug("Revoking tokens for user: " + userId);
7070
String zoneId = IdentityZoneHolder.get().getId();
@@ -76,7 +76,7 @@ public ResponseEntity<Void> revokeTokensForUser(@PathVariable String userId) {
7676
return new ResponseEntity<>(OK);
7777
}
7878

79-
@RequestMapping("/oauth/token/revoke/user/{userId}/client/{clientId}")
79+
@RequestMapping({"/oauth/token/revoke/user/{userId}/client/{clientId}", "/z/{subdomain}/oauth/token/revoke/user/{userId}/client/{clientId}"})
8080
public ResponseEntity<Void> revokeTokensForUserAndClient(@PathVariable String userId, @PathVariable String clientId) {
8181
String zoneId = IdentityZoneHolder.get().getId();
8282
logger.debug("Revoking tokens for user " + userId + " and client " + clientId);
@@ -89,7 +89,7 @@ public ResponseEntity<Void> revokeTokensForUserAndClient(@PathVariable String us
8989
return new ResponseEntity<>(OK);
9090
}
9191

92-
@RequestMapping("/oauth/token/revoke/client/{clientId}")
92+
@RequestMapping({"/oauth/token/revoke/client/{clientId}", "/z/{subdomain}/oauth/token/revoke/client/{clientId}"})
9393
public ResponseEntity<Void> revokeTokensForClient(@PathVariable String clientId) {
9494
logger.debug("Revoking tokens for client: " + clientId);
9595
String zoneId = IdentityZoneHolder.get().getId();
@@ -102,7 +102,7 @@ public ResponseEntity<Void> revokeTokensForClient(@PathVariable String clientId)
102102
return new ResponseEntity<>(OK);
103103
}
104104

105-
@DeleteMapping(value = "/oauth/token/revoke/{tokenId}")
105+
@DeleteMapping(value = {"/oauth/token/revoke/{tokenId}", "/z/{subdomain}/oauth/token/revoke/{tokenId}"})
106106
public ResponseEntity<Void> revokeTokenById(@PathVariable String tokenId) {
107107
logger.debug("Revoking token with ID:" + tokenId);
108108
String zoneId = IdentityZoneHolder.get().getId();

server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointSecurityConfiguration.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,18 +235,18 @@ private synchronized ClientParametersAuthenticationFilter getClientParameterAuth
235235
@Order(FilterChainOrder.OAUTH_01)
236236
UaaFilterChain tokenRevocationFilter(HttpSecurity http, @Qualifier("self") IsSelfCheck selfCheck) throws Exception {
237237
SecurityFilterChain chain = http
238-
.securityMatcher("/oauth/token/revoke/**")
238+
.securityMatcher("/oauth/token/revoke/**", "/z/*/oauth/token/revoke/**")
239239
.authorizeHttpRequests( auth -> {
240-
auth.requestMatchers("/oauth/token/revoke/client/**").access(anyOf(true).hasScope("tokens.revoke").isUaaAdmin().isZoneAdmin());
241-
auth.requestMatchers("/oauth/token/revoke/user/*/client/**").access(anyOf(true).hasScope("tokens.revoke").isUaaAdmin().isZoneAdmin()
240+
auth.requestMatchers("/oauth/token/revoke/client/**", "/z/*/oauth/token/revoke/client/**").access(anyOf(true).hasScope("tokens.revoke").isUaaAdmin().isZoneAdmin());
241+
auth.requestMatchers("/oauth/token/revoke/user/*/client/**", "/z/*/oauth/token/revoke/user/*/client/**").access(anyOf(true).hasScope("tokens.revoke").isUaaAdmin().isZoneAdmin()
242242
.or(SelfCheckAuthorizationManager.isUserTokenRevocationForSelf(selfCheck, 4))
243243
.or(SelfCheckAuthorizationManager.isClientTokenRevocationForSelf(selfCheck, 6))
244244
);
245-
auth.requestMatchers("/oauth/token/revoke/user/**").access(anyOf(true)
245+
auth.requestMatchers("/oauth/token/revoke/user/**", "/z/*/oauth/token/revoke/user/**").access(anyOf(true)
246246
.hasScope("tokens.revoke").isUaaAdmin()
247247
.or(SelfCheckAuthorizationManager.isUserTokenRevocationForSelf(selfCheck, 4))
248248
);
249-
auth.requestMatchers(HttpMethod.DELETE, "/oauth/token/revoke/**").access(anyOf(true)
249+
auth.requestMatchers(HttpMethod.DELETE, "/oauth/token/revoke/**", "/z/*/oauth/token/revoke/**").access(anyOf(true)
250250
.hasScope("tokens.revoke")
251251
.or(SelfCheckAuthorizationManager.isTokenRevocationForSelf(selfCheck, 3))
252252
);

uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.cloudfoundry.identity.uaa.client.UaaClientDetails;
55
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
66
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
7+
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneResolutionMode;
78
import org.cloudfoundry.identity.uaa.oauth.event.TokenRevocationEvent;
89
import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt;
910
import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper;
@@ -14,8 +15,12 @@
1415
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
1516
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
1617
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Nested;
1719
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.EnumSource;
1822
import org.springframework.dao.EmptyResultDataAccessException;
23+
import org.springframework.http.HttpMethod;
1924
import org.springframework.web.context.support.GenericWebApplicationContext;
2025

2126
import java.util.Collections;
@@ -27,6 +32,7 @@
2732
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken;
2833
import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getUserOAuthAccessToken;
2934
import static org.hamcrest.Matchers.containsString;
35+
import static org.springframework.http.MediaType.APPLICATION_JSON;
3036
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
3137
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3238
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -574,4 +580,30 @@ private ScimUser setUpUser(String username) {
574580
scimUser.setOrigin(OriginKeys.UAA);
575581
return jdbcScimUserProvisioning.createUser(scimUser, "secret", IdentityZoneHolder.get().getId());
576582
}
583+
584+
@Nested
585+
class TokenRevokeZonePathSupport {
586+
587+
@ParameterizedTest
588+
@EnumSource(ZoneResolutionMode.class)
589+
void revoke_client_tokens_via_zone_path(ZoneResolutionMode mode) throws Exception {
590+
String subdomain = "revokezone" + generator.generate().toLowerCase();
591+
String resourceClientId = "revoke-resource-" + generator.generate();
592+
IdentityZone testZone = setupIdentityZone(subdomain);
593+
IdentityZoneHolder.set(testZone);
594+
try {
595+
setupIdentityProvider(OriginKeys.UAA);
596+
setUpClients("admin", "uaa.admin", "uaa.admin", "client_credentials", true, null, null, -1, testZone);
597+
setUpClients(resourceClientId, "openid", "openid", "client_credentials", true, null, null, -1, testZone);
598+
} finally {
599+
IdentityZoneHolder.clear();
600+
}
601+
602+
String zoneAdminToken = MockMvcUtils.getClientCredentialsOAuthAccessToken(mode, mockMvc, "admin", SECRET, null, subdomain, false);
603+
604+
mockMvc.perform(mode.createRequestBuilder(subdomain, HttpMethod.GET, "/oauth/token/revoke/client/" + resourceClientId)
605+
.header("Authorization", "Bearer " + zoneAdminToken))
606+
.andExpect(status().isOk());
607+
}
608+
}
577609
}

0 commit comments

Comments
 (0)