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