Skip to content

Commit d39eeb7

Browse files
committed
[CDAP-21188] Enabling RBAC enforcement for dataprep's workspace operations
1 parent 4391282 commit d39eeb7

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

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: 40 additions & 10 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;
@@ -121,6 +125,7 @@ public class WorkspaceHandler extends AbstractDirectiveHandler {
121125
private WorkspaceStore wsStore;
122126
private RecipeStore recipeStore;
123127
private ConnectionDiscoverer discoverer;
128+
private ContextAccessEnforcer contextAccessEnforcer;
124129

125130
// Injected by CDAP
126131
@SuppressWarnings("unused")
@@ -132,6 +137,7 @@ public void initialize(SystemHttpServiceContext context) throws Exception {
132137
wsStore = new WorkspaceStore(context);
133138
recipeStore = new RecipeStore(context);
134139
discoverer = new ConnectionDiscoverer(context);
140+
contextAccessEnforcer = context.getContextAccessEnforcer();
135141
}
136142

137143
@POST
@@ -144,6 +150,10 @@ public void createWorkspace(HttpServiceRequest request, HttpServiceResponder res
144150
throw new BadRequestException("Creating workspace in system namespace is currently not supported");
145151
}
146152

153+
WorkspaceId wsId = new WorkspaceId(ns);
154+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
155+
StandardPermission.CREATE);
156+
147157
WorkspaceCreationRequest creationRequest =
148158
GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), WorkspaceCreationRequest.class);
149159

@@ -171,7 +181,6 @@ public void createWorkspace(HttpServiceRequest request, HttpServiceResponder res
171181
return new StageSpec(plugin.getSchema(), pluginSpec);
172182
}).collect(Collectors.toSet()), detail.getSupportedSampleTypes(), sampleRequest);
173183

174-
WorkspaceId wsId = new WorkspaceId(ns);
175184
long now = System.currentTimeMillis();
176185
Workspace workspace = Workspace.builder(generateWorkspaceName(wsId, creationRequest.getSampleRequest().getPath()),
177186
wsId.getWorkspaceId())
@@ -190,6 +199,8 @@ public void listWorkspaces(HttpServiceRequest request, HttpServiceResponder resp
190199
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
191200
throw new BadRequestException("Listing workspaces in system namespace is currently not supported");
192201
}
202+
contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, new NamespaceId(ns.getName()),
203+
StandardPermission.LIST);
193204
responder.sendString(GSON.toJson(new ServiceResponse<>(wsStore.listWorkspaces(ns))));
194205
});
195206
}
@@ -204,7 +215,10 @@ public void getWorkspace(HttpServiceRequest request, HttpServiceResponder respon
204215
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
205216
throw new BadRequestException("Getting workspace in system namespace is currently not supported");
206217
}
207-
responder.sendString(GSON.toJson(wsStore.getWorkspace(new WorkspaceId(ns, workspaceId))));
218+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
219+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
220+
StandardPermission.GET);
221+
responder.sendString(GSON.toJson(wsStore.getWorkspace(wsId)));
208222
});
209223
}
210224

@@ -221,11 +235,13 @@ public void updateWorkspace(HttpServiceRequest request, HttpServiceResponder res
221235
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
222236
throw new BadRequestException("Updating workspace in system namespace is currently not supported");
223237
}
238+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
239+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
240+
StandardPermission.UPDATE);
224241

225242
WorkspaceUpdateRequest updateRequest =
226243
GSON.fromJson(StandardCharsets.UTF_8.decode(request.getContent()).toString(), WorkspaceUpdateRequest.class);
227244

228-
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
229245
Workspace newWorkspace = Workspace.builder(wsStore.getWorkspace(wsId))
230246
.setDirectives(updateRequest.getDirectives())
231247
.setInsights(updateRequest.getInsights())
@@ -250,6 +266,9 @@ public void resampleWorkspace(HttpServiceRequest request, HttpServiceResponder r
250266
}
251267

252268
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
269+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
270+
StandardPermission.UPDATE);
271+
253272
Workspace currentWorkspace = wsStore.getWorkspace(wsId);
254273

255274
String connectionName = currentWorkspace.getSampleSpec() == null ? null :
@@ -294,7 +313,10 @@ public void deleteWorkspace(HttpServiceRequest request, HttpServiceResponder res
294313
if (ns.getName().equalsIgnoreCase(NamespaceId.SYSTEM.getNamespace())) {
295314
throw new BadRequestException("Deleting workspace in system namespace is currently not supported");
296315
}
297-
wsStore.deleteWorkspace(new WorkspaceId(ns, workspaceId));
316+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
317+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
318+
StandardPermission.DELETE);
319+
wsStore.deleteWorkspace(wsId);
298320
responder.sendStatus(HttpURLConnection.HTTP_OK);
299321
});
300322
}
@@ -312,6 +334,10 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
312334
throw new BadRequestException("Uploading data in system namespace is currently not supported");
313335
}
314336

337+
WorkspaceId id = new WorkspaceId(ns);
338+
contextAccessEnforcer.enforce(new WorkspaceEntityId(id.getNamespace().getName(), id.getWorkspaceId()),
339+
StandardPermission.CREATE);
340+
315341
String name = request.getHeader(PropertyIds.FILE_NAME);
316342
if (name == null) {
317343
throw new BadRequestException("Name must be provided in the 'file' header");
@@ -335,7 +361,6 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
335361
sample.add(new Row(COLUMN_NAME, line));
336362
}
337363

338-
WorkspaceId id = new WorkspaceId(ns);
339364
long now = System.currentTimeMillis();
340365
Workspace workspace = Workspace.builder(name, id.getWorkspaceId())
341366
.setCreatedTimeMillis(now).setUpdatedTimeMillis(now).build();
@@ -360,9 +385,11 @@ public void execute(HttpServiceRequest request, HttpServiceResponder responder,
360385
@PathParam("id") String workspaceId) {
361386
respond(responder, namespace, ns -> {
362387
validateNamespace(ns, "Executing directives in system namespace is currently not supported");
388+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
389+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
390+
StandardPermission.USE);
363391

364-
DirectiveExecutionResponse response = execute(ns, request, new WorkspaceId(ns, workspaceId),
365-
null);
392+
DirectiveExecutionResponse response = execute(ns, request, wsId, null);
366393
responder.sendJson(response);
367394
});
368395
}
@@ -406,6 +433,8 @@ public void specification(HttpServiceRequest request, HttpServiceResponder respo
406433
composite.reload(namespace);
407434

408435
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
436+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
437+
StandardPermission.GET);
409438
WorkspaceDetail detail = wsStore.getWorkspaceDetail(wsId);
410439
List<String> directives = new ArrayList<>(detail.getWorkspace().getDirectives());
411440
UserDirectivesCollector userDirectivesCollector = new UserDirectivesCollector();
@@ -448,12 +477,13 @@ public void applyRecipe(HttpServiceRequest request, HttpServiceResponder respond
448477
@PathParam("recipe-id") String recipeIdString) {
449478
respond(responder, namespace, ns -> {
450479
validateNamespace(ns, "Executing directives in system namespace is currently not supported");
451-
480+
WorkspaceId wsId = new WorkspaceId(ns, workspaceId);
481+
contextAccessEnforcer.enforce(new WorkspaceEntityId(wsId.getNamespace().getName(), wsId.getWorkspaceId()),
482+
StandardPermission.USE);
452483
RecipeId recipeId = RecipeId.builder(ns).setRecipeId(recipeIdString).build();
453484
Recipe recipe = recipeStore.getRecipeById(recipeId);
454485

455-
DirectiveExecutionResponse response = execute(ns, request, new WorkspaceId(ns, workspaceId),
456-
recipe.getDirectives());
486+
DirectiveExecutionResponse response = execute(ns, request, wsId, recipe.getDirectives());
457487
responder.sendJson(response);
458488
});
459489
}

0 commit comments

Comments
 (0)