From 963e2f4f4dfff74932697c0c49dcea6fbafe88d5 Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Tue, 18 Mar 2025 21:23:31 +0100 Subject: [PATCH 01/10] Update caosdb-proto submodule to include scripting api --- caosdb-proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caosdb-proto b/caosdb-proto index c6405e53..47e1c48c 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit c6405e538c179d2a8af952f85d9e9dc51fbadb92 +Subproject commit 47e1c48cdb9ebfd697b8f4515e17f6c9169441d6 -- GitLab From 344b300ebc4891f086ae0a7e931df83c69aa857e Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Tue, 18 Mar 2025 21:24:39 +0100 Subject: [PATCH 02/10] Add skeleton for ServerSideScriptingServiceImpl --- .../grpc/ServerSideScriptingServiceImpl.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java diff --git a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java new file mode 100644 index 00000000..ea68d8f7 --- /dev/null +++ b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java @@ -0,0 +1,123 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2025 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2025 Joscha Schmiedt <joscha@schmiedt.dev> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.caosdb.server.grpc; + +import com.google.protobuf.Timestamp; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.stub.StreamObserver; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.UnauthorizedException; +import org.apache.shiro.subject.Subject; +import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; +import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse; +import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionId; +import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult; +import org.caosdb.api.scripting.v1alpha1.ServerSideScriptingServiceGrpc.ServerSideScriptingServiceImplBase; +import org.caosdb.server.accessControl.AuthenticationUtils; +import org.caosdb.server.utils.ServerMessages; + +/** + * 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 { + + @Override + public void executeServerSideScript( + ExecuteServerSideScriptRequest request, + io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> + responseObserver) { + // ServerSideScriptingCaller caller = new ServerSideScriptingCaller() + // caller.executeServerSideScript(request, responseObserver); + try { + AuthInterceptor.bindSubject(); + ExecuteServerSideScriptResponse response = executeScript(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + + } catch (final Exception e) { + ServerSideScriptingServiceImpl.handleException(responseObserver, e); + } + } + + private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) { + + String scriptExecutionId = ""; + String scriptFilename = ""; + ServerSideScriptExecutionResult result = + ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE; + int returnCode = 1; + String stdout = ""; + 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()) + .setScriptFilename(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) { + String description = e.getMessage(); + if (description == null || description.isBlank()) { + description = "Unknown Error. Please Report!"; + } + if (e instanceof UnauthorizedException) { + Subject subject = SecurityUtils.getSubject(); + if (AuthenticationUtils.isAnonymous(subject)) { + responseObserver.onError(new StatusException(AuthInterceptor.PLEASE_LOG_IN)); + return; + } else { + 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))); + 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))); + } +} -- GitLab From 7492bfa22f1e128e29616cf5307c755d7ab2eecd Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 19 Mar 2025 16:07:36 +0100 Subject: [PATCH 03/10] FEAT: Register server side grpc service --- src/main/java/org/caosdb/server/grpc/GRPCServer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/caosdb/server/grpc/GRPCServer.java b/src/main/java/org/caosdb/server/grpc/GRPCServer.java index 494ce947..ee2bb036 100644 --- a/src/main/java/org/caosdb/server/grpc/GRPCServer.java +++ b/src/main/java/org/caosdb/server/grpc/GRPCServer.java @@ -141,6 +141,12 @@ public class GRPCServer { ServerInterceptors.intercept( entityTransactionService, loggingInterceptor, authInterceptor)); + final ServerSideScriptingServiceImpl serverSideScriptingService = + new ServerSideScriptingServiceImpl(); + services.add( + ServerInterceptors.intercept( + serverSideScriptingService, loggingInterceptor, authInterceptor)); + return services; } -- GitLab From 704d601d5fb24b94aa7332ea574ad3f80af9bd1e Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 19 Mar 2025 17:26:46 +0100 Subject: [PATCH 04/10] Update caosdb-proto --- caosdb-proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caosdb-proto b/caosdb-proto index 47e1c48c..79549b60 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit 47e1c48cdb9ebfd697b8f4515e17f6c9169441d6 +Subproject commit 79549b60e441c0b2d693ce189d6e189fed331a7c -- GitLab From 67312d1177bf0d50f026945a685a5d8a7d148e80 Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 19 Mar 2025 17:27:20 +0100 Subject: [PATCH 05/10] WIP: Towards construction of a commandline from grpc sss request --- .../grpc/ServerSideScriptingServiceImpl.java | 161 ++++++++++++++---- .../grpc/ServerSideScriptingGrpcTest.java | 59 +++++++ 2 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java diff --git a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java index ea68d8f7..5f08b060 100644 --- a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java @@ -21,34 +21,47 @@ 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.util.ArrayList; +import java.util.List; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse; +import org.caosdb.api.scripting.v1alpha1.NamedArgument; import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionId; import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult; import org.caosdb.api.scripting.v1alpha1.ServerSideScriptingServiceGrpc.ServerSideScriptingServiceImplBase; import org.caosdb.server.accessControl.AuthenticationUtils; +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.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; @Override public void executeServerSideScript( ExecuteServerSideScriptRequest request, - io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> - responseObserver) { - // ServerSideScriptingCaller caller = new ServerSideScriptingCaller() - // caller.executeServerSideScript(request, responseObserver); + io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> responseObserver) { + try { AuthInterceptor.bindSubject(); ExecuteServerSideScriptResponse response = executeScript(request); @@ -60,12 +73,90 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm } } - private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) { + 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(); + } + + public static ArrayList<String> request2CommandLine(ExecuteServerSideScriptRequest request) + throws Message { + + if (request.getScriptFilename().length() == 0) { + throw ServerMessages.SERVER_SIDE_SCRIPT_MISSING_CALL; + } + + ArrayList<String> commandLine = new ArrayList<>( + 1 + request.getPositionalArgumentsCount() + request.getNamedArgumentsCount()); + + // first item is the script filename + commandLine.add(request.getScriptFilename()); + + // add named arguments + for (NamedArgument namedArgument : request.getNamedArgumentsList()) { + String name = namedArgument.getName(); + String value = namedArgument.getValue(); + commandLine.add(String.format("--%s=%s", name, value)); + } + + // add positional arguments + for (String positionalArgument : request.getPositionalArgumentsList()) { + commandLine.add(positionalArgument); + } + + return commandLine; + } + + ///////////////////////////////////////////////////////////////////////////////////////// + // TODO: isAnonymous, getUser and generateAuthToken are copy-pasted from + // elsewhere. Consider refactoring. + ///////////////////////////////////////////////////////////////////////////////////////// + public boolean isAnonymous() { + return AuthenticationUtils.isAnonymous(getUser()); + } + + public Subject getUser() { + Subject ret = SecurityUtils.getSubject(); + return ret; + } + + private String[] constructCallStringArray(ArrayList<String> commandLine) { + return commandLine.toArray(new String[commandLine.size()]); + } + + public Object generateAuthToken(String call) { + String purpose = "SCRIPTING:EXECUTE:" + call; + Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser()); + if (authtoken != null || isAnonymous()) { + return authtoken; + } + return SessionToken.generate(getUser()); + } + ///////////////////////////////////////////////////////////////////////////////////////// + + private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) throws Message { + + 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; + 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(); + + // response String scriptExecutionId = ""; String scriptFilename = ""; - ServerSideScriptExecutionResult result = - ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE; + ServerSideScriptExecutionResult result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE; int returnCode = 1; String stdout = ""; String stderr = ""; @@ -73,25 +164,24 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm Timestamp endTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build(); int duration_ms = 0; - final ExecuteServerSideScriptResponse response = - ExecuteServerSideScriptResponse.newBuilder() - .setScriptExecutionId( - ServerSideScriptExecutionId.newBuilder() - .setScriptExecutionId(scriptExecutionId) - .build()) - .setScriptFilename(scriptFilename) - .setResult(result) - .setReturnCode(returnCode) - .setStdout(stdout) - .setStderr(stderr) - .setStartTime(startTime) - .setEndTime(endTime) - .setDurationMs(duration_ms) - .build(); + 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!"; @@ -99,25 +189,26 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm 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))); } } diff --git a/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java new file mode 100644 index 00000000..7265dbdf --- /dev/null +++ b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2025 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2025 Joscha Schmiedt <joscha@schmiedt.dev> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.caosdb.server.grpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; +import org.caosdb.api.scripting.v1alpha1.NamedArgument; +// Python Test Case +// response = run_server_side_script("my_script.py", +// "pos0", +// "pos1", +// option1="val1", +// option2="val2", +// files={"-Ofile": "test_file.txt"}) +// assert response.stderr is None +// assert response.code == 0 +// assert response.call == ('my_script.py ' +// '--option1=val1 --option2=val2 --file=.upload_files/test_file.txt ' +// 'pos0 pos1') +import org.junit.jupiter.api.Test; + +public class ServerSideScriptingGrpcTest { + @Test + public void testExecuteServerSideScriptRequestToCommandline() { + ExecuteServerSideScriptRequest request = ExecuteServerSideScriptRequest.newBuilder() + .setScriptFilename("my_script.py") + .addPositionalArguments("pos0") + .addPositionalArguments("pos1") + .addNamedArguments(NamedArgument.newBuilder().setName("option1").setValue("val1").build()) + .addNamedArguments(NamedArgument.newBuilder().setName("option2").setValue("val2").build()) + // .putFiles("-Ofile", "test_file.txt") + .build(); + + ArrayList<String> commandline = ServerSideScriptingServiceImpl.request2CommandLine(request); + // assertEquals(commandline, + // "my_script.py --option1=val1 --option2=val2 --file=.upload_files/test_file.txt pos0 pos1"); + // } +} -- GitLab From 1978c38c0a79bfce43aa60ac2887b612993f0f02 Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 19 Mar 2025 22:05:18 +0100 Subject: [PATCH 06/10] FEAT(gRPC): Add SSS support for gRPC Related to issue #362 (https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/362) --- .../grpc/ServerSideScriptingServiceImpl.java | 170 +++++++----- .../grpc/ServerSideScriptingGrpcTest.java | 244 ++++++++++++++++-- 2 files changed, 321 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java index 5f08b060..b42dce48 100644 --- a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java @@ -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))); } } diff --git a/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java index 7265dbdf..7e24f9d4 100644 --- a/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java +++ b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java @@ -22,38 +22,230 @@ package org.caosdb.server.grpc; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; +import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse; import org.caosdb.api.scripting.v1alpha1.NamedArgument; -// Python Test Case -// response = run_server_side_script("my_script.py", -// "pos0", -// "pos1", -// option1="val1", -// option2="val2", -// files={"-Ofile": "test_file.txt"}) -// assert response.stderr is None -// assert response.code == 0 -// assert response.call == ('my_script.py ' -// '--option1=val1 --option2=val2 --file=.upload_files/test_file.txt ' -// 'pos0 pos1') +import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; +import org.caosdb.server.accessControl.AnonymousAuthenticationToken; +import org.caosdb.server.accessControl.CredentialsValidator; +import org.caosdb.server.accessControl.Principal; +import org.caosdb.server.accessControl.Role; +import org.caosdb.server.accessControl.UserSources; +import org.caosdb.server.database.BackendTransaction; +import org.caosdb.server.database.access.Access; +import org.caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; +import org.caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; +import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; +import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl; +import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.database.misc.TransactionBenchmark; +import org.caosdb.server.database.proto.ProtoUser; +import org.caosdb.server.entity.Message; +import org.caosdb.server.permissions.PermissionRule; +import org.caosdb.server.scripting.ScriptingPermissions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class ServerSideScriptingGrpcTest { + + public static class RetrieveRoleMockup implements RetrieveRoleImpl { + + public RetrieveRoleMockup(Access a) {} + + @Override + public Role retrieve(String role) throws TransactionException { + Role ret = new Role(); + ret.name = "anonymous"; + ret.description = "bla"; + return ret; + } + + @Override + public TransactionBenchmark getBenchmark() { + return null; + } + + @Override + public void setTransactionBenchmark(TransactionBenchmark b) {} + } + + public static class RetrievePermissionRules implements RetrievePermissionRulesImpl { + + public RetrievePermissionRules(Access a) {} + + @Override + public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException { + HashSet<PermissionRule> result = new HashSet<>(); + result.add( + new PermissionRule( + true, false, ScriptingPermissions.PERMISSION_EXECUTION("anonymous_ok"))); + return result; + } + + @Override + public TransactionBenchmark getBenchmark() { + return null; + } + + @Override + public void setTransactionBenchmark(TransactionBenchmark b) {} + } + + public static class RetrieveUserMockUp implements RetrieveUserImpl { + + public RetrieveUserMockUp(Access a) {} + + @Override + public ProtoUser execute(Principal principal) throws TransactionException { + return new ProtoUser(); + } + + @Override + public TransactionBenchmark getBenchmark() { + return null; + } + + @Override + public void setTransactionBenchmark(TransactionBenchmark b) {} + } + + public static class RetrievePasswordValidator implements RetrievePasswordValidatorImpl { + + public RetrievePasswordValidator(Access a) {} + + @Override + public CredentialsValidator<String> execute(String name) throws TransactionException { + return new CredentialsValidator<String>() { + @Override + public boolean isValid(String credential) { + return true; + } + }; + } + + @Override + public void setTransactionBenchmark(TransactionBenchmark b) {} + + @Override + public TransactionBenchmark getBenchmark() { + return null; + } + } + + @BeforeAll + public static void setupShiro() throws IOException { + CaosDBServer.initServerProperties(); + CaosDBServer.initShiro(); + + BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRoleMockup.class); + BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class); + BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUserMockUp.class); + BackendTransaction.setImpl( + RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class); + + UserSources.getDefaultRealm(); + } + @Test - public void testExecuteServerSideScriptRequestToCommandline() { - ExecuteServerSideScriptRequest request = ExecuteServerSideScriptRequest.newBuilder() - .setScriptFilename("my_script.py") - .addPositionalArguments("pos0") - .addPositionalArguments("pos1") - .addNamedArguments(NamedArgument.newBuilder().setName("option1").setValue("val1").build()) - .addNamedArguments(NamedArgument.newBuilder().setName("option2").setValue("val2").build()) - // .putFiles("-Ofile", "test_file.txt") - .build(); + public void testAnonymousWithOutPermission() { + Subject user = SecurityUtils.getSubject(); + CaosDBServer.setProperty(ServerProperties.KEY_AUTH_OPTIONAL, "true"); + user.login(AnonymousAuthenticationToken.getInstance()); + + // TODO: implement this test + + // Form form = new Form("call=anonymous_no_permission"); + // Representation entity = form.getWebRepresentation(); + // Request request = new Request(Method.POST, "../test", entity); + // request.setRootRef(new Reference("bla")); + // request.getAttributes().put("SRID", "asdf1234"); + // request.setDate(new Date()); + // request.setHostRef("bla"); + // resource.init(null, request, new Response(null)); + + // resource.handle(); + // assertEquals(Status.CLIENT_ERROR_FORBIDDEN, + // resource.getResponse().getStatus()); + } + + @Test + public void testAnonymousWithPermission() { + Subject user = SecurityUtils.getSubject(); + CaosDBServer.setProperty(ServerProperties.KEY_AUTH_OPTIONAL, "true"); + user.login(AnonymousAuthenticationToken.getInstance()); + + // TODO: implement this test + + // Form form = new Form("call=anonymous_ok"); + // Representation entity = form.getWebRepresentation(); + // Request request = new Request(Method.POST, "../test", entity); + // request.setRootRef(new Reference("bla")); + // request.getAttributes().put("SRID", "asdf1234"); + // request.setDate(new Date()); + // request.setHostRef("bla"); + // resource.init(null, request, new Response(null)); + + // resource.handle(); + // assertEquals(Status.SUCCESS_OK, resource.getResponse().getStatus()); + } + + @Test + public void testExecuteServerSideScriptRequestToCommandline() throws Message { + ExecuteServerSideScriptRequest request = + ExecuteServerSideScriptRequest.newBuilder() + .setScriptFilename("my_script.py") + .addPositionalArguments("pos0") + .addPositionalArguments("pos1") + .addNamedArguments( + NamedArgument.newBuilder().setName("option1").setValue("val1").build()) + .addNamedArguments( + NamedArgument.newBuilder().setName("option2").setValue("val2").build()) + // .putFiles("-Ofile", "test_file.txt") + .build(); ArrayList<String> commandline = ServerSideScriptingServiceImpl.request2CommandLine(request); - // assertEquals(commandline, - // "my_script.py --option1=val1 --option2=val2 --file=.upload_files/test_file.txt pos0 pos1"); - // } + + assertEquals(commandline.get(0), "my_script.py"); + assertEquals(commandline.get(1), "--option1=val1"); + assertEquals(commandline.get(2), "--option2=val2"); + assertEquals(commandline.get(3), "pos0"); + assertEquals(commandline.get(4), "pos1"); + } + + @Test + public void testCallScript() throws Message { + + // TODO: Implement this test + + // ServerSideScriptingServiceImpl service = new ServerSideScriptingServiceImpl(); + // ExecuteServerSideScriptRequest request = + // ExecuteServerSideScriptRequest.newBuilder() + // .setScriptFilename("my_script.py") + // .addPositionalArguments("pos0") + // .addPositionalArguments("pos1") + // .addNamedArguments( + // NamedArgument.newBuilder().setName("option1").setValue("val1").build()) + // .addNamedArguments( + // NamedArgument.newBuilder().setName("option2").setValue("val2").build()) + // .setTimeoutMs(1000) + // .build(); + + // ExecuteServerSideScriptResponse response = service.callScript(request); + + // assertEquals(response.getCall(), "my_script.py"); + // assertEquals( + // response.getResult(), + // ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_SUCCESS); + // assertEquals(response.getStdout(), ""); // Assuming the script does not produce any output + // assertEquals(response.getStderr(), ""); // Assuming the script does not produce any error output + // assertEquals(response.getReturnCode(), 0); // Assuming the script returns 0 + // assertEquals(response.getDurationMs() > 0, true); // Check if duration is greater than 0 + } } -- GitLab From fd0ed36e67de7a00d198b92203b80826e71ef81e Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 26 Mar 2025 17:12:34 +0100 Subject: [PATCH 07/10] Remove attempts for integration tests for SSS in gRPC --- .../grpc/ServerSideScriptingGrpcTest.java | 196 ------------------ 1 file changed, 196 deletions(-) diff --git a/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java index 7e24f9d4..013b2397 100644 --- a/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java +++ b/src/test/java/org/caosdb/server/grpc/ServerSideScriptingGrpcTest.java @@ -22,180 +22,14 @@ package org.caosdb.server.grpc; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; -import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse; import org.caosdb.api.scripting.v1alpha1.NamedArgument; -import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult; -import org.caosdb.server.CaosDBServer; -import org.caosdb.server.ServerProperties; -import org.caosdb.server.accessControl.AnonymousAuthenticationToken; -import org.caosdb.server.accessControl.CredentialsValidator; -import org.caosdb.server.accessControl.Principal; -import org.caosdb.server.accessControl.Role; -import org.caosdb.server.accessControl.UserSources; -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; -import org.caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; -import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; -import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.misc.TransactionBenchmark; -import org.caosdb.server.database.proto.ProtoUser; import org.caosdb.server.entity.Message; -import org.caosdb.server.permissions.PermissionRule; -import org.caosdb.server.scripting.ScriptingPermissions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class ServerSideScriptingGrpcTest { - public static class RetrieveRoleMockup implements RetrieveRoleImpl { - - public RetrieveRoleMockup(Access a) {} - - @Override - public Role retrieve(String role) throws TransactionException { - Role ret = new Role(); - ret.name = "anonymous"; - ret.description = "bla"; - return ret; - } - - @Override - public TransactionBenchmark getBenchmark() { - return null; - } - - @Override - public void setTransactionBenchmark(TransactionBenchmark b) {} - } - - public static class RetrievePermissionRules implements RetrievePermissionRulesImpl { - - public RetrievePermissionRules(Access a) {} - - @Override - public HashSet<PermissionRule> retrievePermissionRule(String role) throws TransactionException { - HashSet<PermissionRule> result = new HashSet<>(); - result.add( - new PermissionRule( - true, false, ScriptingPermissions.PERMISSION_EXECUTION("anonymous_ok"))); - return result; - } - - @Override - public TransactionBenchmark getBenchmark() { - return null; - } - - @Override - public void setTransactionBenchmark(TransactionBenchmark b) {} - } - - public static class RetrieveUserMockUp implements RetrieveUserImpl { - - public RetrieveUserMockUp(Access a) {} - - @Override - public ProtoUser execute(Principal principal) throws TransactionException { - return new ProtoUser(); - } - - @Override - public TransactionBenchmark getBenchmark() { - return null; - } - - @Override - public void setTransactionBenchmark(TransactionBenchmark b) {} - } - - public static class RetrievePasswordValidator implements RetrievePasswordValidatorImpl { - - public RetrievePasswordValidator(Access a) {} - - @Override - public CredentialsValidator<String> execute(String name) throws TransactionException { - return new CredentialsValidator<String>() { - @Override - public boolean isValid(String credential) { - return true; - } - }; - } - - @Override - public void setTransactionBenchmark(TransactionBenchmark b) {} - - @Override - public TransactionBenchmark getBenchmark() { - return null; - } - } - - @BeforeAll - public static void setupShiro() throws IOException { - CaosDBServer.initServerProperties(); - CaosDBServer.initShiro(); - - BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRoleMockup.class); - BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class); - BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUserMockUp.class); - BackendTransaction.setImpl( - RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class); - - UserSources.getDefaultRealm(); - } - - @Test - public void testAnonymousWithOutPermission() { - Subject user = SecurityUtils.getSubject(); - CaosDBServer.setProperty(ServerProperties.KEY_AUTH_OPTIONAL, "true"); - user.login(AnonymousAuthenticationToken.getInstance()); - - // TODO: implement this test - - // Form form = new Form("call=anonymous_no_permission"); - // Representation entity = form.getWebRepresentation(); - // Request request = new Request(Method.POST, "../test", entity); - // request.setRootRef(new Reference("bla")); - // request.getAttributes().put("SRID", "asdf1234"); - // request.setDate(new Date()); - // request.setHostRef("bla"); - // resource.init(null, request, new Response(null)); - - // resource.handle(); - // assertEquals(Status.CLIENT_ERROR_FORBIDDEN, - // resource.getResponse().getStatus()); - } - - @Test - public void testAnonymousWithPermission() { - Subject user = SecurityUtils.getSubject(); - CaosDBServer.setProperty(ServerProperties.KEY_AUTH_OPTIONAL, "true"); - user.login(AnonymousAuthenticationToken.getInstance()); - - // TODO: implement this test - - // Form form = new Form("call=anonymous_ok"); - // Representation entity = form.getWebRepresentation(); - // Request request = new Request(Method.POST, "../test", entity); - // request.setRootRef(new Reference("bla")); - // request.getAttributes().put("SRID", "asdf1234"); - // request.setDate(new Date()); - // request.setHostRef("bla"); - // resource.init(null, request, new Response(null)); - - // resource.handle(); - // assertEquals(Status.SUCCESS_OK, resource.getResponse().getStatus()); - } - @Test public void testExecuteServerSideScriptRequestToCommandline() throws Message { ExecuteServerSideScriptRequest request = @@ -218,34 +52,4 @@ public class ServerSideScriptingGrpcTest { assertEquals(commandline.get(3), "pos0"); assertEquals(commandline.get(4), "pos1"); } - - @Test - public void testCallScript() throws Message { - - // TODO: Implement this test - - // ServerSideScriptingServiceImpl service = new ServerSideScriptingServiceImpl(); - // ExecuteServerSideScriptRequest request = - // ExecuteServerSideScriptRequest.newBuilder() - // .setScriptFilename("my_script.py") - // .addPositionalArguments("pos0") - // .addPositionalArguments("pos1") - // .addNamedArguments( - // NamedArgument.newBuilder().setName("option1").setValue("val1").build()) - // .addNamedArguments( - // NamedArgument.newBuilder().setName("option2").setValue("val2").build()) - // .setTimeoutMs(1000) - // .build(); - - // ExecuteServerSideScriptResponse response = service.callScript(request); - - // assertEquals(response.getCall(), "my_script.py"); - // assertEquals( - // response.getResult(), - // ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_SUCCESS); - // assertEquals(response.getStdout(), ""); // Assuming the script does not produce any output - // assertEquals(response.getStderr(), ""); // Assuming the script does not produce any error output - // assertEquals(response.getReturnCode(), 0); // Assuming the script returns 0 - // assertEquals(response.getDurationMs() > 0, true); // Check if duration is greater than 0 - } } -- GitLab From 46b05977c84aca46559493dc8a2c00a8e2d31c1f Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 26 Mar 2025 17:22:18 +0100 Subject: [PATCH 08/10] Add handling of SSS server messages in gRPC --- .../grpc/ServerSideScriptingServiceImpl.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java index b42dce48..99ed6a29 100644 --- a/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/ServerSideScriptingServiceImpl.java @@ -240,9 +240,43 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm 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 + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_DOES_NOT_EXIST) + { + responseObserver.onError( + new StatusException(Status.NOT_FOUND.withDescription(description).withCause(e))); + return; + } + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_NOT_EXECUTABLE) + { + responseObserver.onError( + new StatusException(Status.PERMISSION_DENIED.withDescription(description).withCause(e))); + return; + } + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_ERROR) + { + responseObserver.onError( + new StatusException(Status.UNKNOWN.withDescription(description).withCause(e))); + return; + } + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_SETUP_ERROR) + { + responseObserver.onError( + new StatusException(Status.UNKNOWN.withDescription(description).withCause(e))); + return; + } + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_TIMEOUT) + { + responseObserver.onError( + new StatusException(Status.DEADLINE_EXCEEDED.withDescription(description).withCause(e))); + return; + } + else if (e == ServerMessages.SERVER_SIDE_SCRIPT_MISSING_CALL) + { + responseObserver.onError( + new StatusException(Status.INVALID_ARGUMENT.withDescription(description).withCause(e))); + return; + } + e.printStackTrace(); responseObserver.onError( new StatusException(Status.UNKNOWN.withDescription(description).withCause(e))); -- GitLab From 8ea3b4aca36b2bc3ba364793ac65b39d160c1505 Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 26 Mar 2025 17:56:01 +0100 Subject: [PATCH 09/10] Update CHANGELOG.md for the resumable file upload API --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e73bf69f..a6711b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- The file transmission API was extended to support resumable file uploads via an LinkAhead + Upload Protocol. The currently planned protocol is the TUS protocol. The resumable upload + API is still in alpha status. + ### Changed +- The file transmission API was split from the entity API into its own proto file (v1alpha). + The entity API version is incremented to v2alpha1. is currently identical to v1 but + lacking the file transmission API). + ### Deprecated ### Removed -- GitLab From ab19753815d5c56f3f2a9e256c1903fb0494e315 Mon Sep 17 00:00:00 2001 From: Joscha Schmiedt <joscha@schmiedt.dev> Date: Wed, 26 Mar 2025 17:57:06 +0100 Subject: [PATCH 10/10] Revert accidental update of CHANGELOG.md --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6711b31..e73bf69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,16 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- The file transmission API was extended to support resumable file uploads via an LinkAhead - Upload Protocol. The currently planned protocol is the TUS protocol. The resumable upload - API is still in alpha status. - ### Changed -- The file transmission API was split from the entity API into its own proto file (v1alpha). - The entity API version is incremented to v2alpha1. is currently identical to v1 but - lacking the file transmission API). - ### Deprecated ### Removed -- GitLab