mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 03:46:10 +00:00
[FIX] ldap User Name (#17764)
* migrate userName to emailPrefix for ldap auth provider. * migrate userName to emailPrefix for ldap auth provider. * fix migration. * move migration to v155 * Update Ldap Authenticator * Fix Ldap Issue on Login --------- Co-authored-by: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com> Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com>
This commit is contained in:
parent
c1e20873b1
commit
bc3d4c778e
@ -454,6 +454,7 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
|
||||
connectionType,
|
||||
conf.getMigrationConfiguration().getExtensionPath(),
|
||||
conf.getPipelineServiceClientConfiguration(),
|
||||
conf.getAuthenticationConfiguration(),
|
||||
false);
|
||||
migrationWorkflow.loadMigrations();
|
||||
migrationWorkflow.validateMigrationsForServer();
|
||||
|
@ -9,6 +9,7 @@ import java.util.Map;
|
||||
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 class MigrationProcessImpl implements MigrationProcess {
|
||||
this.pipelineServiceClient =
|
||||
PipelineServiceClientFactory.createPipelineServiceClient(
|
||||
this.migrationFile.pipelineServiceClientConfiguration);
|
||||
this.authenticationConfiguration = migrationFile.authenticationConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,6 +18,7 @@ import org.jdbi.v3.core.Handle;
|
||||
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 class 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 class MigrationWorkflow {
|
||||
this.connectionType = connectionType;
|
||||
this.extensionSQLScriptRootPath = extensionSQLScriptRootPath;
|
||||
this.pipelineServiceClientConfiguration = pipelineServiceClientConfiguration;
|
||||
this.authenticationConfiguration = authenticationConfiguration;
|
||||
}
|
||||
|
||||
public void loadMigrations() {
|
||||
@ -64,7 +68,8 @@ public class MigrationWorkflow {
|
||||
nativeSQLScriptRootPath,
|
||||
connectionType,
|
||||
extensionSQLScriptRootPath,
|
||||
pipelineServiceClientConfiguration);
|
||||
pipelineServiceClientConfiguration,
|
||||
authenticationConfiguration);
|
||||
// Filter Migrations to Be Run
|
||||
this.migrations = filterAndGetMigrationsToRun(availableMigrations);
|
||||
}
|
||||
@ -83,10 +88,15 @@ public class MigrationWorkflow {
|
||||
String nativeSQLScriptRootPath,
|
||||
ConnectionType connectionType,
|
||||
String extensionSQLScriptRootPath,
|
||||
PipelineServiceClientConfiguration pipelineServiceClientConfiguration) {
|
||||
PipelineServiceClientConfiguration pipelineServiceClientConfiguration,
|
||||
AuthenticationConfiguration authenticationConfiguration) {
|
||||
List<MigrationFile> 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 class MigrationWorkflow {
|
||||
// Otherwise, fetch the extension migrations and sort the executions
|
||||
List<MigrationFile> availableExtensionMigrations =
|
||||
getMigrationFilesFromPath(
|
||||
extensionSQLScriptRootPath, connectionType, pipelineServiceClientConfiguration, true);
|
||||
extensionSQLScriptRootPath,
|
||||
connectionType,
|
||||
pipelineServiceClientConfiguration,
|
||||
authenticationConfiguration,
|
||||
true);
|
||||
|
||||
/*
|
||||
If we create migrations version as:
|
||||
@ -114,6 +128,7 @@ public class MigrationWorkflow {
|
||||
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 class MigrationWorkflow {
|
||||
migrationDAO,
|
||||
connectionType,
|
||||
pipelineServiceClientConfiguration,
|
||||
authenticationConfiguration,
|
||||
isExtension))
|
||||
.sorted()
|
||||
.toList();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import org.flywaydb.core.internal.resource.filesystem.FileSystemResource;
|
||||
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<MigrationFile> {
|
||||
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 class MigrationFile implements Comparable<MigrationFile> {
|
||||
MigrationDAO migrationDAO,
|
||||
ConnectionType connectionType,
|
||||
PipelineServiceClientConfiguration pipelineServiceClientConfiguration,
|
||||
AuthenticationConfiguration authenticationConfiguration,
|
||||
Boolean isExtension) {
|
||||
this.dir = dir;
|
||||
this.isExtension = isExtension;
|
||||
@ -45,6 +49,7 @@ public class MigrationFile implements Comparable<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<>();
|
||||
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -261,7 +261,8 @@ public class BasicAuthenticator implements AuthenticatorHandler {
|
||||
|
||||
// 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 class BasicAuthenticator implements AuthenticatorHandler {
|
||||
|
||||
@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 class BasicAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
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<String, String> storedData =
|
||||
(LinkedHashMap<String, String>) storedUser.getAuthenticationMechanism().getConfig();
|
||||
(LinkedHashMap<String, String>) 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"));
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import static org.openmetadata.service.exception.CatalogExceptionMessage.LDAP_MI
|
||||
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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
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 @@ public class LdapAuthenticator implements AuthenticatorHandler {
|
||||
|
||||
@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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
* 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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
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 class LdapAuthenticator implements AuthenticatorHandler {
|
||||
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 @@ public class LdapAuthenticator implements AuthenticatorHandler {
|
||||
.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 @@ public class LdapAuthenticator implements AuthenticatorHandler {
|
||||
* @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 @@ public class LdapAuthenticator implements AuthenticatorHandler {
|
||||
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(
|
||||
|
@ -33,7 +33,7 @@ public class NoopAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@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 class NoopAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@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 class NoopAuthenticator implements AuthenticatorHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public User lookUserInProvider(String userName) {
|
||||
public User lookUserInProvider(String email, String pwd) {
|
||||
throw new CustomExceptionMessage(
|
||||
Response.Status.FORBIDDEN,
|
||||
AUTHENTICATOR_OPERATION_NOT_SUPPORTED,
|
||||
|
@ -618,6 +618,7 @@ public class OpenMetadataOperations implements Callable<Integer> {
|
||||
connType,
|
||||
extensionSQLScriptRootPath,
|
||||
config.getPipelineServiceClientConfiguration(),
|
||||
config.getAuthenticationConfiguration(),
|
||||
force);
|
||||
workflow.loadMigrations();
|
||||
workflow.printMigrationInfo();
|
||||
|
@ -121,7 +121,7 @@ public class EmailUtil {
|
||||
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 class EmailUtil {
|
||||
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 class EmailUtil {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
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 abstract class OpenMetadataApplicationTest {
|
||||
nativeMigrationScriptsLocation,
|
||||
extensionMigrationScripsLocation,
|
||||
null,
|
||||
null,
|
||||
false);
|
||||
createIndices();
|
||||
APP.before();
|
||||
@ -221,6 +223,7 @@ public abstract class OpenMetadataApplicationTest {
|
||||
String nativeMigrationSQLPath,
|
||||
String extensionSQLScriptRootPath,
|
||||
PipelineServiceClientConfiguration pipelineServiceClientConfiguration,
|
||||
AuthenticationConfiguration authenticationConfiguration,
|
||||
boolean forceMigrations) {
|
||||
DatasourceConfig.initialize(connType.label);
|
||||
MigrationWorkflow workflow =
|
||||
@ -230,6 +233,7 @@ public abstract class OpenMetadataApplicationTest {
|
||||
connType,
|
||||
extensionSQLScriptRootPath,
|
||||
pipelineServiceClientConfiguration,
|
||||
authenticationConfiguration,
|
||||
forceMigrations);
|
||||
// Initialize search repository
|
||||
SearchRepository searchRepository =
|
||||
|
@ -28,7 +28,7 @@ public class MigrationWorkflowTest extends OpenMetadataApplicationTest {
|
||||
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 class MigrationWorkflowTest extends OpenMetadataApplicationTest {
|
||||
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 class MigrationWorkflowTest extends OpenMetadataApplicationTest {
|
||||
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 @@ public class MigrationWorkflowTest extends OpenMetadataApplicationTest {
|
||||
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<MigrationFile> 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"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user