Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright © 2026 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.cdap.wrangler.proto.id;

import io.cdap.cdap.proto.id.SystemAppEntityId;

/**
* Uniquely identifies a dataprep.recipe entity
*/
public class RecipeEntityId extends SystemAppEntityId {
public RecipeEntityId(String namespace, String recipeId) {
super(namespace, "dataprep", "recipe", recipeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@
import io.cdap.cdap.api.service.http.HttpServiceRequest;
import io.cdap.cdap.api.service.http.HttpServiceResponder;
import io.cdap.cdap.api.service.http.SystemHttpServiceContext;
import io.cdap.cdap.features.Feature;
import io.cdap.cdap.internal.io.SchemaTypeAdapter;
import io.cdap.cdap.proto.element.EntityType;
import io.cdap.cdap.proto.id.NamespaceId;
import io.cdap.cdap.proto.id.SystemAppEntityId;
import io.cdap.cdap.proto.security.StandardPermission;
import io.cdap.cdap.security.spi.authorization.ContextAccessEnforcer;
import io.cdap.cdap.spi.data.transaction.TransactionRunners;
import io.cdap.wrangler.proto.id.WorkspaceEntityId;
import io.cdap.wrangler.PropertyIds;
import io.cdap.wrangler.RequestExtractor;
import io.cdap.wrangler.SamplingMethod;
Expand Down Expand Up @@ -115,6 +122,8 @@ public class DirectivesHandler extends AbstractDirectiveHandler {
private static final String DATA_MODEL_MODEL_PROPERTY = "dataModelModel";

private DirectiveRegistry composite;
private ContextAccessEnforcer contextAccessEnforcer;
private boolean authEnforcementEnabled;

@Override
public void initialize(SystemHttpServiceContext context) throws Exception {
Expand All @@ -123,6 +132,8 @@ public void initialize(SystemHttpServiceContext context) throws Exception {
SystemDirectiveRegistry.INSTANCE,
new UserDirectiveRegistry(context)
);
contextAccessEnforcer = context.getContextAccessEnforcer();
authEnforcementEnabled = Feature.WRANGLER_DIRECTIVE_RECIPE_AUTH_CHECK.isEnabled(context);
}

/**
Expand Down Expand Up @@ -168,6 +179,7 @@ public void create(HttpServiceRequest request, HttpServiceResponder responder, @
@PathParam("id") String id, @QueryParam("name") String name,
@QueryParam("scope") @DefaultValue(WorkspaceDataset.DEFAULT_SCOPE) String scope) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.CREATE);
String workspaceName = name == null || name.isEmpty() ? id : name;

Map<String, String> properties = new HashMap<>();
Expand Down Expand Up @@ -211,6 +223,7 @@ public void create(HttpServiceRequest request, HttpServiceResponder responder, @
public void list(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @QueryParam("scope") @DefaultValue("default") String scope) {
respond(request, responder, namespace, ns -> {
enforceNamespaceParent(ns.getName(), StandardPermission.LIST);
List<WorkspaceIdentifier> workspaces = TransactionRunners.run(getContext(), context -> {
WorkspaceDataset ws = WorkspaceDataset.get(context);
return ws.listWorkspaces(ns, scope);
Expand Down Expand Up @@ -239,6 +252,7 @@ public void list(HttpServiceRequest request, HttpServiceResponder responder,
public void delete(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.DELETE);
TransactionRunners.run(getContext(), context -> {
WorkspaceDataset ws = WorkspaceDataset.get(context);
ws.deleteWorkspace(new NamespacedId(ns, id));
Expand Down Expand Up @@ -267,6 +281,7 @@ public void delete(HttpServiceRequest request, HttpServiceResponder responder,
public void deleteGroup(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @QueryParam("group") String group) {
respond(request, responder, namespace, ns -> {
enforceNamespaceParent(ns.getName(), StandardPermission.DELETE);
TransactionRunners.run(getContext(), context -> {
WorkspaceDataset ws = WorkspaceDataset.get(context);
ws.deleteScope(ns, group);
Expand Down Expand Up @@ -310,6 +325,7 @@ public void deleteGroup(HttpServiceRequest request, HttpServiceResponder respond
public void get(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.GET);
Workspace workspace = getWorkspace(new NamespacedId(ns, id));
String name = workspace.getName();
Request workspaceReq = workspace.getRequest();
Expand Down Expand Up @@ -367,6 +383,7 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
throw new BadRequestException("Name must be provided in the 'file' header");
}
NamespacedId id = new NamespacedId(ns, ServiceUtils.generateMD5(name));
enforceWorkspace(ns.getName(), id.getId(), StandardPermission.CREATE);

return TransactionRunners.run(getContext(), context -> {
// if workspace doesn't exist, then we create the workspace before
Expand Down Expand Up @@ -456,6 +473,7 @@ public void upload(HttpServiceRequest request, HttpServiceResponder responder,
public void uploadData(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.USE);
RequestExtractor handler = new RequestExtractor(request);

// Extract charset, if not specified, default it to UTF-8.
Expand Down Expand Up @@ -547,6 +565,7 @@ public void uploadData(HttpServiceRequest request, HttpServiceResponder responde
public void execute(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.USE);
composite.reload(namespace);
try {
RequestExtractor handler = new RequestExtractor(request);
Expand Down Expand Up @@ -598,6 +617,7 @@ public void execute(HttpServiceRequest request, HttpServiceResponder responder,
public void summary(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.GET);
try {
composite.reload(namespace);
RequestExtractor handler = new RequestExtractor(request);
Expand Down Expand Up @@ -628,6 +648,7 @@ public void summary(HttpServiceRequest request, HttpServiceResponder responder,
public void schema(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.USE);
composite.reload(namespace);
RequestExtractor handler = new RequestExtractor(request);
Request user = handler.getContent("UTF-8", Request.class);
Expand Down Expand Up @@ -666,12 +687,12 @@ public void schema(HttpServiceRequest request, HttpServiceResponder responder,
* @param request Handler for incoming request.
* @param responder Responder for data going out.
* @param id Workspace to associate a data model.
*/
@POST
*/ @POST
@Path("contexts/{context}/workspaces/{id}/datamodels")
public void addDataModel(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.UPDATE);
NamespacedId namespacedId = new NamespacedId(ns, id);
Workspace workspace = getWorkspace(namespacedId);
RequestExtractor handler = new RequestExtractor(request);
Expand Down Expand Up @@ -720,6 +741,7 @@ public void addDataModel(HttpServiceRequest request, HttpServiceResponder respon
public void removeDataModel(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.UPDATE);
NamespacedId namespacedId = new NamespacedId(ns, id);
Workspace workspace = getWorkspace(namespacedId);
Map<String, String> properties = new HashMap<>(workspace.getProperties());
Expand Down Expand Up @@ -748,6 +770,7 @@ public void removeDataModel(HttpServiceRequest request, HttpServiceResponder res
public void addModels(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.UPDATE);
NamespacedId namespacedId = new NamespacedId(ns, id);
Workspace workspace = getWorkspace(namespacedId);
RequestExtractor handler = new RequestExtractor(request);
Expand Down Expand Up @@ -805,9 +828,10 @@ public void addModels(HttpServiceRequest request, HttpServiceResponder responder
@Path("contexts/{context}/workspaces/{id}/models/{modelid}")
@TransactionPolicy(value = TransactionControl.EXPLICIT)
public void removeModels(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace, @PathParam("id") String id,
@PathParam("modelid") String modelId) {
@PathParam("context") String namespace, @PathParam("id") String id,
@PathParam("modelid") String modelId) {
respond(request, responder, namespace, ns -> {
enforceWorkspace(ns.getName(), id, StandardPermission.UPDATE);
NamespacedId namespacedId = new NamespacedId(ns, id);
Workspace workspace = getWorkspace(namespacedId);
Map<String, String> properties = new HashMap<>(workspace.getProperties());
Expand Down Expand Up @@ -837,7 +861,10 @@ public void removeModels(HttpServiceRequest request, HttpServiceResponder respon
@Path("info")
@TransactionPolicy(value = TransactionControl.EXPLICIT)
public void capabilities(HttpServiceRequest request, HttpServiceResponder responder) {
respond(request, responder, () -> new ServiceResponse<>(ProjectInfo.getProperties()));
respond(request, responder, () -> {
enforceSystemApp("dataprep", "service", "info", StandardPermission.GET);
return new ServiceResponse<>(ProjectInfo.getProperties());
});
}

/**
Expand Down Expand Up @@ -908,6 +935,7 @@ public void usage(HttpServiceRequest request, HttpServiceResponder responder,
public void artifacts(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace) {
respond(request, responder, namespace, ns -> {
enforceNamespaceParent(ns.getName(), StandardPermission.LIST);
List<DirectiveArtifact> values = new ArrayList<>();
List<ArtifactInfo> artifacts = getContext().listArtifacts(namespace);
for (ArtifactInfo artifact : artifacts) {
Expand Down Expand Up @@ -935,6 +963,7 @@ public void artifacts(HttpServiceRequest request, HttpServiceResponder responder
public void directives(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace) {
respond(request, responder, namespace, ns -> {
enforceNamespaceParent(ns.getName(), StandardPermission.LIST);
List<DirectiveDescriptor> values = new ArrayList<>();
List<ArtifactInfo> artifacts = getContext().listArtifacts(namespace);
for (ArtifactInfo artifact : artifacts) {
Expand Down Expand Up @@ -962,6 +991,7 @@ public void directives(HttpServiceRequest request, HttpServiceResponder responde
public void directivesReload(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("context") String namespace) {
respond(request, responder, namespace, ns -> {
enforceNamespaceParent(ns.getName(), StandardPermission.UPDATE);
composite.reload(namespace);
return new ServiceResponse<Void>("Successfully reloaded all user defined directives.");
});
Expand All @@ -977,7 +1007,10 @@ public void directivesReload(HttpServiceRequest request, HttpServiceResponder re
@Path("charsets")
@TransactionPolicy(value = TransactionControl.EXPLICIT)
public void charsets(HttpServiceRequest request, HttpServiceResponder responder) {
respond(request, responder, () -> new ServiceResponse<>(Charset.availableCharsets().keySet()));
respond(request, responder, () -> {
enforceSystemApp("dataprep", "service", "charsets", StandardPermission.GET);
return new ServiceResponse<>(Charset.availableCharsets().keySet());
});
}

/**
Expand All @@ -991,6 +1024,7 @@ public void charsets(HttpServiceRequest request, HttpServiceResponder responder)
@TransactionPolicy(value = TransactionControl.EXPLICIT)
public void uploadConfig(HttpServiceRequest request, HttpServiceResponder responder) {
respond(request, responder, () -> {
enforceSystemApp("dataprep", "service", "config", StandardPermission.UPDATE);
// Read the request body
RequestExtractor handler = new RequestExtractor(request);
DirectiveConfig config = handler.getContent("UTF-8", DirectiveConfig.class);
Expand All @@ -1012,7 +1046,10 @@ public void uploadConfig(HttpServiceRequest request, HttpServiceResponder respon
@Path("config")
@TransactionPolicy(value = TransactionControl.EXPLICIT)
public void getConfig(HttpServiceRequest request, HttpServiceResponder responder) {
respond(request, responder, () -> new ServiceResponse<>(configStore.getConfig()));
respond(request, responder, () -> {
enforceSystemApp("dataprep", "service", "config", StandardPermission.GET);
return new ServiceResponse<>(configStore.getConfig());
});
}

/**
Expand Down Expand Up @@ -1089,4 +1126,22 @@ private <E extends Exception> List<Row> executeDirectives(NamespacedId id, List<
grammarVisitor);
});
}

private void enforceWorkspace(String namespace, String workspaceId, StandardPermission permission) {
if (authEnforcementEnabled) {
contextAccessEnforcer.enforce(new WorkspaceEntityId(namespace, workspaceId), permission);
}
}

private void enforceNamespaceParent(String namespace, StandardPermission permission) {
if (authEnforcementEnabled) {
contextAccessEnforcer.enforceOnParent(EntityType.SYSTEM_APP_ENTITY, new NamespaceId(namespace), permission);
}
}

private void enforceSystemApp(String appName, String type, String name, StandardPermission permission) {
if (authEnforcementEnabled) {
contextAccessEnforcer.enforce(new SystemAppEntityId(NamespaceId.SYSTEM.getNamespace(), appName, type, name), permission);
}
}
}
Loading
Loading