From c5d6ba4a5b648075660564503dd51c7da69f8d8b Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Fri, 10 Apr 2026 18:08:35 -0400 Subject: [PATCH] Enforce system-app authorization on OAuth provider endpoints The data pipeline StudioService's OAuthHandler exposes PUT/DELETE/GET endpoints for managing OAuth providers and stored credentials. These are cluster-wide resources, but the handler previously performed no authorization check beyond CDAP's baseline authentication, so any authenticated user of the instance could register, modify, delete, or fetch credentials for any OAuth provider. Wire the existing ContextAccessEnforcer from the SystemHttpServiceContext and gate every endpoint on enforceOnParent(SYSTEM_APP_ENTITY, SYSTEM) with the matching StandardPermission (GET for reads, UPDATE for PUT-based mutations, DELETE for the delete endpoint). Only users with system-app administration permissions in the system namespace can now use these endpoints. This mirrors the ContextAccessEnforcer usage already adopted by ConnectionHandler and DraftHandler in the same module. --- .../datapipeline/service/OAuthHandler.java | 24 +++++++++++++++++++ 1 file changed, 24 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..5ccdbc713e8e 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,25 @@ 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(); + } + + /** + * OAuth providers and credentials are a cluster-wide resource managed by the + * data pipeline system app. Gate every endpoint on the caller having the + * matching permission on the system-app parent in the system namespace, so + * only users authorized to administer system apps can list, read, create, + * update or delete OAuth providers and credentials. + */ + private void enforceSystemAppPermission(StandardPermission permission) { + contextAccessEnforcer.enforceOnParent( + EntityType.SYSTEM_APP_ENTITY, NamespaceId.SYSTEM, permission); } @GET @@ -85,6 +103,7 @@ public void getAuthURL(HttpServiceRequest request, HttpServiceResponder responde @QueryParam("redirect_uri") String redirectURI, @QueryParam("redirect_url") String redirectURL) { try { + enforceSystemAppPermission(StandardPermission.GET); OAuthProvider oauthProvider = getProvider(provider); String formatURL = "%s"; @@ -116,6 +135,7 @@ public void putOAuthProvider(HttpServiceRequest request, HttpServiceResponder re @QueryParam("reuse_client_credentials") @DefaultValue("false") Boolean reuseClientCredentials) { try { + enforceSystemAppPermission(StandardPermission.UPDATE); try { PutOAuthProviderRequest putOAuthProviderRequest = GSON.fromJson( StandardCharsets.UTF_8.decode(request.getContent()).toString(), @@ -163,6 +183,7 @@ public void putOAuthProvider(HttpServiceRequest request, HttpServiceResponder re public void deleteOAuthProvider(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("provider") String oauthProvider) { try { + enforceSystemAppPermission(StandardPermission.DELETE); try { oauthStore.deleteProvider(oauthProvider); responder.sendStatus(HttpURLConnection.HTTP_OK); @@ -182,6 +203,7 @@ public void putOAuthCredential(HttpServiceRequest request, HttpServiceResponder @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + enforceSystemAppPermission(StandardPermission.UPDATE); PutOAuthCredentialRequest putOAuthCredentialRequest; try { putOAuthCredentialRequest = GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), @@ -291,6 +313,7 @@ public void getOAuthCredential(HttpServiceRequest request, HttpServiceResponder @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + enforceSystemAppPermission(StandardPermission.GET); OAuthProvider oauthProvider = getProvider(provider); Optional oAuthAccessToken = getAccessToken(provider, credentialId); @@ -371,6 +394,7 @@ public void getOAuthCredentialValidity(HttpServiceRequest request, HttpServiceRe @PathParam("provider") String provider, @PathParam("credential") String credentialId) { try { + enforceSystemAppPermission(StandardPermission.GET); OAuthProvider oauthProvider = getProvider(provider); Optional oAuthAccessToken = getAccessToken(provider, credentialId);