Skip to content
Snippets Groups Projects

Add server side scripting to gRPC API

Open Joscha Schmiedt requested to merge f-sss4grpc into dev
2 files
+ 321
93
Compare changes
  • Side-by-side
  • Inline

Files

@@ -21,13 +21,14 @@
package org.caosdb.server.grpc;
import com.google.protobuf.Timestamp;
import com.ibm.icu.text.Collator;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest;
@@ -41,30 +42,29 @@ import org.caosdb.server.accessControl.OneTimeAuthenticationToken;
import org.caosdb.server.accessControl.SessionToken;
import org.caosdb.server.entity.FileProperties;
import org.caosdb.server.entity.Message;
import org.caosdb.server.scripting.ScriptingPermissions;
import org.caosdb.server.scripting.ServerSideScriptingCaller;
import org.caosdb.server.utils.ServerMessages;
import org.restlet.data.Form;
import org.restlet.data.Parameter;
/**
* Main entry point for the server-side scripting service of the server's GRPC
* API.
* Main entry point for the server-side scripting service of the server's GRPC API.
*
* @author Joscha Schmiedt <joscha@schmiedt.dev>
*/
public class ServerSideScriptingServiceImpl
extends ServerSideScriptingServiceImplBase {
public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceImplBase {
private ServerSideScriptingCaller caller;
private static AtomicLong executionId = new AtomicLong(0);
@Override
public void executeServerSideScript(
ExecuteServerSideScriptRequest request,
io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> responseObserver) {
io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse>
responseObserver) {
try {
AuthInterceptor.bindSubject();
ExecuteServerSideScriptResponse response = executeScript(request);
ExecuteServerSideScriptResponse response = callScript(request);
responseObserver.onNext(response);
responseObserver.onCompleted();
@@ -73,24 +73,27 @@ public class ServerSideScriptingServiceImpl
}
}
public int callScript(List<String> commandLine, Integer timeoutMs,
List<FileProperties> files, Object authToken)
throws Message {
this.caller = new ServerSideScriptingCaller(
commandLine.toArray(new String[commandLine.size()]), timeoutMs, files,
authToken);
return caller.invoke();
private long incrementAndGetExecutionId() {
return executionId.incrementAndGet();
}
/**
* Converts the ExecuteServerSideScriptRequest to a command line argument list.
*
* @param request the request containing the script filename and arguments
* @return the command line argument list
* @throws IllegalArgumentException if the script filename is missing
*/
public static ArrayList<String> request2CommandLine(ExecuteServerSideScriptRequest request)
throws Message {
throws IllegalArgumentException {
if (request.getScriptFilename().length() == 0) {
throw ServerMessages.SERVER_SIDE_SCRIPT_MISSING_CALL;
throw new IllegalArgumentException("Script filename is missing in the request.");
}
ArrayList<String> commandLine = new ArrayList<>(
1 + request.getPositionalArgumentsCount() + request.getNamedArgumentsCount());
ArrayList<String> commandLine =
new ArrayList<>(
1 + request.getPositionalArgumentsCount() + request.getNamedArgumentsCount());
// first item is the script filename
commandLine.add(request.getScriptFilename());
@@ -114,20 +117,20 @@ public class ServerSideScriptingServiceImpl
// TODO: isAnonymous, getUser and generateAuthToken are copy-pasted from
// elsewhere. Consider refactoring.
/////////////////////////////////////////////////////////////////////////////////////////
public boolean isAnonymous() {
private static boolean isAnonymous() {
return AuthenticationUtils.isAnonymous(getUser());
}
public Subject getUser() {
private static Subject getUser() {
Subject ret = SecurityUtils.getSubject();
return ret;
}
private String[] constructCallStringArray(ArrayList<String> commandLine) {
private static String[] constructCallStringArray(ArrayList<String> commandLine) {
return commandLine.toArray(new String[commandLine.size()]);
}
public Object generateAuthToken(String call) {
private static Object generateAuthToken(String call) {
String purpose = "SCRIPTING:EXECUTE:" + call;
Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser());
if (authtoken != null || isAnonymous()) {
@@ -135,53 +138,87 @@ public class ServerSideScriptingServiceImpl
}
return SessionToken.generate(getUser());
}
private static void checkExecutionPermission(Subject user, String call)
throws AuthorizationException {
user.checkPermission(ScriptingPermissions.PERMISSION_EXECUTION(call));
}
/////////////////////////////////////////////////////////////////////////////////////////
private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) throws Message {
public ExecuteServerSideScriptResponse callScript(ExecuteServerSideScriptRequest request)
throws Message {
long startTimeMillis = System.currentTimeMillis();
Timestamp startTime = Timestamp.newBuilder().setSeconds(startTimeMillis / 1000).build();
checkExecutionPermission(getUser(), request.getScriptFilename());
String[] call = constructCallStringArray(request2CommandLine(request));
// construct caller and invoke (using an empty file list for now)
Integer timeoutMs = request.getTimeoutMs() != 0 ? (int) request.getTimeoutMs() : null;
Integer timeoutMs = request.getTimeoutMs() != 0 ? (int) request.getTimeoutMs() : -1;
if (request.getScriptFilesCount() > 0) {
throw new UnsupportedOperationException(
"Uploading files for scripting via gRPC is not supported yet.");
}
// auth token
Object authToken = request.getAuthToken();
if (authToken == null) {
authToken = generateAuthToken(request.getScriptFilename());
}
caller = new ServerSideScriptingCaller(call, timeoutMs, new ArrayList<FileProperties>(), authToken);
// request.getCommandLineList(), request.getTimeoutMs(),
// request.getFilesList(), request.getAuthToken());
// caller.invoke();
// invoke script
caller =
new ServerSideScriptingCaller(call, timeoutMs, new ArrayList<FileProperties>(), authToken);
int returnCode = caller.invoke();
// construct response
String scriptExecutionId = String.valueOf(incrementAndGetExecutionId());
String scriptFilename = request.getScriptFilename();
ServerSideScriptExecutionResult result =
ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_SUCCESS;
// response
String scriptExecutionId = "";
String scriptFilename = "";
ServerSideScriptExecutionResult result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
int returnCode = 1;
String stdout = "";
try {
stdout = caller.getStdOut();
} catch (final IOException e) {
stdout = "Error retreiving stdout";
result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
}
String stderr = "";
Timestamp startTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build();
Timestamp endTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build();
int duration_ms = 0;
final ExecuteServerSideScriptResponse response = ExecuteServerSideScriptResponse.newBuilder()
.setScriptExecutionId(ServerSideScriptExecutionId.newBuilder()
.setScriptExecutionId(scriptExecutionId)
.build())
.setCall(scriptFilename)
.setResult(result)
.setReturnCode(returnCode)
.setStdout(stdout)
.setStderr(stderr)
.setStartTime(startTime)
.setEndTime(endTime)
.setDurationMs(duration_ms)
.build();
try {
stderr = caller.getStdErr();
} catch (final IOException e) {
stderr = "Error retrieving stderr";
result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
}
long endTimeMillis = System.currentTimeMillis();
Timestamp endTime = Timestamp.newBuilder().setSeconds(endTimeMillis / 1000).build();
int duration_ms = (int) (endTimeMillis - startTimeMillis);
final ExecuteServerSideScriptResponse response =
ExecuteServerSideScriptResponse.newBuilder()
.setScriptExecutionId(
ServerSideScriptExecutionId.newBuilder()
.setScriptExecutionId(scriptExecutionId)
.build())
.setCall(scriptFilename)
.setResult(result)
.setReturnCode(returnCode)
.setStdout(stdout)
.setStderr(stderr)
.setStartTime(startTime)
.setEndTime(endTime)
.setDurationMs(duration_ms)
.build();
return response;
}
public static void handleException(StreamObserver<?> responseObserver,
Exception e) {
public static void handleException(StreamObserver<?> responseObserver, Exception e) {
String description = e.getMessage();
if (description == null || description.isBlank()) {
description = "Unknown Error. Please Report!";
@@ -189,26 +226,25 @@ public class ServerSideScriptingServiceImpl
if (e instanceof UnauthorizedException) {
Subject subject = SecurityUtils.getSubject();
if (AuthenticationUtils.isAnonymous(subject)) {
responseObserver.onError(
new StatusException(AuthInterceptor.PLEASE_LOG_IN));
responseObserver.onError(new StatusException(AuthInterceptor.PLEASE_LOG_IN));
return;
} else {
responseObserver.onError(new StatusException(
Status.PERMISSION_DENIED.withCause(e).withDescription(
description)));
responseObserver.onError(
new StatusException(
Status.PERMISSION_DENIED.withCause(e).withDescription(description)));
return;
}
} else if (e == ServerMessages.ROLE_DOES_NOT_EXIST ||
e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
responseObserver.onError(new StatusException(
Status.NOT_FOUND.withDescription(description).withCause(e)));
} else if (e == ServerMessages.ROLE_DOES_NOT_EXIST
|| e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
responseObserver.onError(
new StatusException(Status.NOT_FOUND.withDescription(description).withCause(e)));
return;
}
// TODO: SERVER_SIDE_DOES_NOT_EXIST, SERVER_SIDE_SCRIPT_NOT_EXECUTABLE,
// SERVER_SIDE_SCRIPT_ERROR, SERVER_SIDE_SCRIPT_SETUP_ERROR,
// SERVER_SIDE_SCRIPT_TIMEOUT, SERVER_SIDE_SCRIPT_MISSING_CALL
e.printStackTrace();
responseObserver.onError(new StatusException(
Status.UNKNOWN.withDescription(description).withCause(e)));
responseObserver.onError(
new StatusException(Status.UNKNOWN.withDescription(description).withCause(e)));
}
}
Loading