diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java index 40c896f3496a..80fe64f77d6d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java @@ -454,6 +454,7 @@ private void validateMigrations(Jdbi jdbi, OpenMetadataApplicationConfig conf) connectionType, conf.getMigrationConfiguration().getExtensionPath(), conf.getPipelineServiceClientConfiguration(), + conf.getAuthenticationConfiguration(), false); migrationWorkflow.loadMigrations(); migrationWorkflow.validateMigrationsForServer(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationProcessImpl.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationProcessImpl.java index 98e06643f1fd..75897e005539 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationProcessImpl.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationProcessImpl.java @@ -9,6 +9,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.core.Handle; +import org.openmetadata.schema.api.security.AuthenticationConfiguration; import org.openmetadata.sdk.PipelineServiceClientInterface; import org.openmetadata.service.clients.pipeline.PipelineServiceClientFactory; import org.openmetadata.service.jdbi3.CollectionDAO; @@ -24,6 +25,7 @@ public class MigrationProcessImpl implements MigrationProcess { protected CollectionDAO collectionDAO; protected Handle handle; protected PipelineServiceClientInterface pipelineServiceClient; + protected AuthenticationConfiguration authenticationConfiguration; private final MigrationFile migrationFile; public @Getter MigrationContext context; @@ -40,6 +42,7 @@ public void initialize(Handle handle) { this.pipelineServiceClient = PipelineServiceClientFactory.createPipelineServiceClient( this.migrationFile.pipelineServiceClientConfiguration); + this.authenticationConfiguration = migrationFile.authenticationConfiguration; } @Override diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationWorkflow.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationWorkflow.java index 7c2aba84781e..d40083b32da4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationWorkflow.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/api/MigrationWorkflow.java @@ -18,6 +18,7 @@ import org.jdbi.v3.core.Jdbi; import org.json.JSONObject; import org.openmetadata.schema.api.configuration.pipelineServiceClient.PipelineServiceClientConfiguration; +import org.openmetadata.schema.api.security.AuthenticationConfiguration; import org.openmetadata.service.jdbi3.MigrationDAO; import org.openmetadata.service.jdbi3.locator.ConnectionType; import org.openmetadata.service.migration.QueryStatus; @@ -35,6 +36,7 @@ public class MigrationWorkflow { private final ConnectionType connectionType; private final String extensionSQLScriptRootPath; @Getter private final PipelineServiceClientConfiguration pipelineServiceClientConfiguration; + @Getter private final AuthenticationConfiguration authenticationConfiguration; private final MigrationDAO migrationDAO; private final Jdbi jdbi; private final boolean forceMigrations; @@ -47,6 +49,7 @@ public MigrationWorkflow( ConnectionType connectionType, String extensionSQLScriptRootPath, PipelineServiceClientConfiguration pipelineServiceClientConfiguration, + AuthenticationConfiguration authenticationConfiguration, boolean forceMigrations) { this.jdbi = jdbi; this.migrationDAO = jdbi.onDemand(MigrationDAO.class); @@ -55,6 +58,7 @@ public MigrationWorkflow( this.connectionType = connectionType; this.extensionSQLScriptRootPath = extensionSQLScriptRootPath; this.pipelineServiceClientConfiguration = pipelineServiceClientConfiguration; + this.authenticationConfiguration = authenticationConfiguration; } public void loadMigrations() { @@ -64,7 +68,8 @@ public void loadMigrations() { nativeSQLScriptRootPath, connectionType, extensionSQLScriptRootPath, - pipelineServiceClientConfiguration); + pipelineServiceClientConfiguration, + authenticationConfiguration); // Filter Migrations to Be Run this.migrations = filterAndGetMigrationsToRun(availableMigrations); } @@ -83,10 +88,15 @@ public List getMigrationFiles( String nativeSQLScriptRootPath, ConnectionType connectionType, String extensionSQLScriptRootPath, - PipelineServiceClientConfiguration pipelineServiceClientConfiguration) { + PipelineServiceClientConfiguration pipelineServiceClientConfiguration, + AuthenticationConfiguration authenticationConfiguration) { List availableOMNativeMigrations = getMigrationFilesFromPath( - nativeSQLScriptRootPath, connectionType, pipelineServiceClientConfiguration, false); + nativeSQLScriptRootPath, + connectionType, + pipelineServiceClientConfiguration, + authenticationConfiguration, + false); // If we only have OM migrations, return them if (extensionSQLScriptRootPath == null || extensionSQLScriptRootPath.isEmpty()) { @@ -96,7 +106,11 @@ public List getMigrationFiles( // Otherwise, fetch the extension migrations and sort the executions List availableExtensionMigrations = getMigrationFilesFromPath( - extensionSQLScriptRootPath, connectionType, pipelineServiceClientConfiguration, true); + extensionSQLScriptRootPath, + connectionType, + pipelineServiceClientConfiguration, + authenticationConfiguration, + true); /* If we create migrations version as: @@ -114,6 +128,7 @@ public List getMigrationFilesFromPath( String path, ConnectionType connectionType, PipelineServiceClientConfiguration pipelineServiceClientConfiguration, + AuthenticationConfiguration authenticationConfiguration, Boolean isExtension) { return Arrays.stream(Objects.requireNonNull(new File(path).listFiles(File::isDirectory))) .map( @@ -123,6 +138,7 @@ public List getMigrationFilesFromPath( migrationDAO, connectionType, pipelineServiceClientConfiguration, + authenticationConfiguration, isExtension)) .sorted() .toList(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v155/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v155/Migration.java new file mode 100644 index 000000000000..b3dce3d71e30 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v155/Migration.java @@ -0,0 +1,21 @@ +package org.openmetadata.service.migration.mysql.v155; + +import static org.openmetadata.service.migration.utils.v155.MigrationUtil.updateUserNameToEmailPrefixForLdapAuthProvider; + +import lombok.SneakyThrows; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; + +public class Migration extends MigrationProcessImpl { + + public Migration(MigrationFile migrationFile) { + super(migrationFile); + } + + @Override + @SneakyThrows + public void runDataMigration() { + updateUserNameToEmailPrefixForLdapAuthProvider( + handle, collectionDAO, authenticationConfiguration, false); + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v155/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v155/Migration.java new file mode 100644 index 000000000000..5b470da547b4 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v155/Migration.java @@ -0,0 +1,21 @@ +package org.openmetadata.service.migration.postgres.v155; + +import static org.openmetadata.service.migration.utils.v155.MigrationUtil.updateUserNameToEmailPrefixForLdapAuthProvider; + +import lombok.SneakyThrows; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; + +public class Migration extends MigrationProcessImpl { + + public Migration(MigrationFile migrationFile) { + super(migrationFile); + } + + @Override + @SneakyThrows + public void runDataMigration() { + updateUserNameToEmailPrefixForLdapAuthProvider( + handle, collectionDAO, authenticationConfiguration, true); + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/MigrationFile.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/MigrationFile.java index 4a0248e885e1..66d834e94e61 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/MigrationFile.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/MigrationFile.java @@ -14,6 +14,7 @@ import org.flywaydb.core.internal.sqlscript.SqlStatementIterator; import org.flywaydb.database.mysql.MySQLParser; import org.openmetadata.schema.api.configuration.pipelineServiceClient.PipelineServiceClientConfiguration; +import org.openmetadata.schema.api.security.AuthenticationConfiguration; import org.openmetadata.service.jdbi3.MigrationDAO; import org.openmetadata.service.jdbi3.locator.ConnectionType; import org.openmetadata.service.util.EntityUtil; @@ -23,6 +24,8 @@ public class MigrationFile implements Comparable { public final String version; public final ConnectionType connectionType; public final PipelineServiceClientConfiguration pipelineServiceClientConfiguration; + public final AuthenticationConfiguration authenticationConfiguration; + public final File dir; public final Boolean isExtension; public final String dbPackageName; @@ -38,6 +41,7 @@ public MigrationFile( MigrationDAO migrationDAO, ConnectionType connectionType, PipelineServiceClientConfiguration pipelineServiceClientConfiguration, + AuthenticationConfiguration authenticationConfiguration, Boolean isExtension) { this.dir = dir; this.isExtension = isExtension; @@ -45,6 +49,7 @@ public MigrationFile( this.connectionType = connectionType; this.migrationDAO = migrationDAO; this.pipelineServiceClientConfiguration = pipelineServiceClientConfiguration; + this.authenticationConfiguration = authenticationConfiguration; this.dbPackageName = connectionType == ConnectionType.MYSQL ? "mysql" : "postgres"; versionNumbers = convertToNumber(version); schemaChanges = new ArrayList<>(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v155/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v155/MigrationUtil.java new file mode 100644 index 000000000000..28967d860bfe --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v155/MigrationUtil.java @@ -0,0 +1,60 @@ +package org.openmetadata.service.migration.utils.v155; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.jdbi.v3.core.Handle; +import org.openmetadata.schema.api.security.AuthenticationConfiguration; +import org.openmetadata.schema.entity.teams.User; +import org.openmetadata.schema.services.connections.metadata.AuthProvider; +import org.openmetadata.schema.utils.EntityInterfaceUtil; +import org.openmetadata.service.jdbi3.CollectionDAO; +import org.openmetadata.service.util.JsonUtils; + +@Slf4j +public class MigrationUtil { + public static void updateUserNameToEmailPrefixForLdapAuthProvider( + Handle handle, + CollectionDAO daoCollection, + AuthenticationConfiguration config, + boolean postgres) { + if (config.getProvider().equals(AuthProvider.LDAP)) { + LOG.info("Starting migration username -> email prefix"); + int total = daoCollection.userDAO().listTotalCount(); + int offset = 0; + int limit = 200; + while (offset < total) { + List userEntities = daoCollection.userDAO().listAfterWithOffset(limit, offset); + for (String json : userEntities) { + User userEntity = JsonUtils.readValue(json, User.class); + String email = userEntity.getEmail(); + String emailPrefix = email.substring(0, email.indexOf("@")); + userEntity.setFullyQualifiedName(EntityInterfaceUtil.quoteName(emailPrefix)); + userEntity.setName(emailPrefix); + + daoCollection.userDAO().update(userEntity); + } + offset = offset + limit; + } + + updateUserEntityNameHash(handle, postgres); + LOG.info("Completed migrating username -> email prefix"); + } + } + + public static void updateUserEntityNameHash(Handle handle, boolean postgres) { + String updateNameHashSql; + if (postgres) { + updateNameHashSql = "UPDATE user_entity SET nameHash = MD5(json ->> 'fullyQualifiedName');"; + } else { + updateNameHashSql = + "UPDATE user_entity SET nameHash = MD5(JSON_UNQUOTE(JSON_EXTRACT(json, '$.fullyQualifiedName')));"; + } + + try { + handle.execute(updateNameHashSql); + LOG.info("Successfully updated nameHash for all user entities."); + } catch (Exception e) { + LOG.error("Error updating nameHash field", e); + } + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/AuthenticatorHandler.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/AuthenticatorHandler.java index b514b264e68a..7c77a0e88fc6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/AuthenticatorHandler.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/AuthenticatorHandler.java @@ -33,13 +33,13 @@ public interface AuthenticatorHandler { void checkIfLoginBlocked(String userName); - void recordFailedLoginAttempt(String providedIdentity, User user) + void recordFailedLoginAttempt(String email, String userName) throws TemplateException, IOException; - void validatePassword(String providedIdentity, User storedUser, String reqPassword) + void validatePassword(String providedIdentity, String reqPassword, User omUser) throws TemplateException, IOException; - User lookUserInProvider(String userName); + User lookUserInProvider(String email, String pwd) throws TemplateException, IOException; default User registerUser(RegistrationRequest registrationRequest) { throw new CustomExceptionMessage( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java index e6c2041c5f60..a99a7eeab87a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java @@ -261,7 +261,8 @@ public void resetUserPasswordWithToken(UriInfo uriInfo, PasswordResetRequest req // Update user about Password Change try { - sendAccountStatus(storedUser, "Update Password", "Change Successful"); + sendAccountStatus( + storedUser.getName(), storedUser.getEmail(), "Update Password", "Change Successful"); } catch (TemplateException ex) { LOG.error("Error in sending Password Change Mail to User. Reason : " + ex.getMessage(), ex); throw new CustomExceptionMessage(424, FAILED_SEND_EMAIL, EMAIL_SENDING_ISSUE); @@ -466,28 +467,29 @@ public void validateEmailAlreadyExists(String email) { @Override public JwtResponse loginUser(LoginRequest loginRequest) throws IOException, TemplateException { - String userName = loginRequest.getEmail(); - checkIfLoginBlocked(userName); - User storedUser = lookUserInProvider(userName); - validatePassword(userName, storedUser, loginRequest.getPassword()); + String email = loginRequest.getEmail(); + checkIfLoginBlocked(email); + User storedUser = lookUserInProvider(email, loginRequest.getPassword()); + validatePassword(email, loginRequest.getPassword(), storedUser); return getJwtResponse(storedUser, SecurityUtil.getLoginConfiguration().getJwtTokenExpiryTime()); } @Override - public void checkIfLoginBlocked(String userName) { - if (loginAttemptCache.isLoginBlocked(userName)) { + public void checkIfLoginBlocked(String email) { + if (loginAttemptCache.isLoginBlocked(email)) { throw new AuthenticationException(MAX_FAILED_LOGIN_ATTEMPT); } } @Override - public void recordFailedLoginAttempt(String providedIdentity, User storedUser) + public void recordFailedLoginAttempt(String email, String userName) throws TemplateException, IOException { - loginAttemptCache.recordFailedLogin(providedIdentity); - int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(providedIdentity); + loginAttemptCache.recordFailedLogin(email); + int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(email); if (failedLoginAttempt == SecurityUtil.getLoginConfiguration().getMaxLoginFailAttempts()) { sendAccountStatus( - storedUser, + userName, + email, "Multiple Failed Login Attempts.", String.format( "Someone is trying to access your account. Login is Blocked for %s minutes. Please change your password.", @@ -495,35 +497,35 @@ public void recordFailedLoginAttempt(String providedIdentity, User storedUser) } } - public void validatePassword(String providedIdentity, User storedUser, String reqPassword) + public void validatePassword(String providedIdentity, String reqPassword, User omUser) throws TemplateException, IOException { // when basic auth is enabled and the user is created through the API without password, the // stored auth mechanism // for the user is null - if (storedUser.getAuthenticationMechanism() == null) { + if (omUser.getAuthenticationMechanism() == null) { throw new AuthenticationException(INVALID_USERNAME_PASSWORD); } @SuppressWarnings("unchecked") LinkedHashMap storedData = - (LinkedHashMap) storedUser.getAuthenticationMechanism().getConfig(); + (LinkedHashMap) omUser.getAuthenticationMechanism().getConfig(); String storedHashPassword = storedData.get("password"); if (!BCrypt.verifyer().verify(reqPassword.toCharArray(), storedHashPassword).verified) { // record Failed Login Attempts - recordFailedLoginAttempt(providedIdentity, storedUser); + recordFailedLoginAttempt(omUser.getEmail(), omUser.getName()); throw new AuthenticationException(INVALID_USERNAME_PASSWORD); } } @Override - public User lookUserInProvider(String userName) { + public User lookUserInProvider(String email, String pwd) { User storedUser = null; try { - if (userName.contains("@")) { + if (email.contains("@")) { // lookup by User Email storedUser = userRepository.getByEmail( null, - userName, + email, new EntityUtil.Fields( Set.of(USER_PROTECTED_FIELDS, "roles"), "authenticationMechanism,roles")); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java index 005910e0de45..9531d76c1f97 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java @@ -12,6 +12,8 @@ import static org.openmetadata.service.exception.CatalogExceptionMessage.MAX_FAILED_LOGIN_ATTEMPT; import static org.openmetadata.service.exception.CatalogExceptionMessage.MULTIPLE_EMAIL_ENTRIES; import static org.openmetadata.service.exception.CatalogExceptionMessage.PASSWORD_RESET_TOKEN_EXPIRED; +import static org.openmetadata.service.exception.CatalogExceptionMessage.SELF_SIGNUP_DISABLED_MESSAGE; +import static org.openmetadata.service.exception.CatalogExceptionMessage.SELF_SIGNUP_NOT_ENABLED; import static org.openmetadata.service.util.UserUtil.getRoleListFromUser; import com.fasterxml.jackson.core.JsonProcessingException; @@ -85,6 +87,7 @@ public class LdapAuthenticator implements AuthenticatorHandler { private LoginAttemptCache loginAttemptCache; private LdapConfiguration ldapConfiguration; private LDAPConnectionPool ldapLookupConnectionPool; + private boolean isSelfSignUpEnabled; @Override public void init(OpenMetadataApplicationConfig config) { @@ -100,6 +103,7 @@ public void init(OpenMetadataApplicationConfig config) { this.tokenRepository = Entity.getTokenRepository(); this.ldapConfiguration = config.getAuthenticationConfiguration().getLdapConfiguration(); this.loginAttemptCache = new LoginAttemptCache(); + this.isSelfSignUpEnabled = config.getAuthenticationConfiguration().getEnableSelfSignup(); } private LDAPConnectionPool getLdapConnectionPool(LdapConfiguration ldapConfiguration) { @@ -138,10 +142,9 @@ private LDAPConnectionPool getLdapConnectionPool(LdapConfiguration ldapConfigura @Override public JwtResponse loginUser(LoginRequest loginRequest) throws IOException, TemplateException { - checkIfLoginBlocked(loginRequest.getEmail()); - User storedUser = lookUserInProvider(loginRequest.getEmail()); - validatePassword(storedUser.getEmail(), storedUser, loginRequest.getPassword()); - User omUser = checkAndCreateUser(storedUser.getEmail(), storedUser.getName()); + String email = loginRequest.getEmail(); + checkIfLoginBlocked(email); + User omUser = lookUserInProvider(email, loginRequest.getPassword()); return getJwtResponse(omUser, SecurityUtil.getLoginConfiguration().getJwtTokenExpiryTime()); } @@ -149,21 +152,25 @@ public JwtResponse loginUser(LoginRequest loginRequest) throws IOException, Temp * Check if the user exists in database by userName, if user exist, reassign roles for user according to it's ldap * group else, create a new user and assign roles according to it's ldap group * - * @param email email address of user - * @param name userName of user + * @param userDn userDn from LDAP + * @param email Email of the User * @return user info * @author Eric Wen@2023-07-16 17:06:43 */ - private User checkAndCreateUser(String email, String name) throws IOException { + private User checkAndCreateUser(String userDn, String email, String userName) throws IOException { // Check if the user exists in OM Database try { User omUser = - userRepository.getByName(null, name, userRepository.getFields("id,name,email,roles")); - getRoleForLdap(omUser, Boolean.TRUE); + userRepository.getByEmail(null, email, userRepository.getFields("id,name,email,roles")); + getRoleForLdap(userDn, omUser, Boolean.TRUE); return omUser; } catch (EntityNotFoundException ex) { - // User does not exist - return userRepository.create(null, getUserForLdap(email, name)); + if (isSelfSignUpEnabled) { + return userRepository.create(null, getUserForLdap(userDn, email, userName)); + } else { + throw new CustomExceptionMessage( + INTERNAL_SERVER_ERROR, SELF_SIGNUP_NOT_ENABLED, SELF_SIGNUP_DISABLED_MESSAGE); + } } } @@ -175,13 +182,14 @@ public void checkIfLoginBlocked(String email) { } @Override - public void recordFailedLoginAttempt(String providedIdentity, User storedUser) + public void recordFailedLoginAttempt(String email, String userName) throws TemplateException, IOException { - loginAttemptCache.recordFailedLogin(providedIdentity); - int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(providedIdentity); + loginAttemptCache.recordFailedLogin(email); + int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(email); if (failedLoginAttempt == SecurityUtil.getLoginConfiguration().getMaxLoginFailAttempts()) { EmailUtil.sendAccountStatus( - storedUser, + userName, + email, "Multiple Failed Login Attempts.", String.format( "Someone is tried accessing your account. Login is Blocked for %s seconds.", @@ -190,12 +198,12 @@ public void recordFailedLoginAttempt(String providedIdentity, User storedUser) } @Override - public void validatePassword(String providedIdentity, User storedUser, String reqPassword) + public void validatePassword(String userDn, String reqPassword, User dummy) throws TemplateException, IOException { // performed in LDAP , the storedUser's name set as DN of the User in Ldap BindResult bindingResult = null; try { - bindingResult = ldapLookupConnectionPool.bind(storedUser.getName(), reqPassword); + bindingResult = ldapLookupConnectionPool.bind(userDn, reqPassword); if (Objects.equals(bindingResult.getResultCode().getName(), ResultCode.SUCCESS.getName())) { return; } @@ -203,7 +211,7 @@ public void validatePassword(String providedIdentity, User storedUser, String re if (bindingResult != null && Objects.equals( bindingResult.getResultCode().getName(), ResultCode.INVALID_CREDENTIALS.getName())) { - recordFailedLoginAttempt(providedIdentity, storedUser); + recordFailedLoginAttempt(dummy.getEmail(), dummy.getName()); throw new CustomExceptionMessage( UNAUTHORIZED, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD); } @@ -218,7 +226,20 @@ public void validatePassword(String providedIdentity, User storedUser, String re } @Override - public User lookUserInProvider(String email) { + public User lookUserInProvider(String email, String pwd) throws TemplateException, IOException { + String userDN = getUserDnFromLdap(email); + + if (!nullOrEmpty(userDN)) { + User dummy = getUserForLdap(email); + validatePassword(userDN, pwd, dummy); + return checkAndCreateUser(userDN, email, dummy.getName()); + } + + throw new CustomExceptionMessage( + INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD); + } + + private String getUserDnFromLdap(String email) { try { Filter emailFilter = Filter.createEqualityFilter(ldapConfiguration.getMailAttributeName(), email); @@ -237,8 +258,10 @@ public User lookUserInProvider(String email) { Attribute emailAttr = searchResultEntry.getAttribute(ldapConfiguration.getMailAttributeName()); - if (!CommonUtil.nullOrEmpty(userDN) && emailAttr != null) { - return getUserForLdap(email).withName(userDN.toLowerCase()); + if (!CommonUtil.nullOrEmpty(userDN) + && emailAttr != null + && email.equalsIgnoreCase(emailAttr.getValue())) { + return userDN; } else { throw new CustomExceptionMessage(FORBIDDEN, INVALID_USER_OR_PASSWORD, LDAP_MISSING_ATTR); } @@ -247,7 +270,7 @@ public User lookUserInProvider(String email) { INTERNAL_SERVER_ERROR, MULTIPLE_EMAIL_ENTRIES, MULTIPLE_EMAIL_ENTRIES); } else { throw new CustomExceptionMessage( - INTERNAL_SERVER_ERROR, MULTIPLE_EMAIL_ENTRIES, INVALID_EMAIL_PASSWORD); + INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD); } } catch (LDAPException ex) { throw new CustomExceptionMessage(INTERNAL_SERVER_ERROR, "LDAP_ERROR", ex.getMessage()); @@ -262,14 +285,14 @@ userName, new CreateUser().withName(userName).withEmail(email).withIsBot(false)) .withAuthenticationMechanism(null); } - private User getUserForLdap(String email, String userName) { + private User getUserForLdap(String ldapUserDn, String email, String userName) { User user = UserUtil.getUser( userName, new CreateUser().withName(userName).withEmail(email).withIsBot(false)) .withIsEmailVerified(false) .withAuthenticationMechanism(null); try { - getRoleForLdap(user, false); + getRoleForLdap(ldapUserDn, user, false); } catch (JsonProcessingException e) { LOG.error( "Failed to assign roles from LDAP to OpenMetadata for the user {} due to {}", @@ -286,7 +309,8 @@ userName, new CreateUser().withName(userName).withEmail(email).withIsBot(false)) * @param reAssign flag to decide whether to reassign roles * @author Eric Wen@2023-07-16 17:23:57 */ - private void getRoleForLdap(User user, Boolean reAssign) throws JsonProcessingException { + private void getRoleForLdap(String userDn, User user, Boolean reAssign) + throws JsonProcessingException { // Get user's groups from LDAP server using the DN of the user try { Filter groupFilter = @@ -294,8 +318,7 @@ private void getRoleForLdap(User user, Boolean reAssign) throws JsonProcessingEx ldapConfiguration.getGroupAttributeName(), ldapConfiguration.getGroupAttributeValue()); Filter groupMemberAttr = - Filter.createEqualityFilter( - ldapConfiguration.getGroupMemberAttributeName(), user.getName()); + Filter.createEqualityFilter(ldapConfiguration.getGroupMemberAttributeName(), userDn); Filter groupAndMemberFilter = Filter.createANDFilter(groupFilter, groupMemberAttr); SearchRequest searchRequest = new SearchRequest( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/NoopAuthenticator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/NoopAuthenticator.java index de5be6cd5b9e..bd3826de0eb2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/NoopAuthenticator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/NoopAuthenticator.java @@ -33,7 +33,7 @@ public void checkIfLoginBlocked(String userName) { } @Override - public void recordFailedLoginAttempt(String providedIdentity, User user) { + public void recordFailedLoginAttempt(String providedIdentity, String userName) { throw new CustomExceptionMessage( Response.Status.FORBIDDEN, AUTHENTICATOR_OPERATION_NOT_SUPPORTED, @@ -41,7 +41,7 @@ public void recordFailedLoginAttempt(String providedIdentity, User user) { } @Override - public void validatePassword(String providedIdentity, User storedUser, String reqPassword) { + public void validatePassword(String providedIdentity, String reqPassword, User storedUser) { throw new CustomExceptionMessage( Response.Status.FORBIDDEN, AUTHENTICATOR_OPERATION_NOT_SUPPORTED, @@ -49,7 +49,7 @@ public void validatePassword(String providedIdentity, User storedUser, String re } @Override - public User lookUserInProvider(String userName) { + public User lookUserInProvider(String email, String pwd) { throw new CustomExceptionMessage( Response.Status.FORBIDDEN, AUTHENTICATOR_OPERATION_NOT_SUPPORTED, diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java index cd81cb82752f..754867160730 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java @@ -618,6 +618,7 @@ private void validateAndRunSystemDataMigrations(boolean force) { connType, extensionSQLScriptRootPath, config.getPipelineServiceClientConfiguration(), + config.getAuthenticationConfiguration(), force); workflow.loadMigrations(); workflow.printMigrationInfo(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/email/EmailUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/email/EmailUtil.java index a87018d79810..ee3ab278f213 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/email/EmailUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/email/EmailUtil.java @@ -121,7 +121,7 @@ private static Mailer createMailer(SmtpSettings smtpServerSettings) { return null; } - public static void sendAccountStatus(User user, String action, String status) + public static void sendAccountStatus(String userName, String email, String action, String status) throws IOException, TemplateException { if (Boolean.TRUE.equals(getSmtpSettings().getEnableSmtpServer())) { @@ -129,7 +129,7 @@ public static void sendAccountStatus(User user, String action, String status) new TemplatePopulatorBuilder() .add(ENTITY, getSmtpSettings().getEmailingEntity()) .add(SUPPORT_URL, getSmtpSettings().getSupportUrl()) - .add(USERNAME, user.getName()) + .add(USERNAME, userName) .add(ACTION_KEY, action) .add(ACTION_STATUS_KEY, status) .build(); @@ -137,11 +137,11 @@ public static void sendAccountStatus(User user, String action, String status) sendMail( getAccountStatusChangeSubject(), templatePopulator, - user.getEmail(), + email, ACCOUNT_ACTIVITY_CHANGE_TEMPLATE, true); } else { - LOG.warn(EMAIL_IGNORE_MSG, user.getEmail()); + LOG.warn(EMAIL_IGNORE_MSG, userName); } } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/OpenMetadataApplicationTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/OpenMetadataApplicationTest.java index 897ee0af5197..14bd2bdd5e28 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/OpenMetadataApplicationTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/OpenMetadataApplicationTest.java @@ -48,6 +48,7 @@ import org.junit.jupiter.api.TestInstance; import org.openmetadata.common.utils.CommonUtil; import org.openmetadata.schema.api.configuration.pipelineServiceClient.PipelineServiceClientConfiguration; +import org.openmetadata.schema.api.security.AuthenticationConfiguration; import org.openmetadata.schema.service.configuration.elasticsearch.ElasticSearchConfiguration; import org.openmetadata.schema.type.IndexMappingLanguage; import org.openmetadata.service.jdbi3.CollectionDAO; @@ -208,6 +209,7 @@ public void createApplication() throws Exception { nativeMigrationScriptsLocation, extensionMigrationScripsLocation, null, + null, false); createIndices(); APP.before(); @@ -221,6 +223,7 @@ public static void validateAndRunSystemDataMigrations( String nativeMigrationSQLPath, String extensionSQLScriptRootPath, PipelineServiceClientConfiguration pipelineServiceClientConfiguration, + AuthenticationConfiguration authenticationConfiguration, boolean forceMigrations) { DatasourceConfig.initialize(connType.label); MigrationWorkflow workflow = @@ -230,6 +233,7 @@ public static void validateAndRunSystemDataMigrations( connType, extensionSQLScriptRootPath, pipelineServiceClientConfiguration, + authenticationConfiguration, forceMigrations); // Initialize search repository SearchRepository searchRepository = diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/util/MigrationWorkflowTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/util/MigrationWorkflowTest.java index 9bf3699aa8d3..5b65dc330901 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/util/MigrationWorkflowTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/util/MigrationWorkflowTest.java @@ -28,7 +28,7 @@ public static void setup() { migrationWorkflow = spy( new MigrationWorkflow( - jdbi, "nativePath", ConnectionType.MYSQL, "extensionPath", null, false)); + jdbi, "nativePath", ConnectionType.MYSQL, "extensionPath", null, null, false)); omMigrationList = List.of( @@ -37,18 +37,21 @@ public static void setup() { null, ConnectionType.MYSQL, migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration(), false), new MigrationFile( new File("/bootstrap/sql/migrations/native/1.2.0"), null, ConnectionType.MYSQL, migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration(), false), new MigrationFile( new File("/bootstrap/sql/migrations/native/1.2.1"), null, ConnectionType.MYSQL, migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration(), false)); collateMigrationList = @@ -58,12 +61,14 @@ public static void setup() { null, ConnectionType.MYSQL, migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration(), true), new MigrationFile( new File("/bootstrap-collate/sql/migrations/native/1.2.2-collate"), null, ConnectionType.MYSQL, migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration(), true)); } @@ -72,18 +77,19 @@ void test_getMigrationFiles() { Mockito.doReturn(omMigrationList) .when(migrationWorkflow) .getMigrationFilesFromPath( - eq("nativePath"), any(ConnectionType.class), eq(null), eq(false)); + eq("nativePath"), any(ConnectionType.class), eq(null), eq(null), eq(false)); Mockito.doReturn(collateMigrationList) .when(migrationWorkflow) .getMigrationFilesFromPath( - eq("extensionPath"), any(ConnectionType.class), eq(null), eq(true)); + eq("extensionPath"), any(ConnectionType.class), eq(null), eq(null), eq(true)); List foundList = migrationWorkflow.getMigrationFiles( "nativePath", ConnectionType.MYSQL, "extensionPath", - migrationWorkflow.getPipelineServiceClientConfiguration()); + migrationWorkflow.getPipelineServiceClientConfiguration(), + migrationWorkflow.getAuthenticationConfiguration()); assertEquals( List.of("1.1.0", "1.1.0-collate", "1.2.0", "1.2.1", "1.2.2-collate"),