From 04dfb64335e5a6151b9eb1e823c0f3fc82773ba4 Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Sat, 16 May 2026 21:19:33 +0300 Subject: [PATCH 1/3] fix: add authorization checks to Studio OAuth service endpoints OAuthHandler endpoints that create/read OAuth providers and credentials lacked any permission enforcement, while the adjacent ConnectionHandler enforces ContextAccessEnforcer checks on every endpoint. Add enforceOnParent(SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, ...) to: - putOAuthProvider (CREATE) -- prevents unauthorized provider registration and arbitrary tokenRefreshURL injection - putOAuthCredential (CREATE) -- prevents unauthorized credential storage - getOAuthCredential (GET) -- prevents unauthorized OAuth access token retrieval Without these checks, any authenticated caller who can reach the Studio system-service route can read or exchange stored OAuth credentials for access tokens belonging to other users. --- .../cdap/cdap/datapipeline/service/OAuthHandler.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java index 9f8b064c1c04..753c865b02e4 100644 --- a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java +++ b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java @@ -36,6 +36,10 @@ import io.cdap.cdap.datapipeline.oauth.PutOAuthCredentialRequest; import io.cdap.cdap.datapipeline.oauth.PutOAuthProviderRequest; import io.cdap.cdap.datapipeline.oauth.RefreshTokenResponse; +import io.cdap.cdap.proto.element.EntityType; +import io.cdap.cdap.proto.id.NamespaceId; +import io.cdap.cdap.proto.security.StandardPermission; +import io.cdap.cdap.security.spi.authorization.ContextAccessEnforcer; import io.cdap.common.http.HttpRequest; import io.cdap.common.http.HttpRequests; import io.cdap.common.http.HttpResponse; @@ -71,11 +75,13 @@ public class OAuthHandler extends AbstractSystemHttpServiceHandler { .create(); private OAuthStore oauthStore; + private ContextAccessEnforcer contextAccessEnforcer; @Override public void initialize(SystemHttpServiceContext context) throws Exception { super.initialize(context); this.oauthStore = new OAuthStore(context, context, context.getAdmin()); + this.contextAccessEnforcer = context.getContextAccessEnforcer(); } @GET @@ -116,6 +122,8 @@ public void putOAuthProvider(HttpServiceRequest request, HttpServiceResponder re @QueryParam("reuse_client_credentials") @DefaultValue("false") Boolean reuseClientCredentials) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.CREATE); try { PutOAuthProviderRequest putOAuthProviderRequest = GSON.fromJson( StandardCharsets.UTF_8.decode(request.getContent()).toString(), @@ -182,6 +190,8 @@ public void putOAuthCredential(HttpServiceRequest request, HttpServiceResponder @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.CREATE); PutOAuthCredentialRequest putOAuthCredentialRequest; try { putOAuthCredentialRequest = GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), @@ -291,6 +301,8 @@ public void getOAuthCredential(HttpServiceRequest request, HttpServiceResponder @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.GET); OAuthProvider oauthProvider = getProvider(provider); Optional oAuthAccessToken = getAccessToken(provider, credentialId); From e250ec981ccf96aeb96eb87872f5d8a297b4af0a Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Sat, 16 May 2026 21:29:12 +0300 Subject: [PATCH 2/3] fix: address review feedback on OAuth authorization checks - Change StandardPermission.GET to LIST in getOAuthCredential; GET has isCheckedOnParent()=false so auth backends may skip the check entirely - Add LIST check to getAuthURL (was unprotected) - Add CREATE check to deleteOAuthProvider (was unprotected) - Add LIST check to getOAuthCredentialValidity (was unprotected) All six handler methods now enforce on the parent entity before executing any store or network operation. --- .../io/cdap/cdap/datapipeline/service/OAuthHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java index 753c865b02e4..48d2793f76ca 100644 --- a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java +++ b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java @@ -91,6 +91,8 @@ public void getAuthURL(HttpServiceRequest request, HttpServiceResponder responde @QueryParam("redirect_uri") String redirectURI, @QueryParam("redirect_url") String redirectURL) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.LIST); OAuthProvider oauthProvider = getProvider(provider); String formatURL = "%s"; @@ -171,6 +173,8 @@ public void putOAuthProvider(HttpServiceRequest request, HttpServiceResponder re public void deleteOAuthProvider(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("provider") String oauthProvider) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.CREATE); try { oauthStore.deleteProvider(oauthProvider); responder.sendStatus(HttpURLConnection.HTTP_OK); @@ -302,7 +306,7 @@ public void getOAuthCredential(HttpServiceRequest request, HttpServiceResponder @PathParam("credential") String credentialId) { try { contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, - StandardPermission.GET); + StandardPermission.LIST); OAuthProvider oauthProvider = getProvider(provider); Optional oAuthAccessToken = getAccessToken(provider, credentialId); @@ -383,6 +387,8 @@ public void getOAuthCredentialValidity(HttpServiceRequest request, HttpServiceRe @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, + StandardPermission.LIST); OAuthProvider oauthProvider = getProvider(provider); Optional oAuthAccessToken = getAccessToken(provider, credentialId); From e7d5e5100165c91f8e74933c020d7ec0edb53ae8 Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Sat, 16 May 2026 21:44:03 +0300 Subject: [PATCH 3/3] fix: correct deleteOAuthProvider permission from CREATE to DELETE DELETE operation should enforce DELETE permission, not CREATE. The previous commit incorrectly used StandardPermission.CREATE for a delete endpoint. --- .../java/io/cdap/cdap/datapipeline/service/OAuthHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java index 48d2793f76ca..ce8675f23e3d 100644 --- a/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java +++ b/cdap-app-templates/cdap-etl/cdap-data-pipeline-base/src/main/java/io/cdap/cdap/datapipeline/service/OAuthHandler.java @@ -174,7 +174,7 @@ public void deleteOAuthProvider(HttpServiceRequest request, HttpServiceResponder @PathParam("provider") String oauthProvider) { try { contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, - StandardPermission.CREATE); + StandardPermission.DELETE); try { oauthStore.deleteProvider(oauthProvider); responder.sendStatus(HttpURLConnection.HTTP_OK);