Skip to content

Commit 414bbf9

Browse files
Merge pull request #1010 from data-integrations/CDAP-21188_enable_rbac_wrangler
[cherry-pick][6.11] [CDAP-21188] Enabling RBAC enforcement for dataprep's workspace operations with Feature flag
2 parents e55e929 + f8ec51f commit 414bbf9

File tree

4 files changed

+94
-13
lines changed

4 files changed

+94
-13
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
<aws.sdk.version>1.11.133</aws.sdk.version>
7373
<bigquery.connector.hadoop2.version>0.10.2-hadoop2</bigquery.connector.hadoop2.version>
7474
<bouncycastle.version>1.56</bouncycastle.version>
75-
<cdap.version>6.11.0</cdap.version>
75+
<cdap.version>6.11.1-SNAPSHOT</cdap.version>
7676
<chlorine.version>1.1.5</chlorine.version>
7777
<commons.validator.version>1.6</commons.validator.version>
7878
<commons-io.version>2.5</commons-io.version>

wrangler-proto/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
<artifactId>cdap-etl-api</artifactId>
3737
<version>${cdap.version}</version>
3838
</dependency>
39+
<dependency>
40+
<groupId>io.cdap.cdap</groupId>
41+
<artifactId>cdap-proto</artifactId>
42+
<version>${cdap.version}</version>
43+
</dependency>
3944
<dependency>
4045
<groupId>io.cdap.wrangler</groupId>
4146
<artifactId>wrangler-api</artifactId>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package io.cdap.wrangler.proto.id;
17+
18+
import io.cdap.cdap.proto.id.SystemAppEntityId;
19+
20+
/**
21+
* Uniquely identifies a dataprep.workspace entity
22+
*/
23+
public class WorkspaceEntityId extends SystemAppEntityId {
24+
public WorkspaceEntityId(String namespace, String workspaceId) {
25+
super(namespace, "dataprep", "workspace", workspaceId);
26+
}
27+
}

wrangler-service/src/main/java/io/cdap/wrangler/service/directive/WorkspaceHandler.java

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
import io.cdap.cdap.etl.proto.connection.SampleResponse;
4242
import io.cdap.cdap.features.Feature;
4343
import io.cdap.cdap.internal.io.SchemaTypeAdapter;
44+
import io.cdap.cdap.proto.element.EntityType;
4445
import io.cdap.cdap.proto.id.NamespaceId;
46+
import io.cdap.cdap.proto.security.StandardPermission;
47+
import io.cdap.cdap.security.spi.authorization.ContextAccessEnforcer;
4548
import io.cdap.wrangler.PropertyIds;
4649
import io.cdap.wrangler.RequestExtractor;
4750
import io.cdap.wrangler.api.DirectiveConfig;
@@ -58,6 +61,7 @@
5861
import io.cdap.wrangler.parser.MigrateToV2;
5962
import io.cdap.wrangler.parser.RecipeCompiler;
6063
import io.cdap.wrangler.proto.BadRequestException;
64+
import io.cdap.wrangler.proto.id.WorkspaceEntityId;
6165
import io.cdap.wrangler.proto.recipe.v2.Recipe;
6266
import io.cdap.wrangler.proto.recipe.v2.RecipeId;
6367
import io.cdap.wrangler.proto.workspace.v2.Artifact;
@@ -85,6 +89,8 @@
8589
import io.cdap.wrangler.utils.SchemaConverter;
8690
import io.cdap.wrangler.utils.StructuredToRowTransformer;
8791
import org.apache.commons.lang3.StringEscapeUtils;
92+
import org.slf4j.Logger;
93+
import org.slf4j.LoggerFactory;
8894

8995
import java.net.HttpURLConnection;
9096
import java.nio.charset.StandardCharsets;
@@ -121,6 +127,8 @@ public class WorkspaceHandler extends AbstractDirectiveHandler {
121127
private WorkspaceStore wsStore;
122128
private RecipeStore recipeStore;
123129
private ConnectionDiscoverer discoverer;
130+
private ContextAccessEnforcer contextAccessEnforcer;
131+
private boolean authEnforcementEnabled;
124132

125133
// Injected by CDAP
126134
@SuppressWarnings("unused")
@@ -132,6 +140,8 @@ public void initialize(SystemHttpServiceContext context) throws Exception {
132140
wsStore = new WorkspaceStore(context);
133141
recipeStore = new RecipeStore(context);
134142
discoverer = new ConnectionDiscoverer(context);
143+
contextAccessEnforcer = context.getContextAccessEnforcer();
144+
authEnforcementEnabled = Feature.WRANGLER_WORKSPACE_AUTH_CHECK.isEnabled(context);
135145
}
136146

137147
@POST
@@ -144,6 +154,11 @@ public void createWorkspace(HttpServiceRequest request, HttpServiceResponder res
144154
throw new BadRequestException("Creating workspace in system namespace is currently not supported");
145155
}
146156

157+
WorkspaceId wsId = new WorkspaceId(ns);
158+
if (authEnforcementEnabled) {
159+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
160+
StandardPermission.CREATE);
161+
}
147162
WorkspaceCreationRequest creationRequest =
148163
GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), WorkspaceCreationRequest.class);
149164

@@ -171,7 +186,6 @@ public void createWorkspace(HttpServiceRequest request, HttpServiceResponder res
171186
return new StageSpec(plugin.getSchema(), pluginSpec);
172187
}).collect(Collectors.toSet()), detail.getSupportedSampleTypes(), sampleRequest);
173188

174-
WorkspaceId wsId = new WorkspaceId(ns);
175189
long now = System.currentTimeMillis();
176190
Workspace workspace = Workspace.builder(generateWorkspaceName(wsId, creationRequest.getSampleRequest().getPath()),
177191
wsId.getWorkspaceId())
@@ -190,6 +204,10 @@ public void listWorkspaces(HttpServiceRequest request, HttpServiceResponder resp
190204
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
191205
throw new BadRequestException("Listing workspaces in system namespace is currently not supported");
192206
}
207+
if (authEnforcementEnabled) {
208+
contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, new NamespaceId(ns.getName()),
209+
StandardPermission.LIST);
210+
}
193211
responder.sendString(GSON.toJson(new ServiceResponse<>(wsStore.listWorkspaces(ns))));
194212
});
195213
}
@@ -204,7 +222,12 @@ public void getWorkspace(HttpServiceRequest request, HttpServiceResponder respon
204222
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
205223
throw new BadRequestException("Getting workspace in system namespace is currently not supported");
206224
}
207-
responder.sendString(GSON.toJson(wsStore.getWorkspace(new WorkspaceId(ns, workspaceId))));
225+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
226+
if (authEnforcementEnabled) {
227+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
228+
StandardPermission.GET);
229+
}
230+
responder.sendString(GSON.toJson(wsStore.getWorkspace(wsId)));
208231
});
209232
}
210233

@@ -221,11 +244,14 @@ public void updateWorkspace(HttpServiceRequest request, HttpServiceResponder res
221244
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
222245
throw new BadRequestException("Updating workspace in system namespace is currently not supported");
223246
}
224-
247+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
248+
if (authEnforcementEnabled) {
249+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
250+
StandardPermission.UPDATE);
251+
}
225252
WorkspaceUpdateRequest updateRequest =
226253
GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), WorkspaceUpdateRequest.class);
227254

228-
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
229255
Workspace newWorkspace = Workspace.builder(wsStore.getWorkspace(wsId))
230256
.setDirectives(updateRequest.getDirectives())
231257
.setInsights(updateRequest.getInsights())
@@ -250,6 +276,10 @@ public void resampleWorkspace(HttpServiceRequest request, HttpServiceResponder r
250276
}
251277

252278
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
279+
if (authEnforcementEnabled) {
280+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
281+
StandardPermission.UPDATE);
282+
}
253283
Workspace currentWorkspace = wsStore.getWorkspace(wsId);
254284

255285
String connectionName = currentWorkspace.getSampleSpec() == null ? null :
@@ -294,7 +324,12 @@ public void deleteWorkspace(HttpServiceRequest request, HttpServiceResponder res
294324
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
295325
throw new BadRequestException("Deleting workspace in system namespace is currently not supported");
296326
}
297-
wsStore.deleteWorkspace(new WorkspaceId(ns, workspaceId));
327+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
328+
if (authEnforcementEnabled) {
329+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
330+
StandardPermission.DELETE);
331+
}
332+
wsStore.deleteWorkspace(wsId);
298333
responder.sendStatus(HttpURLConnection.HTTP_OK);
299334
});
300335
}
@@ -312,6 +347,11 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
312347
throw new BadRequestException("Uploading data in system namespace is currently not supported");
313348
}
314349

350+
WorkspaceId id = new WorkspaceId(ns);
351+
if (authEnforcementEnabled) {
352+
contextAccessEnforcer.enforce(new WorkspaceEntityId(id.getNamespace().getName(), id.getWorkspaceId()),
353+
StandardPermission.CREATE);
354+
}
315355
String name = request.getHeader(PropertyIds.FILE_NAME);
316356
if (name == null) {
317357
throw new BadRequestException("Name must be provided in the 'file' header");
@@ -335,7 +375,6 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
335375
sample.add(new Row(COLUMN_NAME, line));
336376
}
337377

338-
WorkspaceId id = new WorkspaceId(ns);
339378
long now = System.currentTimeMillis();
340379
Workspace workspace = Workspace.builder(name, id.getWorkspaceId())
341380
.setCreatedTimeMillis(now).setUpdatedTimeMillis(now).build();
@@ -360,9 +399,12 @@ public void execute(HttpServiceRequest request, HttpServiceResponder responder,
360399
@PathParam("id") String workspaceId) {
361400
respond(responder, namespace, ns -> {
362401
validateNamespace(ns, "Executing directives in system namespace is currently not supported");
363-
364-
DirectiveExecutionResponse response = execute(ns, request, new WorkspaceId(ns, workspaceId),
365-
null);
402+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
403+
if (authEnforcementEnabled) {
404+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
405+
StandardPermission.USE);
406+
}
407+
DirectiveExecutionResponse response = execute(ns, request, wsId, null);
366408
responder.sendJson(response);
367409
});
368410
}
@@ -406,6 +448,10 @@ public void specification(HttpServiceRequest request, HttpServiceResponder respo
406448
composite.reload(namespace);
407449

408450
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
451+
if (authEnforcementEnabled) {
452+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
453+
StandardPermission.GET);
454+
}
409455
WorkspaceDetail detail = wsStore.getWorkspaceDetail(wsId);
410456
List<String> directives = new ArrayList<>(detail.getWorkspace().getDirectives());
411457
UserDirectivesCollector userDirectivesCollector = new UserDirectivesCollector();
@@ -448,12 +494,15 @@ public void applyRecipe(HttpServiceRequest request, HttpServiceResponder respond
448494
@PathParam("recipe-id") String recipeIdString) {
449495
respond(responder, namespace, ns -> {
450496
validateNamespace(ns, "Executing directives in system namespace is currently not supported");
451-
497+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
498+
if (authEnforcementEnabled) {
499+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
500+
StandardPermission.USE);
501+
}
452502
RecipeId recipeId = RecipeId.builder(ns).setRecipeId(recipeIdString).build();
453503
Recipe recipe = recipeStore.getRecipeById(recipeId);
454504

455-
DirectiveExecutionResponse response = execute(ns, request, new WorkspaceId(ns, workspaceId),
456-
recipe.getDirectives());
505+
DirectiveExecutionResponse response = execute(ns, request, wsId, recipe.getDirectives());
457506
responder.sendJson(response);
458507
});
459508
}

0 commit comments

Comments
 (0)