From 97fc72d3762bfb0bb303c1e03ce33261324778c7 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Fri, 10 Dec 2021 00:38:47 +0100
Subject: [PATCH] WIP: acm grpc

---
 .../SinglePermissionSubject.java              | 210 ++++++++++++++++++
 .../server/accessControl/UserSources.java     |   8 +-
 .../AccessControlManagementServiceImpl.java   |  21 +-
 .../caosdb/server/grpc/AuthInterceptor.java   |  46 +++-
 .../grpc/EntityTransactionServiceImpl.java    |   3 +
 .../grpc/FileTransmissionServiceImpl.java     |   3 +
 .../server/grpc/GeneralInfoServiceImpl.java   | 122 +++++++---
 src/main/java/org/caosdb/server/jobs/Job.java |   7 -
 .../jobs/core/CheckDatatypePresent.java       |   9 +-
 .../server/jobs/core/CheckParValid.java       |  21 +-
 .../server/jobs/core/CheckPropValid.java      |  11 +-
 .../server/jobs/core/CheckRefidValid.java     |  11 +-
 .../AbstractCaosDBServerResource.java         |   1 +
 .../caosdb/server/resource/RolesResource.java |   2 -
 .../transaction/RetrieveRoleTransaction.java  |  13 +-
 15 files changed, 400 insertions(+), 88 deletions(-)
 create mode 100644 src/main/java/org/caosdb/server/accessControl/SinglePermissionSubject.java

diff --git a/src/main/java/org/caosdb/server/accessControl/SinglePermissionSubject.java b/src/main/java/org/caosdb/server/accessControl/SinglePermissionSubject.java
new file mode 100644
index 00000000..2c3a3479
--- /dev/null
+++ b/src/main/java/org/caosdb/server/accessControl/SinglePermissionSubject.java
@@ -0,0 +1,210 @@
+package org.caosdb.server.accessControl;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.ExecutionException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+
+public class SinglePermissionSubject implements Subject {
+
+  private WildcardPermission permission;
+
+  public SinglePermissionSubject(String permission) {
+    this.permission = new WildcardPermission(permission);
+  }
+
+  @Override
+  public Object getPrincipal() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public PrincipalCollection getPrincipals() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean isPermitted(String permission) {
+    return this.permission.implies(new WildcardPermission(permission));
+  }
+
+  @Override
+  public boolean isPermitted(Permission permission) {
+    return this.permission.implies(permission);
+  }
+
+  @Override
+  public boolean[] isPermitted(String... permissions) {
+    boolean[] result = new boolean[permissions.length];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = this.permission.implies(new WildcardPermission(permissions[i]));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean[] isPermitted(List<Permission> permissions) {
+    boolean[] result = new boolean[permissions.size()];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = this.permission.implies(permissions.get(i));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean isPermittedAll(String... permissions) {
+    boolean result = true;
+    for (int i = 0; i < permissions.length; i++) {
+      result &= this.permission.implies(new WildcardPermission(permissions[i]));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean isPermittedAll(Collection<Permission> permissions) {
+    Boolean result = true;
+    Iterator<Permission> iterator = permissions.iterator();
+    while (iterator.hasNext()) {
+      result &= this.permission.implies(iterator.next());
+    }
+    return result;
+  }
+
+  @Override
+  public void checkPermission(String permission) throws AuthorizationException {
+    if (!isPermitted(permission)) {
+      throw new AuthenticationException("Not permitted: " + permission);
+    }
+  }
+
+  @Override
+  public void checkPermission(Permission permission) throws AuthorizationException {
+    if (!isPermitted(permission)) {
+      throw new AuthenticationException("Not permitted: " + permission.toString());
+    }
+  }
+
+  @Override
+  public void checkPermissions(String... permissions) throws AuthorizationException {
+    if (!isPermittedAll(permissions)) {
+      throw new AuthenticationException("Not permitted: " + permissions.toString());
+    }
+  }
+
+  @Override
+  public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException {
+    if (!isPermittedAll(permissions)) {
+      throw new AuthenticationException("Not permitted: " + permissions.toString());
+    }
+  }
+
+  @Override
+  public boolean hasRole(String roleIdentifier) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean[] hasRoles(List<String> roleIdentifiers) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean hasAllRoles(Collection<String> roleIdentifiers) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void checkRole(String roleIdentifier) throws AuthorizationException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void checkRoles(String... roleIdentifiers) throws AuthorizationException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void login(AuthenticationToken token) throws AuthenticationException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean isAuthenticated() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean isRemembered() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public Session getSession() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public Session getSession(boolean create) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void logout() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public <V> V execute(Callable<V> callable) throws ExecutionException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void execute(Runnable runnable) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public <V> Callable<V> associateWith(Callable<V> callable) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public Runnable associateWith(Runnable runnable) {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public void runAs(PrincipalCollection principals)
+      throws NullPointerException, IllegalStateException {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public boolean isRunAs() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public PrincipalCollection getPreviousPrincipals() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+
+  @Override
+  public PrincipalCollection releaseRunAs() {
+    throw new UnsupportedOperationException("This method should never be called");
+  }
+}
diff --git a/src/main/java/org/caosdb/server/accessControl/UserSources.java b/src/main/java/org/caosdb/server/accessControl/UserSources.java
index bbbb1bcb..760c3e90 100644
--- a/src/main/java/org/caosdb/server/accessControl/UserSources.java
+++ b/src/main/java/org/caosdb/server/accessControl/UserSources.java
@@ -26,6 +26,7 @@ package org.caosdb.server.accessControl;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
@@ -169,6 +170,10 @@ public class UserSources extends HashMap<String, UserSource> {
    * @return A set of user roles.
    */
   public static Set<String> resolveRoles(String realm, final String username) {
+    if (AnonymousAuthenticationToken.PRINCIPAL.getRealm().equals(realm)
+        && AnonymousAuthenticationToken.PRINCIPAL.getUsername().equals(username)) {
+      return Collections.singleton(Role.ANONYMOUS_ROLE.toString());
+    }
     if (realm == null) {
       realm = guessRealm(username);
     }
@@ -237,7 +242,8 @@ public class UserSources extends HashMap<String, UserSource> {
   }
 
   public static boolean isRoleExisting(final String role) {
-    final RetrieveRoleTransaction t = new RetrieveRoleTransaction(role);
+    final RetrieveRoleTransaction t =
+        new RetrieveRoleTransaction(role, new SinglePermissionSubject("ACM:*:RETRIEVE:*"));
     try {
       t.execute();
       return true;
diff --git a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java
index 18dd29a2..71225820 100644
--- a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java
+++ b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java
@@ -430,6 +430,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void listUsers(
       ListUsersRequest request, StreamObserver<ListUsersResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final ListUsersResponse response = listUsersTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -443,6 +444,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void listRoles(
       ListRolesRequest request, StreamObserver<ListRolesResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final ListRolesResponse response = listRolesTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -456,6 +458,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void createSingleRole(
       CreateSingleRoleRequest request, StreamObserver<CreateSingleRoleResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final CreateSingleRoleResponse response = createSingleRoleTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -470,6 +473,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
       RetrieveSingleRoleRequest request,
       StreamObserver<RetrieveSingleRoleResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final RetrieveSingleRoleResponse response = retrieveSingleRoleTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -483,6 +487,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void createSingleUser(
       CreateSingleUserRequest request, StreamObserver<CreateSingleUserResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final CreateSingleUserResponse response = createSingleUserTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -496,6 +501,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void updateSingleUser(
       UpdateSingleUserRequest request, StreamObserver<UpdateSingleUserResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final UpdateSingleUserResponse response = updateSingleUserTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -510,6 +516,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
       RetrieveSingleUserRequest request,
       StreamObserver<RetrieveSingleUserResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final RetrieveSingleUserResponse response = retrieveSingleUserTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -523,6 +530,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void deleteSingleUser(
       DeleteSingleUserRequest request, StreamObserver<DeleteSingleUserResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final DeleteSingleUserResponse response = deleteSingleUserTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -536,6 +544,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void deleteSingleRole(
       DeleteSingleRoleRequest request, StreamObserver<DeleteSingleRoleResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final DeleteSingleRoleResponse response = deleteSingleRoleTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -549,6 +558,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   public void updateSingleRole(
       UpdateSingleRoleRequest request, StreamObserver<UpdateSingleRoleResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final UpdateSingleRoleResponse response = updateSingleRoleTransaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -563,6 +573,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
       ListKnownPermissionsRequest request,
       StreamObserver<ListKnownPermissionsResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final ListKnownPermissionsResponse response = listKnownPermissions(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -573,6 +584,10 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
   }
 
   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)) {
@@ -582,17 +597,17 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS
       } else {
         responseObserver.onError(
             new StatusException(
-                Status.PERMISSION_DENIED.withCause(e).withDescription(e.getMessage())));
+                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(e.getMessage()).withCause(e)));
+          new StatusException(Status.NOT_FOUND.withDescription(description).withCause(e)));
       return;
     }
     e.printStackTrace();
     responseObserver.onError(
-        new StatusException(Status.UNKNOWN.withDescription(e.getMessage()).withCause(e)));
+        new StatusException(Status.UNKNOWN.withDescription(description).withCause(e)));
   }
 }
diff --git a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java
index b58769d4..5d06f05c 100644
--- a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java
+++ b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java
@@ -39,6 +39,7 @@ import java.util.regex.Pattern;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.ServerProperties;
 import org.caosdb.server.accessControl.AnonymousAuthenticationToken;
@@ -72,6 +73,13 @@ public class AuthInterceptor implements ServerInterceptor {
   public static final Predicate<String> SESSION_TOKEN_COOKIE_PREFIX_PREDICATE =
       SESSION_TOKEN_COOKIE_PREFIX_PATTERN.asPredicate();
 
+  @SuppressWarnings("unused")
+  public static Subject bindSubject() {
+    Subject subject = (Subject) SUBJECT_KEY.get();
+    ThreadContext.bind(subject);
+    return subject;
+  }
+
   public final Metadata expiredSessionMetadata() {
     Metadata metadata = new Metadata();
     metadata.put(CookieSetter.SET_COOKIE, CookieSetter.EXPIRED_SESSION_COOKIE);
@@ -102,7 +110,7 @@ public class AuthInterceptor implements ServerInterceptor {
     final String password = split[1];
     final RealmUsernamePasswordToken token =
         new RealmUsernamePasswordToken(UserSources.getDefaultRealm(), username, password);
-    final org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
+    final Subject subject = SecurityUtils.getSubject();
     subject.login(token);
     return subject;
   }
@@ -112,6 +120,16 @@ public class AuthInterceptor implements ServerInterceptor {
       final ServerCall<ReqT, RespT> call,
       final Metadata headers,
       final ServerCallHandler<ReqT, RespT> next) {
+    ThreadContext.remove();
+    Subject user = SecurityUtils.getSubject();
+    System.out.println(
+        "interceptCall: "
+            + Long.toString(Thread.currentThread().getId())
+            + " "
+            + Thread.currentThread().getName()
+            + " subject: "
+            + user.toString());
+
     String authentication = headers.get(AUTHENTICATION_HEADER);
     if (authentication == null) {
       authentication = headers.get(AUTHORIZATION_HEADER);
@@ -162,7 +180,7 @@ public class AuthInterceptor implements ServerInterceptor {
       final String tokenString = URLDecodeWithUTF8(sessionTokenCookie.split(";")[0]);
 
       final Subject subject = sessionTokenAuth(tokenString);
-      return updateContext(subject, call, headers, next);
+      return updateContext(subject, call, headers, next, "sessionToken: " + tokenString);
     } catch (final AuthenticationException e) {
       final Status status =
           Status.UNAUTHENTICATED.withDescription(
@@ -192,7 +210,12 @@ public class AuthInterceptor implements ServerInterceptor {
       final ServerCallHandler<ReqT, RespT> next) {
     try {
       final Subject subject = basicAuth(base64);
-      return updateContext(subject, call, headers, next);
+      return updateContext(
+          subject,
+          call,
+          headers,
+          next,
+          "basic: " + base64 + " thread: " + Thread.currentThread().getName());
     } catch (final AuthenticationException e) {
       final Status status =
           Status.UNAUTHENTICATED.withDescription(
@@ -212,7 +235,7 @@ public class AuthInterceptor implements ServerInterceptor {
       final Metadata headers,
       final ServerCallHandler<ReqT, RespT> next) {
     final Subject subject = anonymous();
-    return updateContext(subject, call, headers, next);
+    return updateContext(subject, call, headers, next, "anonymous");
   }
 
   /** Login as anonymous. */
@@ -230,10 +253,11 @@ public class AuthInterceptor implements ServerInterceptor {
       final Subject subject,
       final ServerCall<ReqT, RespT> call,
       final Metadata headers,
-      final ServerCallHandler<ReqT, RespT> next) {
+      final ServerCallHandler<ReqT, RespT> next,
+      final String tag) {
     final Context context = Context.current();
     context.withValue(SUBJECT_KEY, subject);
-    ServerCall<ReqT, RespT> cookieSetter = new CookieSetter<>(call);
+    ServerCall<ReqT, RespT> cookieSetter = new CookieSetter<>(call, subject, tag);
     return Contexts.interceptCall(context, cookieSetter, headers, next);
   }
 }
@@ -245,9 +269,11 @@ final class CookieSetter<ReqT, RespT>
           + "=expired; Path=/; HttpOnly; SameSite=Strict; Max-Age=0";
   public static final Key<String> SET_COOKIE =
       Key.of("Set-Cookie", Metadata.ASCII_STRING_MARSHALLER);
+  private Subject subject;
 
-  protected CookieSetter(ServerCall<ReqT, RespT> delegate) {
+  protected CookieSetter(ServerCall<ReqT, RespT> delegate, Subject subject, String tag) {
     super(delegate);
+    this.subject = subject;
   }
 
   String getSessionTimeoutSeconds() {
@@ -264,10 +290,9 @@ final class CookieSetter<ReqT, RespT>
   };
 
   private void setSessionCookies(Metadata headers) {
-    final Subject subject = SecurityUtils.getSubject();
     // if authenticated as a normal user: generate and set session cookie.
     if (subject.isAuthenticated()
-        && subject.getPrincipal() != AnonymousAuthenticationToken.PRINCIPAL) {
+        && !AnonymousAuthenticationToken.PRINCIPAL.equals(subject.getPrincipal())) {
       final SessionToken sessionToken = SessionToken.generate(subject);
       if (sessionToken != null && sessionToken.isValid()) {
 
@@ -284,6 +309,9 @@ final class CookieSetter<ReqT, RespT>
                   + getSessionTimeoutSeconds());
         }
       }
+    } else if (AnonymousAuthenticationToken.PRINCIPAL.equals(subject.getPrincipal())) {
+      // this is anonymous, do nothing
+      headers.toString();
     } else {
       headers.put(SET_COOKIE, EXPIRED_SESSION_COOKIE);
     }
diff --git a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java
index 9b43a04c..8abb2847 100644
--- a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java
+++ b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java
@@ -410,6 +410,7 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa
       final MultiTransactionRequest request,
       final StreamObserver<MultiTransactionResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final MultiTransactionResponse response = transaction(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -424,6 +425,7 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa
       MultiRetrieveEntityACLRequest request,
       StreamObserver<MultiRetrieveEntityACLResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final MultiRetrieveEntityACLResponse response = multiRetrieveEntityACL(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
@@ -438,6 +440,7 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa
       MultiUpdateEntityACLRequest request,
       StreamObserver<MultiUpdateEntityACLResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final MultiUpdateEntityACLResponse response = multiUpdateEntityACL(request);
       responseObserver.onNext(response);
       responseObserver.onCompleted();
diff --git a/src/main/java/org/caosdb/server/grpc/FileTransmissionServiceImpl.java b/src/main/java/org/caosdb/server/grpc/FileTransmissionServiceImpl.java
index 0b0c4b50..1f549b37 100644
--- a/src/main/java/org/caosdb/server/grpc/FileTransmissionServiceImpl.java
+++ b/src/main/java/org/caosdb/server/grpc/FileTransmissionServiceImpl.java
@@ -45,6 +45,7 @@ public class FileTransmissionServiceImpl extends FileTransmissionServiceImplBase
 
     @Override
     public void onNext(final FileUploadRequest request) {
+      AuthInterceptor.bindSubject();
       final FileChunk chunk = request.getChunk();
       if (chunk.hasFileTransmissionId()) {
         fileUpload =
@@ -104,6 +105,7 @@ public class FileTransmissionServiceImpl extends FileTransmissionServiceImplBase
       final RegisterFileUploadRequest request,
       final StreamObserver<RegisterFileUploadResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       final FileTransmission result = fileUploadRegistration.registerFileUpload();
       final Builder builder = RegisterFileUploadResponse.newBuilder();
       builder.setStatus(result.getRegistrationStatus());
@@ -133,6 +135,7 @@ public class FileTransmissionServiceImpl extends FileTransmissionServiceImplBase
       final FileDownloadRequest request,
       final StreamObserver<FileDownloadResponse> responseObserver) {
     try {
+      AuthInterceptor.bindSubject();
       FileDownloadResponse response =
           fileDownloadRegistration.downloadNextChunk(request.getFileTransmissionId());
       responseObserver.onNext(response);
diff --git a/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java b/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java
index de7b6987..295ed15b 100644
--- a/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java
+++ b/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java
@@ -20,10 +20,10 @@
 
 package org.caosdb.server.grpc;
 
+import io.grpc.Context;
 import io.grpc.stub.StreamObserver;
 import java.util.Collection;
 import java.util.LinkedList;
-import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.subject.Subject;
@@ -50,10 +50,7 @@ import org.caosdb.server.permissions.CaosPermission;
  */
 public class GeneralInfoServiceImpl extends GeneralInfoServiceImplBase {
 
-  @Override
-  public void getVersionInfo(
-      final GetVersionInfoRequest request,
-      final StreamObserver<GetVersionInfoResponse> responseObserver) {
+  private GetVersionInfoResponse getVersionInfo(GetVersionInfoRequest request) {
 
     final String version[] =
         CaosDBServer.getServerProperty(ServerProperties.KEY_PROJECT_VERSION).split("[\\.-]", 4);
@@ -71,43 +68,100 @@ public class GeneralInfoServiceImpl extends GeneralInfoServiceImplBase {
             .setPreRelease(pre_release)
             .setBuild(build)
             .build();
-    final GetVersionInfoResponse response =
-        GetVersionInfoResponse.newBuilder().setVersionInfo(versionInfo).build();
-
-    responseObserver.onNext(response);
-    responseObserver.onCompleted();
+    return GetVersionInfoResponse.newBuilder().setVersionInfo(versionInfo).build();
   }
 
   @Override
-  public void getSessionInfo(
-      GetSessionInfoRequest request, StreamObserver<GetSessionInfoResponse> responseObserver) {
-    final GetSessionInfoResponse.Builder response = GetSessionInfoResponse.newBuilder();
-
-    Subject user = SecurityUtils.getSubject();
-    Principal principal = (Principal) user.getPrincipal();
+  public void getVersionInfo(
+      final GetVersionInfoRequest request,
+      final StreamObserver<GetVersionInfoResponse> responseObserver) {
 
-    response.setUsername(principal.getUsername());
-    response.setRealm(principal.getRealm());
+    try {
+      GetVersionInfoResponse response = getVersionInfo(request);
+      responseObserver.onNext(response);
+      responseObserver.onCompleted();
 
-    AuthorizationInfo authorizationInfo = AuthenticationUtils.getAuthorizationInfo(user);
-    Collection<String> roles = authorizationInfo.getRoles();
-    if (roles != null && !roles.isEmpty()) {
-      response.addAllRoles(roles);
+    } catch (final Exception e) {
+      AccessControlManagementServiceImpl.handleException(responseObserver, e);
     }
-    Collection<String> permissions =
-        new LinkedList<String>(authorizationInfo.getStringPermissions());
-    for (Permission p : authorizationInfo.getObjectPermissions()) {
-      if (p instanceof CaosPermission) {
-        permissions.addAll(((CaosPermission) p).getStringPermissions(user));
-      } else {
-        permissions.add(p.toString());
+  }
+
+  private GetSessionInfoResponse getSessionInfo(GetSessionInfoRequest request) {
+
+    Subject subject = AuthInterceptor.bindSubject();
+    System.out.println(
+        "getSessionInfo  (in): "
+            + Long.toString(Thread.currentThread().getId())
+            + " "
+            + Thread.currentThread().getName()
+            + " subject: "
+            + subject.toString());
+    GetSessionInfoResponse.Builder response = GetSessionInfoResponse.newBuilder();
+
+    Principal principal = (Principal) subject.getPrincipal();
+
+    try {
+      response.setUsername(principal.getUsername());
+      response.setRealm(principal.getRealm());
+
+      AuthorizationInfo authorizationInfo = AuthenticationUtils.getAuthorizationInfo(subject);
+      Collection<String> roles = authorizationInfo.getRoles();
+      if (roles != null && !roles.isEmpty()) {
+        response.addAllRoles(roles);
       }
+
+      Collection<String> permissions = new LinkedList<>();
+
+      Collection<String> stringPermissions = authorizationInfo.getStringPermissions();
+      if (stringPermissions != null && !stringPermissions.isEmpty()) {
+        permissions.addAll(stringPermissions);
+      }
+
+      for (Permission p : authorizationInfo.getObjectPermissions()) {
+        if (p instanceof CaosPermission) {
+          permissions.addAll(((CaosPermission) p).getStringPermissions(subject));
+        } else {
+          permissions.add(p.toString());
+        }
+      }
+
+      if (permissions != null && !permissions.isEmpty()) {
+        response.addAllPermissions(permissions);
+      }
+
+      System.out.println(
+          "getSessionInfo (out): "
+              + Long.toString(Thread.currentThread().getId())
+              + " "
+              + Thread.currentThread().getName()
+              + " subject: "
+              + subject.toString());
+      return response.build();
+    } catch (NullPointerException e) {
+      final Context context = Context.current();
+
+      System.out.println(
+          "getSessionInfo (exc): "
+              + Long.toString(Thread.currentThread().getId())
+              + " "
+              + Thread.currentThread().getName()
+              + " subject: "
+              + subject.toString());
+      throw e;
     }
-    if (permissions != null && !permissions.isEmpty()) {
-      response.addAllPermissions(permissions);
-    }
+  }
+
+  @Override
+  public void getSessionInfo(
+      GetSessionInfoRequest request, StreamObserver<GetSessionInfoResponse> responseObserver) {
 
-    responseObserver.onNext(response.build());
-    responseObserver.onCompleted();
+    try {
+      final GetSessionInfoResponse response = getSessionInfo(request);
+      responseObserver.onNext(response);
+      responseObserver.onCompleted();
+
+    } catch (final Exception e) {
+      AccessControlManagementServiceImpl.handleException(responseObserver, e);
+    }
   }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java
index 9bca9eb2..675686aa 100644
--- a/src/main/java/org/caosdb/server/jobs/Job.java
+++ b/src/main/java/org/caosdb/server/jobs/Job.java
@@ -27,8 +27,6 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import org.apache.shiro.authz.AuthorizationException;
-import org.apache.shiro.authz.Permission;
 import org.apache.shiro.subject.Subject;
 import org.caosdb.server.CaosDBException;
 import org.caosdb.server.database.BackendTransaction;
@@ -300,11 +298,6 @@ public abstract class Job {
     return entity;
   }
 
-  protected final void checkPermission(final EntityInterface entity, final Permission permission)
-      throws AuthorizationException {
-    entity.checkPermission(permission);
-  }
-
   /**
    * Create a Job object with the given parameters.
    *
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
index 9ea7d4b3..dd87d529 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java
@@ -23,6 +23,7 @@
 package org.caosdb.server.jobs.core;
 
 import java.util.List;
+import org.apache.shiro.authz.AuthorizationException;
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
 import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.datatype.AbstractCollectionDatatype;
@@ -97,13 +98,15 @@ public final class CheckDatatypePresent extends EntityJob {
         // finally, no data type
         throw ServerMessages.PROPERTY_HAS_NO_DATATYPE;
       }
-
     } catch (final Message m) {
       if (m == ServerMessages.ENTITY_DOES_NOT_EXIST) {
         getEntity().addError(ServerMessages.UNKNOWN_DATATYPE);
       } else {
         getEntity().addError(m);
       }
+    } catch (AuthorizationException exc) {
+      getEntity().addError(ServerMessages.AUTHORIZATION_ERROR);
+      getEntity().addInfo(exc.getMessage());
     } catch (final EntityDoesNotExistException exc) {
       getEntity().addError(ServerMessages.UNKNOWN_DATATYPE);
     } catch (final EntityWasNotUniqueException exc) {
@@ -152,8 +155,8 @@ public final class CheckDatatypePresent extends EntityJob {
     }
   }
 
-  private void assertAllowedToUse(final EntityInterface datatype) throws Message {
-    checkPermission(datatype, EntityPermission.USE_AS_DATA_TYPE);
+  private void assertAllowedToUse(final EntityInterface datatype) {
+    datatype.checkPermission(EntityPermission.USE_AS_DATA_TYPE);
   }
 
   private void checkIfOverride() throws Message {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
index 4e6b097e..d3b28bd5 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java
@@ -23,6 +23,7 @@
 package org.caosdb.server.jobs.core;
 
 import com.google.common.base.Objects;
+import org.apache.shiro.authz.AuthorizationException;
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
 import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.entity.Affiliation;
@@ -118,13 +119,16 @@ public class CheckParValid extends EntityJob {
             }
           }
 
-          addError(parent, ServerMessages.ENTITY_DOES_NOT_EXIST);
+          parent.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
         } catch (final Message m) {
-          addError(parent, m);
+          parent.addError(m);
+        } catch (AuthorizationException e) {
+          parent.addError(ServerMessages.AUTHORIZATION_ERROR);
+          parent.addInfo(e.getMessage());
         } catch (final EntityDoesNotExistException exc) {
-          addError(parent, ServerMessages.ENTITY_DOES_NOT_EXIST);
+          parent.addError(ServerMessages.ENTITY_DOES_NOT_EXIST);
         } catch (final EntityWasNotUniqueException exc) {
-          addError(parent, ServerMessages.ENTITY_NAME_DUPLICATES);
+          parent.addError(ServerMessages.ENTITY_NAME_DUPLICATES);
         }
       }
     }
@@ -191,12 +195,7 @@ public class CheckParValid extends EntityJob {
     throw ServerMessages.AFFILIATION_ERROR;
   }
 
-  private void assertAllowedToUse(final EntityInterface entity) throws Message {
-    checkPermission(entity, EntityPermission.USE_AS_PARENT);
-  }
-
-  private void addError(final EntityInterface parent, final Message m) {
-    parent.addError(m);
-    parent.setEntityStatus(EntityStatus.UNQUALIFIED);
+  private void assertAllowedToUse(final EntityInterface entity) {
+    entity.checkPermission(EntityPermission.USE_AS_PARENT);
   }
 }
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
index 112692c8..eeea52b8 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java
@@ -138,17 +138,10 @@ public class CheckPropValid extends EntityJob {
 
     // process names
     appendJob(ProcessNameProperties.class);
-    // final ProcessNameProperties processNameProperties = new
-    // ProcessNameProperties();
-    // processNameProperties.init(getMode(), getEntity(), getContainer(),
-    // getTransaction());
-    // getTransaction().getSchedule().add(processNameProperties);
-    // getTransaction().getSchedule().runJob(processNameProperties);
-
   }
 
-  private void assertAllowedToUse(final EntityInterface property) throws Message {
-    checkPermission(property, EntityPermission.USE_AS_PROPERTY);
+  private void assertAllowedToUse(final EntityInterface property) {
+    property.checkPermission(EntityPermission.USE_AS_PROPERTY);
   }
 
   private static void deriveOverrideStatus(final Property child, final EntityInterface parent) {
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
index f19424b1..645f87d0 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java
@@ -24,6 +24,7 @@
  */
 package org.caosdb.server.jobs.core;
 
+import org.apache.shiro.authz.AuthorizationException;
 import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
 import org.caosdb.server.database.exceptions.EntityWasNotUniqueException;
 import org.caosdb.server.datatype.CollectionValue;
@@ -71,13 +72,13 @@ public class CheckRefidValid extends EntityJob implements Observer {
       }
     } catch (final Message m) {
       getEntity().addError(m);
-      getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
+    } catch (AuthorizationException exc) {
+      getEntity().addError(ServerMessages.AUTHORIZATION_ERROR);
+      getEntity().addInfo(exc.getMessage());
     } catch (final EntityDoesNotExistException e) {
       getEntity().addError(ServerMessages.REFERENCED_ENTITY_DOES_NOT_EXIST);
-      getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
     } catch (final EntityWasNotUniqueException e) {
       getEntity().addError(ServerMessages.REFERENCE_NAME_DUPLICATES);
-      getEntity().setEntityStatus(EntityStatus.UNQUALIFIED);
     }
   }
 
@@ -139,8 +140,8 @@ public class CheckRefidValid extends EntityJob implements Observer {
     }
   }
 
-  private void assertAllowedToUse(final EntityInterface referencedEntity) throws Message {
-    checkPermission(referencedEntity, EntityPermission.USE_AS_REFERENCE);
+  private void assertAllowedToUse(final EntityInterface referencedEntity) {
+    referencedEntity.checkPermission(EntityPermission.USE_AS_REFERENCE);
   }
 
   @Override
diff --git a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
index 0fa5b4b4..44ebfa22 100644
--- a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
+++ b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java
@@ -389,6 +389,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource {
   public Representation handleThrowable(final Throwable t) {
     try {
       getRequest().getAttributes().put("THROWN", t);
+      t.printStackTrace();
       throw t;
     } catch (final AuthenticationException e) {
       return error(ServerMessages.UNAUTHENTICATED, Status.CLIENT_ERROR_UNAUTHORIZED);
diff --git a/src/main/java/org/caosdb/server/resource/RolesResource.java b/src/main/java/org/caosdb/server/resource/RolesResource.java
index a89a7319..ad1544da 100644
--- a/src/main/java/org/caosdb/server/resource/RolesResource.java
+++ b/src/main/java/org/caosdb/server/resource/RolesResource.java
@@ -26,7 +26,6 @@ import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import java.sql.SQLException;
 import org.caosdb.server.CaosDBException;
-import org.caosdb.server.accessControl.ACMPermissions;
 import org.caosdb.server.accessControl.Role;
 import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException;
 import org.caosdb.server.entity.Message;
@@ -55,7 +54,6 @@ public class RolesResource extends AbstractCaosDBServerResource {
     if (getRequestedItems().length > 0) {
       final String name = getRequestedItems()[0];
       if (name != null) {
-        getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(name));
         final RetrieveRoleTransaction t = new RetrieveRoleTransaction(name);
         try {
           t.execute();
diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveRoleTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveRoleTransaction.java
index 1047e1f6..8e92a347 100644
--- a/src/main/java/org/caosdb/server/transaction/RetrieveRoleTransaction.java
+++ b/src/main/java/org/caosdb/server/transaction/RetrieveRoleTransaction.java
@@ -36,15 +36,20 @@ public class RetrieveRoleTransaction extends AccessControlTransaction {
 
   private final String name;
   private Role role;
+  private Subject transactor;
 
-  public RetrieveRoleTransaction(final String name) {
+  public RetrieveRoleTransaction(final String name, Subject transactor) {
+    this.transactor = transactor;
     this.name = name;
   }
 
+  public RetrieveRoleTransaction(final String name) {
+    this(name, SecurityUtils.getSubject());
+  }
+
   @Override
   protected void transaction() throws Exception {
-    Subject currentUser = SecurityUtils.getSubject();
-    if (!currentUser.isPermitted(ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(this.name))) {
+    if (!transactor.isPermitted(ACMPermissions.PERMISSION_RETRIEVE_ROLE_DESCRIPTION(this.name))) {
       throw new AuthorizationException("You are not permitted to retrieve this role");
     }
     this.role = execute(new RetrieveRole(this.name), getAccess()).getRole();
@@ -55,7 +60,7 @@ public class RetrieveRoleTransaction extends AccessControlTransaction {
       Iterator<ProtoUser> iterator = this.role.users.iterator();
       while (iterator.hasNext()) {
         ProtoUser user = iterator.next();
-        if (!currentUser.isPermitted(
+        if (!transactor.isPermitted(
             ACMPermissions.PERMISSION_RETRIEVE_USER_ROLES(user.realm, user.name))) {
           iterator.remove();
         }
-- 
GitLab