diff --git a/pom.xml b/pom.xml index 2a0756fdfe87505ed5d20d4a5cf60be144205223..45e286116ba16b9f30860da5ac877f1131b90522 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ <grpc.version>1.42.1</grpc.version> <netty-tcnative.version>2.0.34.Final</netty-tcnative.version> <restlet.version>2.4.3</restlet.version> + <log4j.version>2.15.0</log4j.version> </properties> <repositories> <repository> @@ -167,22 +168,22 @@ <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> - <version>2.11.1</version> + <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> - <version>1.7.21</version> + <version>1.7.32</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> - <version>2.11.1</version> + <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> - <version>2.11.1</version> + <version>${log4j.version}</version> </dependency> <dependency> <groupId>io.grpc</groupId> diff --git a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java index 712258207527bbbcff27d74c5c19220bce857650..2ac5c1610e05de9f7710e7c936672729d46a1570 100644 --- a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java @@ -592,7 +592,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS Subject subject = SecurityUtils.getSubject(); if (AuthenticationUtils.isAnonymous(subject)) { responseObserver.onError( - new StatusException(Status.UNAUTHENTICATED.withDescription("Please login!"))); + new StatusException(AuthInterceptor.PLEASE_LOG_IN)); return; } else { responseObserver.onError( diff --git a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java index 5d06f05c7e03a3130a9006c4a1a399ddf3df0d27..169dbade4af3b45e194df21bfe86906b24f2bda9 100644 --- a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java +++ b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java @@ -60,6 +60,7 @@ import org.restlet.data.CookieSetting; */ public class AuthInterceptor implements ServerInterceptor { + public static final Status PLEASE_LOG_IN = Status.UNAUTHENTICATED.withDescription("Please log in!"); public static final Key<String> AUTHENTICATION_HEADER = Key.of("authentication", Metadata.ASCII_STRING_MARSHALLER); public static final Key<String> AUTHORIZATION_HEADER = @@ -109,7 +110,7 @@ public class AuthInterceptor implements ServerInterceptor { final String username = split[0]; final String password = split[1]; final RealmUsernamePasswordToken token = - new RealmUsernamePasswordToken(UserSources.getDefaultRealm(), username, password); + new RealmUsernamePasswordToken(UserSources.guessRealm(username, UserSources.getDefaultRealm()), username, password); final Subject subject = SecurityUtils.getSubject(); subject.login(token); return subject; @@ -143,7 +144,7 @@ public class AuthInterceptor implements ServerInterceptor { if (authentication == null && isAuthOptional()) { return anonymous(call, headers, next); } else if (authentication == null) { - status = Status.UNAUTHENTICATED.withDescription("Please login."); + status = PLEASE_LOG_IN; } else if (authentication.startsWith(BASIC_SCHEME_PREFIX)) { return basicAuth(authentication.substring(BASIC_SCHEME_PREFIX.length()), call, headers, next); } else if (SESSION_TOKEN_COOKIE_PREFIX_PREDICATE.test(authentication)) { @@ -255,8 +256,7 @@ public class AuthInterceptor implements ServerInterceptor { final Metadata headers, final ServerCallHandler<ReqT, RespT> next, final String tag) { - final Context context = Context.current(); - context.withValue(SUBJECT_KEY, subject); + final Context context = Context.current().withValue(SUBJECT_KEY, subject); ServerCall<ReqT, RespT> cookieSetter = new CookieSetter<>(call, subject, tag); return Contexts.interceptCall(context, cookieSetter, headers, next); } diff --git a/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java b/src/main/java/org/caosdb/server/grpc/GeneralInfoServiceImpl.java index 295ed15b15a144222d17aec20185cf1b5cbb3ab0..e47bda9c1783fd9a82703626ad8017c26dbea1fe 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; @@ -77,6 +77,7 @@ public class GeneralInfoServiceImpl extends GeneralInfoServiceImplBase { final StreamObserver<GetVersionInfoResponse> responseObserver) { try { + AuthInterceptor.bindSubject(); GetVersionInfoResponse response = getVersionInfo(request); responseObserver.onNext(response); responseObserver.onCompleted(); @@ -87,68 +88,40 @@ public class GeneralInfoServiceImpl extends GeneralInfoServiceImplBase { } 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()); + Subject subject = SecurityUtils.getSubject(); GetSessionInfoResponse.Builder response = GetSessionInfoResponse.newBuilder(); Principal principal = (Principal) subject.getPrincipal(); - try { - response.setUsername(principal.getUsername()); - response.setRealm(principal.getRealm()); + 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<>(); + AuthorizationInfo authorizationInfo = AuthenticationUtils.getAuthorizationInfo(subject); + Collection<String> roles = authorizationInfo.getRoles(); + if (roles != null && !roles.isEmpty()) { + response.addAllRoles(roles); + } - Collection<String> stringPermissions = authorizationInfo.getStringPermissions(); - if (stringPermissions != null && !stringPermissions.isEmpty()) { - permissions.addAll(stringPermissions); - } + Collection<String> permissions = new LinkedList<>(); - for (Permission p : authorizationInfo.getObjectPermissions()) { - if (p instanceof CaosPermission) { - permissions.addAll(((CaosPermission) p).getStringPermissions(subject)); - } else { - permissions.add(p.toString()); - } - } + Collection<String> stringPermissions = authorizationInfo.getStringPermissions(); + if (stringPermissions != null && !stringPermissions.isEmpty()) { + permissions.addAll(stringPermissions); + } - if (permissions != null && !permissions.isEmpty()) { - response.addAllPermissions(permissions); + for (Permission p : authorizationInfo.getObjectPermissions()) { + if (p instanceof CaosPermission) { + permissions.addAll(((CaosPermission) p).getStringPermissions(subject)); + } else { + permissions.add(p.toString()); } + } - 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); } + + return response.build(); } @Override @@ -156,6 +129,7 @@ public class GeneralInfoServiceImpl extends GeneralInfoServiceImplBase { GetSessionInfoRequest request, StreamObserver<GetSessionInfoResponse> responseObserver) { try { + AuthInterceptor.bindSubject(); final GetSessionInfoResponse response = getSessionInfo(request); responseObserver.onNext(response); responseObserver.onCompleted(); diff --git a/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java index 463763e7887ec9a6dda7695a842b09703ee4b1ee..09d59e8d84659037da66e11f7b4c0da556d8aa50 100644 --- a/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java @@ -25,13 +25,14 @@ package org.caosdb.server.transaction; import org.apache.shiro.SecurityUtils; import org.caosdb.server.accessControl.ACMPermissions; +import org.caosdb.server.accessControl.Principal; import org.caosdb.server.accessControl.UserSources; import org.caosdb.server.accessControl.UserStatus; -import org.caosdb.server.database.backend.transaction.RetrievePasswordValidator; import org.caosdb.server.database.backend.transaction.SetPassword; import org.caosdb.server.database.backend.transaction.UpdateUser; import org.caosdb.server.database.backend.transaction.UpdateUserRoles; import org.caosdb.server.database.proto.ProtoUser; +import org.caosdb.server.entity.Message; import org.caosdb.server.utils.ServerMessages; import org.caosdb.server.utils.Utils; import org.jdom2.Element; @@ -65,6 +66,8 @@ public class InsertUserTransaction extends AccessControlTransaction { SecurityUtils.getSubject() .checkPermission(ACMPermissions.PERMISSION_INSERT_USER(this.user.realm)); + checkUserName(this.user.name); + if (this.user.email != null && !Utils.isRFC822Compliant(this.user.email)) { throw ServerMessages.EMAIL_NOT_WELL_FORMED; } @@ -73,16 +76,31 @@ public class InsertUserTransaction extends AccessControlTransaction { UpdateUserTransaction.checkEntityExists(this.user.entity); } - if (execute(new RetrievePasswordValidator(this.user.name), getAccess()).getValidator() - == null) { - if (this.password != null) { - Utils.checkPasswordStrength(this.password); - } + if (this.password != null) { + Utils.checkPasswordStrength(this.password); + } + + execute(new SetPassword(this.user.name, this.password), getAccess()); + execute(new UpdateUser(this.user), getAccess()); + execute(new UpdateUserRoles(this.user.realm, this.user.name, this.user.roles), getAccess()); + } + + /* + * Names should have at least a length of 1, a maximum length of 32 and match + * ^[a-zA-Z_][a-zA-Z0-9_-]*$. + */ + private void checkUserName(String name) throws Message { + // Make this configurable? + final boolean length = name.length() >= 1 && name.length() <= 32; + final boolean match = + name.matches("^[\\p{Lower}\\p{Upper}_][\\p{Lower}\\p{Upper}\\p{Digit}_-]*$"); + + if (!(length && match)) { + throw ServerMessages.INVALID_USER_NAME( + "User names must have a length from 1 to 32 characters, begin with a latin letter a-z (upper case or lower case) or an underscore (_), and all other characters must be latin letters, arabic numbers, hyphens (-) or undescores (_)."); + } - execute(new SetPassword(this.user.name, this.password), getAccess()); - execute(new UpdateUser(this.user), getAccess()); - execute(new UpdateUserRoles(this.user.realm, this.user.name, this.user.roles), getAccess()); - } else { + if (UserSources.isUserExisting(new Principal(this.user.realm, this.user.name))) { throw ServerMessages.ACCOUNT_NAME_NOT_UNIQUE; } } diff --git a/src/main/java/org/caosdb/server/utils/ServerMessages.java b/src/main/java/org/caosdb/server/utils/ServerMessages.java index c1f361e24ce88260d9ac2932d542eedb136180df..ea5222896895e22ef190ee4d04ea6b3e1c282e1a 100644 --- a/src/main/java/org/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/org/caosdb/server/utils/ServerMessages.java @@ -597,4 +597,11 @@ public class ServerMessages { MessageType.Error, MessageCode.MESSAGE_CODE_INTEGRITY_VIOLATION, "This entity caused an unexpected integrity violation. This is a strong indicator for a server bug. Please report."); + + public static final Message INVALID_USER_NAME(String policy) { + return new Message( + MessageType.Error, + MessageCode.MESSAGE_CODE_UNKNOWN, + "The user name does not comply with the policies for user names: " + policy); + } }