diff --git a/.gitignore b/.gitignore index 3ff10224622c..53e337d59ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,6 @@ managed/devops/pex/pexEnv/ *.bad_clang_tidy_output.* *.compiler_errors.txt + +# jenv local version +**/.java-version \ No newline at end of file diff --git a/managed/src/main/java/com/yugabyte/yw/common/LdapUtil.java b/managed/src/main/java/com/yugabyte/yw/common/LdapUtil.java index 3eeddd1c5f60..eba3ae0ef0b9 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/LdapUtil.java +++ b/managed/src/main/java/com/yugabyte/yw/common/LdapUtil.java @@ -73,6 +73,7 @@ public static class LdapConfiguration { String ldapGroupMemberOfAttribute; boolean ldapGroupUseQuery; boolean ldapGroupUseRoleMapping; + Role ldapDefaultRole; } public Users loginWithLdap(CustomerLoginFormData data) throws LdapException { @@ -100,6 +101,7 @@ public Users loginWithLdap(CustomerLoginFormData data) throws LdapException { boolean ldapGroupUseRoleMapping = confGetter.getGlobalConf(GlobalConfKeys.ldapGroupUseRoleMapping); String ldapGroupSearchBaseDn = confGetter.getGlobalConf(GlobalConfKeys.ldapGroupSearchBaseDn); + Role ldapDefaultRole = confGetter.getGlobalConf(GlobalConfKeys.ldapDefaultRole); LdapConfiguration ldapConfiguration = new LdapConfiguration( @@ -120,7 +122,8 @@ public Users loginWithLdap(CustomerLoginFormData data) throws LdapException { ldapGroupSearchBaseDn, ldapGroupMemberOfAttribute, ldapGroupUseQuery, - ldapGroupUseRoleMapping); + ldapGroupUseRoleMapping, + ldapDefaultRole); Users user = authViaLDAP(data.getEmail(), data.getPassword(), ldapConfiguration); if (user == null) { @@ -490,8 +493,8 @@ public Users authViaLDAP(String email, String password, LdapConfiguration ldapCo roleToAssign = Users.Role.ReadOnly; break; default: - roleToAssign = Users.Role.ReadOnly; - log.warn("No valid role could be ascertained, defaulting to ReadOnly."); + roleToAssign = ldapConfiguration.getLdapDefaultRole(); + log.warn("No valid role could be ascertained, defaulting to {}.", roleToAssign); if (!ldapConfiguration.isLdapGroupUseRoleMapping()) { users.setLdapSpecifiedRole(false); } diff --git a/managed/src/main/java/com/yugabyte/yw/common/config/ConfDataType.java b/managed/src/main/java/com/yugabyte/yw/common/config/ConfDataType.java index 40189e1a3b9a..0550e7765d1b 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/config/ConfDataType.java +++ b/managed/src/main/java/com/yugabyte/yw/common/config/ConfDataType.java @@ -22,6 +22,7 @@ import com.yugabyte.yw.common.NodeManager.SkipCertValidationType; import com.yugabyte.yw.common.PlatformServiceException; import com.yugabyte.yw.common.config.ConfKeyInfo.ConfKeyTags; +import com.yugabyte.yw.models.Users.Role; import java.time.Duration; import java.time.Period; import java.util.List; @@ -158,11 +159,30 @@ public class ConfDataType { (s) -> { try { return SearchScope.valueOf(s); - } catch (Exception e) { + } catch (IllegalArgumentException e) { String failMsg = String.format("%s is not a valid LDAP Search Scope\n", s); throw new PlatformServiceException(BAD_REQUEST, failMsg + e.getMessage()); } }); + static ConfDataType LdapDefaultRoleEnum = + new ConfDataType<>( + "LdapDefaultRole", + Role.class, + new EnumGetter<>(Role.class), + (s) -> { + try { + Role defaultRole = Role.valueOf(s); + if (defaultRole != Role.ConnectOnly && defaultRole != Role.ReadOnly) { + throw new PlatformServiceException( + BAD_REQUEST, + String.format("%s role cannot be set as default LDAP role!", defaultRole)); + } + return defaultRole; + } catch (IllegalArgumentException e) { + String failMsg = String.format("%s is not a valid Default LDAP role!\n", s); + throw new PlatformServiceException(BAD_REQUEST, failMsg + e.getMessage()); + } + }); private final String name; diff --git a/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java b/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java index 510a1f687503..2e23cda698a0 100644 --- a/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java +++ b/managed/src/main/java/com/yugabyte/yw/common/config/GlobalConfKeys.java @@ -13,6 +13,7 @@ import com.google.common.collect.ImmutableList; import com.yugabyte.yw.common.config.ConfKeyInfo.ConfKeyTags; import com.yugabyte.yw.forms.RuntimeConfigFormData.ScopedConfig.ScopeType; +import com.yugabyte.yw.models.Users.Role; import java.time.Duration; import java.util.List; import org.apache.directory.api.ldap.model.message.SearchScope; @@ -552,6 +553,14 @@ public class GlobalConfKeys extends RuntimeConfigKeysModule { "Hidden because this key has dedicated UI", ConfDataType.BooleanType, ImmutableList.of(ConfKeyTags.UIDriven)); + public static ConfKeyInfo ldapDefaultRole = + new ConfKeyInfo<>( + "yb.security.ldap.ldap_default_role", + ScopeType.GLOBAL, + "Which role to use in case role cannot be discerned via LDAP", + "Hidden because this key has dedicated UI", + ConfDataType.LdapDefaultRoleEnum, + ImmutableList.of(ConfKeyTags.UIDriven)); public static ConfKeyInfo enableDetailedLogs = new ConfKeyInfo<>( "yb.security.enable_detailed_logs", diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/TokenAuthenticator.java b/managed/src/main/java/com/yugabyte/yw/controllers/TokenAuthenticator.java index 2942571b1cae..6c8c28ca2dcc 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/TokenAuthenticator.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/TokenAuthenticator.java @@ -262,12 +262,6 @@ private static String fetchToken(Http.Request request, boolean isApiToken) { // Check role, and if the API call is accessible. private boolean checkAccessLevel(String endPoint, Users user, String requestType) { - - // Allow only superadmins to change LDAP Group Mappings. - if (endPoint.endsWith("/ldap_mappings") && requestType.equals("PUT")) { - return user.getRole() == Role.SuperAdmin; - } - // Users should be allowed to change their password. // Even admin users should not be allowed to change another // user's password. @@ -276,6 +270,20 @@ private boolean checkAccessLevel(String endPoint, Users user, String requestType return userUUID.equals(user.getUuid()); } + if (requestType.equals("GET") && endPoint.endsWith("/users/" + user.getUuid())) { + return true; + } + + // If the user is ConnectOnly, then don't get any further access + if (user.getRole() == Role.ConnectOnly) { + return false; + } + + // Allow only superadmins to change LDAP Group Mappings. + if (endPoint.endsWith("/ldap_mappings") && requestType.equals("PUT")) { + return user.getRole() == Role.SuperAdmin; + } + // All users have access to get, metrics and setting an API token. if (requestType.equals("GET") || endPoint.equals("/metrics") || endPoint.equals("/api_token")) { return true; diff --git a/managed/src/main/java/com/yugabyte/yw/controllers/UsersController.java b/managed/src/main/java/com/yugabyte/yw/controllers/UsersController.java index 7c56a421c71e..8edda8ad4171 100644 --- a/managed/src/main/java/com/yugabyte/yw/controllers/UsersController.java +++ b/managed/src/main/java/com/yugabyte/yw/controllers/UsersController.java @@ -317,16 +317,16 @@ public Result updateProfile(UUID customerUUID, UUID userUUID, Http.Request reque } Users loggedInUser = getLoggedInUser(request); - if ((loggedInUser.getRole() == Role.ReadOnly || loggedInUser.getRole() == Role.BackupAdmin) + if (loggedInUser.getRole().compareTo(Role.BackupAdmin) <= 0 && formData.getRole() != user.getRole()) { throw new PlatformServiceException( - BAD_REQUEST, "ReadOnly/BackupAdmin users can't change their assigned roles"); + BAD_REQUEST, "ConnectOnly/ReadOnly/BackupAdmin users can't change their assigned roles"); } - if ((loggedInUser.getRole() == Role.ReadOnly || loggedInUser.getRole() == Role.BackupAdmin) + if (loggedInUser.getRole().compareTo(Role.BackupAdmin) <= 0 && !formData.getTimezone().equals(user.getTimezone())) { throw new PlatformServiceException( - BAD_REQUEST, "ReadOnly/BackupAdmin users can't change their timezone"); + BAD_REQUEST, "ConnectOnly/ReadOnly/BackupAdmin users can't change their timezone"); } if (StringUtils.isNotEmpty(formData.getTimezone()) diff --git a/managed/src/main/java/com/yugabyte/yw/models/Users.java b/managed/src/main/java/com/yugabyte/yw/models/Users.java index 681a60670c50..71b5f590ac80 100644 --- a/managed/src/main/java/com/yugabyte/yw/models/Users.java +++ b/managed/src/main/java/com/yugabyte/yw/models/Users.java @@ -50,6 +50,9 @@ public class Users extends Model { /** These are the available user roles */ public enum Role { + @EnumValue("ConnectOnly") + ConnectOnly, + @EnumValue("ReadOnly") ReadOnly, diff --git a/managed/src/main/resources/db/migration/default_/common/V262__Alter_Users_role_constraint.sql b/managed/src/main/resources/db/migration/default_/common/V262__Alter_Users_role_constraint.sql new file mode 100644 index 000000000000..a75eeec9639c --- /dev/null +++ b/managed/src/main/resources/db/migration/default_/common/V262__Alter_Users_role_constraint.sql @@ -0,0 +1,4 @@ +ALTER TABLE users + DROP CONSTRAINT ck_users_role; +ALTER TABLE users + ADD CONSTRAINT ck_users_role check (role in ('ConnectOnly', 'ReadOnly','Admin','SuperAdmin', 'BackupAdmin')); \ No newline at end of file diff --git a/managed/src/main/resources/reference.conf b/managed/src/main/resources/reference.conf index c81f0292f490..34cf1852173d 100644 --- a/managed/src/main/resources/reference.conf +++ b/managed/src/main/resources/reference.conf @@ -661,6 +661,7 @@ yb { ldap_group_member_of_attribute = "memberOf" ldap_group_use_query = false ldap_group_use_role_mapping = false + ldap_default_role = "ReadOnly" } forbidden_ips="169.254.169.254" custom_hooks { diff --git a/managed/src/main/resources/swagger-strict.json b/managed/src/main/resources/swagger-strict.json index 3dff99048864..7ef7b5d1d2b7 100644 --- a/managed/src/main/resources/swagger-strict.json +++ b/managed/src/main/resources/swagger-strict.json @@ -5489,7 +5489,7 @@ "type" : "string" }, "ybaRole" : { - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" } }, @@ -11960,7 +11960,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "example" : "Admin", "type" : "string" }, @@ -12000,7 +12000,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "example" : "Admin", "type" : "string" }, @@ -12051,7 +12051,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" }, "timezone" : { @@ -12111,7 +12111,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" }, "timezone" : { diff --git a/managed/src/main/resources/swagger.json b/managed/src/main/resources/swagger.json index 3b927b187019..45b813f59900 100644 --- a/managed/src/main/resources/swagger.json +++ b/managed/src/main/resources/swagger.json @@ -5507,7 +5507,7 @@ "type" : "string" }, "ybaRole" : { - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" } }, @@ -12028,7 +12028,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "example" : "Admin", "type" : "string" }, @@ -12068,7 +12068,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "example" : "Admin", "type" : "string" }, @@ -12119,7 +12119,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" }, "timezone" : { @@ -12179,7 +12179,7 @@ }, "role" : { "description" : "User role", - "enum" : [ "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], + "enum" : [ "ConnectOnly", "ReadOnly", "BackupAdmin", "Admin", "SuperAdmin" ], "type" : "string" }, "timezone" : { diff --git a/managed/src/test/java/com/yugabyte/yw/common/LdapUtilTest.java b/managed/src/test/java/com/yugabyte/yw/common/LdapUtilTest.java index 7b6b8ccb5dae..b59ca67fa934 100644 --- a/managed/src/test/java/com/yugabyte/yw/common/LdapUtilTest.java +++ b/managed/src/test/java/com/yugabyte/yw/common/LdapUtilTest.java @@ -109,7 +109,8 @@ public void testAuthViaLDAPSimpleBindSuccess() throws Exception { "base-dn", "", false, - false)); + false, + Role.ReadOnly)); assertNotNull(user); assertEquals("test-user", user.getEmail()); @@ -149,7 +150,8 @@ public void testAuthViaLDAPSimpleBindFailure() throws Exception { "base-dn", "", false, - false))); + false, + Role.ReadOnly))); } @Test @@ -188,7 +190,8 @@ public void testAuthViaLDAPSimpleBindWithUserDeleted() throws Exception { "base-dn", "", false, - false))); + false, + Role.ReadOnly))); assertNull(Users.getByEmail(user.getEmail())); } @@ -223,7 +226,8 @@ public void testAuthViaLDAPReadRole() throws Exception { "base-dn", "", false, - false)); + false, + Role.ReadOnly)); assertNotNull(user); assertEquals(Users.Role.BackupAdmin, user.getRole()); @@ -266,7 +270,8 @@ public void testAuthViaLDAPUpdateRole() throws Exception { "base-dn", "", false, - false)); + false, + Role.ReadOnly)); assertNotNull(updatedUser); assertEquals(Users.Role.BackupAdmin, updatedUser.getRole()); @@ -309,7 +314,8 @@ public void testAuthViaLDAPWithOldUser() throws Exception { "base-dn", "", false, - false)); + false, + Role.ReadOnly)); assertEquals(user, oldUser); } @@ -387,14 +393,15 @@ public void testRoleAssignment( "base-dn", memberOfAttribute, false, - groupMappingOn)); + groupMappingOn, + Role.ConnectOnly)); if (groupMappingOn) { assertEquals(true, loggedInUser.isLdapSpecifiedRole()); if (newLdapRoleValid) { assertEquals(newLdapRole, loggedInUser.getRole()); } else { - assertEquals(Role.ReadOnly, loggedInUser.getRole()); + assertEquals(Role.ConnectOnly, loggedInUser.getRole()); } } else { if (!newLdapRoleValid) { @@ -402,7 +409,7 @@ public void testRoleAssignment( if (oldUserPresent) { assertEquals(oldUserRole, loggedInUser.getRole()); } else { - assertEquals(Role.ReadOnly, loggedInUser.getRole()); + assertEquals(Role.ConnectOnly, loggedInUser.getRole()); } } else { assertEquals(true, loggedInUser.isLdapSpecifiedRole()); @@ -442,7 +449,8 @@ public void testAuthViaLDAPWithSearchAndBindSuccess() throws Exception { "base-dn", "", false, - false)); + false, + Role.ReadOnly)); assertNotNull(user); assertEquals("test-user", user.getEmail()); @@ -478,7 +486,8 @@ public void testAuthViaLDAPWithSearchAndBindWithoutServiceAccountAndSearchAttrib "base-dn", "", false, - false))); + false, + Role.ReadOnly))); } @Test @@ -548,7 +557,8 @@ public void testAuthViaLDAPGroupSearchFilter() throws Exception { "base-dn", "", true, - true)); + true, + Role.ReadOnly)); assertNotNull(user); assertEquals(username, user.getEmail()); @@ -594,7 +604,8 @@ public void testAuthViaLDAPUserMemberOfAttribute() throws Exception { "base-dn", memberOfAttribute, false, - true)); + true, + Role.ReadOnly)); assertNotNull(user); assertEquals(username, user.getEmail()); @@ -630,7 +641,8 @@ public void testAuthWithLDAPRoleMappingServiceAccountRequired() throws Exception "base-dn", memberOfAttribute, false, - true)); + true, + Role.ReadOnly)); } catch (LdapException e) { throw new RuntimeException(e); } diff --git a/managed/src/test/java/com/yugabyte/yw/common/config/ConfKeysTest.java b/managed/src/test/java/com/yugabyte/yw/common/config/ConfKeysTest.java index 190eedb2719d..59e461aa6723 100644 --- a/managed/src/test/java/com/yugabyte/yw/common/config/ConfKeysTest.java +++ b/managed/src/test/java/com/yugabyte/yw/common/config/ConfKeysTest.java @@ -114,6 +114,7 @@ public void testRuntimeConfKeysAuto() { ConfDataType.KeyValuesSetMultimapType, "[\"yb_task:task1\",\"yb_task:task2\",\"yb_dev:*\"]"); validVals.put(ConfDataType.LdapSearchScopeEnum, "SUBTREE"); + validVals.put(ConfDataType.LdapDefaultRoleEnum, "ReadOnly"); // No data validation for these types yet Set> exceptions = diff --git a/managed/src/test/java/com/yugabyte/yw/models/UsersTest.java b/managed/src/test/java/com/yugabyte/yw/models/UsersTest.java index a27b4b2cbe1f..922fbc3e013c 100644 --- a/managed/src/test/java/com/yugabyte/yw/models/UsersTest.java +++ b/managed/src/test/java/com/yugabyte/yw/models/UsersTest.java @@ -152,7 +152,9 @@ public void testNoSensitiveDataInJson() { @Test public void testRoleUnion() { - Role[] roles = {null, Role.ReadOnly, Role.BackupAdmin, Role.Admin, Role.SuperAdmin}; + Role[] roles = { + null, Role.ConnectOnly, Role.ReadOnly, Role.BackupAdmin, Role.Admin, Role.SuperAdmin + }; for (int i = 0; i < roles.length; i++) { for (int j = 0; j < roles.length; j++) {