mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +00:00 
			
		
		
		
	LDAP login issues fixed, added retry mechanism (#23690)
* LDAP login issues fixed, added retry mechanism * Remove unwanted comments * Addressed copilot comments * Added integration TCs for ldap
This commit is contained in:
		
							parent
							
								
									e23638da60
								
							
						
					
					
						commit
						9d0a739c69
					
				@ -1,5 +1,8 @@
 | 
				
			|||||||
package org.openmetadata.service.security.auth;
 | 
					package org.openmetadata.service.security.auth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
 | 
				
			||||||
 | 
					import static jakarta.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE;
 | 
				
			||||||
 | 
					import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED;
 | 
				
			||||||
import static org.openmetadata.service.security.SecurityUtil.writeJsonResponse;
 | 
					import static org.openmetadata.service.security.SecurityUtil.writeJsonResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
@ -16,6 +19,7 @@ import org.openmetadata.schema.auth.TokenRefreshRequest;
 | 
				
			|||||||
import org.openmetadata.schema.utils.JsonUtils;
 | 
					import org.openmetadata.schema.utils.JsonUtils;
 | 
				
			||||||
import org.openmetadata.service.OpenMetadataApplicationConfig;
 | 
					import org.openmetadata.service.OpenMetadataApplicationConfig;
 | 
				
			||||||
import org.openmetadata.service.auth.JwtResponse;
 | 
					import org.openmetadata.service.auth.JwtResponse;
 | 
				
			||||||
 | 
					import org.openmetadata.service.exception.CustomExceptionMessage;
 | 
				
			||||||
import org.openmetadata.service.security.AuthServeletHandler;
 | 
					import org.openmetadata.service.security.AuthServeletHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
@ -109,9 +113,21 @@ public class LdapAuthServletHandler implements AuthServeletHandler {
 | 
				
			|||||||
      resp.setContentType("application/json");
 | 
					      resp.setContentType("application/json");
 | 
				
			||||||
      writeJsonResponse(resp, JsonUtils.pojoToJson(responseToClient));
 | 
					      writeJsonResponse(resp, JsonUtils.pojoToJson(responseToClient));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } catch (CustomExceptionMessage e) {
 | 
				
			||||||
 | 
					      LOG.error("LDAP login error: {}", e.getMessage(), e);
 | 
				
			||||||
 | 
					      int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 | 
				
			||||||
 | 
					      if (e.getResponse().getStatus() == SERVICE_UNAVAILABLE.getStatusCode()) {
 | 
				
			||||||
 | 
					        statusCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
 | 
				
			||||||
 | 
					      } else if (e.getResponse().getStatus() == UNAUTHORIZED.getStatusCode()) {
 | 
				
			||||||
 | 
					        statusCode = HttpServletResponse.SC_UNAUTHORIZED;
 | 
				
			||||||
 | 
					      } else if (e.getResponse().getStatus() == FORBIDDEN.getStatusCode()) {
 | 
				
			||||||
 | 
					        statusCode = HttpServletResponse.SC_FORBIDDEN;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      sendError(resp, statusCode, e.getMessage());
 | 
				
			||||||
    } catch (Exception e) {
 | 
					    } catch (Exception e) {
 | 
				
			||||||
      LOG.error("Error handling LDAP login", e);
 | 
					      LOG.error("Unexpected error handling LDAP login", e);
 | 
				
			||||||
      sendError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
 | 
					      sendError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Authentication service error");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -225,7 +241,7 @@ public class LdapAuthServletHandler implements AuthServeletHandler {
 | 
				
			|||||||
  private void sendError(HttpServletResponse resp, int status, String message) {
 | 
					  private void sendError(HttpServletResponse resp, int status, String message) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      resp.setStatus(status);
 | 
					      resp.setStatus(status);
 | 
				
			||||||
      writeJsonResponse(resp, String.format("{\"error\":\"%s\"}", message));
 | 
					      writeJsonResponse(resp, String.format("{\"message\":\"%s\"}", message));
 | 
				
			||||||
    } catch (IOException e) {
 | 
					    } catch (IOException e) {
 | 
				
			||||||
      LOG.error("Error writing error response", e);
 | 
					      LOG.error("Error writing error response", e);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ package org.openmetadata.service.security.auth;
 | 
				
			|||||||
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
 | 
					import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
 | 
				
			||||||
import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
 | 
					import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
 | 
				
			||||||
import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 | 
					import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 | 
				
			||||||
 | 
					import static jakarta.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE;
 | 
				
			||||||
import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED;
 | 
					import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED;
 | 
				
			||||||
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
 | 
					import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
 | 
				
			||||||
import static org.openmetadata.schema.auth.TokenType.REFRESH_TOKEN;
 | 
					import static org.openmetadata.schema.auth.TokenType.REFRESH_TOKEN;
 | 
				
			||||||
@ -81,6 +82,8 @@ import org.springframework.util.CollectionUtils;
 | 
				
			|||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class LdapAuthenticator implements AuthenticatorHandler {
 | 
					public class LdapAuthenticator implements AuthenticatorHandler {
 | 
				
			||||||
  static final String LDAP_ERR_MSG = "[LDAP] Issue in creating a LookUp Connection ";
 | 
					  static final String LDAP_ERR_MSG = "[LDAP] Issue in creating a LookUp Connection ";
 | 
				
			||||||
 | 
					  private static final int MAX_RETRIES = 3;
 | 
				
			||||||
 | 
					  private static final int BASE_DELAY_MS = 500;
 | 
				
			||||||
  private RoleRepository roleRepository;
 | 
					  private RoleRepository roleRepository;
 | 
				
			||||||
  private UserRepository userRepository;
 | 
					  private UserRepository userRepository;
 | 
				
			||||||
  private TokenRepository tokenRepository;
 | 
					  private TokenRepository tokenRepository;
 | 
				
			||||||
@ -197,29 +200,74 @@ public class LdapAuthenticator implements AuthenticatorHandler {
 | 
				
			|||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
  public void validatePassword(String userDn, String reqPassword, User dummy)
 | 
					  public void validatePassword(String userDn, String reqPassword, User dummy)
 | 
				
			||||||
      throws TemplateException, IOException {
 | 
					      throws TemplateException, IOException {
 | 
				
			||||||
    // performed in LDAP , the storedUser's name set as DN of the User in Ldap
 | 
					    // Retry configuration for connection establishment
 | 
				
			||||||
 | 
					    final int maxRetries = 3;
 | 
				
			||||||
 | 
					    final int baseDelayMs = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int attempt = 1; attempt <= maxRetries; attempt++) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        performLdapBind(userDn, reqPassword, dummy);
 | 
				
			||||||
 | 
					        return; // Success
 | 
				
			||||||
 | 
					      } catch (CustomExceptionMessage e) {
 | 
				
			||||||
 | 
					        handleRetryableException(e, attempt, "LDAP connection failed for authentication");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					        SERVICE_UNAVAILABLE,
 | 
				
			||||||
 | 
					        "LDAP_CONNECTION_ERROR",
 | 
				
			||||||
 | 
					        "Unable to connect to authentication server after " + maxRetries + " attempts.");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void performLdapBind(String userDn, String reqPassword, User dummy)
 | 
				
			||||||
 | 
					      throws TemplateException, IOException {
 | 
				
			||||||
    BindResult bindingResult = null;
 | 
					    BindResult bindingResult = null;
 | 
				
			||||||
 | 
					    LDAPConnection userConnection = null;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      bindingResult = ldapLookupConnectionPool.bind(userDn, reqPassword);
 | 
					      // Create a new connection for user authentication with proper SSL/TLS support
 | 
				
			||||||
 | 
					      if (Boolean.TRUE.equals(ldapConfiguration.getSslEnabled())) {
 | 
				
			||||||
 | 
					        // LDAPS (LDAP over SSL) - same configuration as connection pool
 | 
				
			||||||
 | 
					        LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
 | 
				
			||||||
 | 
					        LdapUtil ldapUtil = new LdapUtil();
 | 
				
			||||||
 | 
					        SSLUtil sslUtil =
 | 
				
			||||||
 | 
					            new SSLUtil(ldapUtil.getLdapSSLConnection(ldapConfiguration, connectionOptions));
 | 
				
			||||||
 | 
					        userConnection =
 | 
				
			||||||
 | 
					            new LDAPConnection(
 | 
				
			||||||
 | 
					                sslUtil.createSSLSocketFactory(),
 | 
				
			||||||
 | 
					                connectionOptions,
 | 
				
			||||||
 | 
					                ldapConfiguration.getHost(),
 | 
				
			||||||
 | 
					                ldapConfiguration.getPort());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        userConnection =
 | 
				
			||||||
 | 
					            new LDAPConnection(ldapConfiguration.getHost(), ldapConfiguration.getPort());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Perform the bind operation
 | 
				
			||||||
 | 
					      bindingResult = userConnection.bind(userDn, reqPassword);
 | 
				
			||||||
      if (Objects.equals(bindingResult.getResultCode().getName(), ResultCode.SUCCESS.getName())) {
 | 
					      if (Objects.equals(bindingResult.getResultCode().getName(), ResultCode.SUCCESS.getName())) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (Exception ex) {
 | 
					
 | 
				
			||||||
      if (bindingResult != null
 | 
					      if (Objects.equals(
 | 
				
			||||||
          && Objects.equals(
 | 
					          bindingResult.getResultCode().getName(), ResultCode.INVALID_CREDENTIALS.getName())) {
 | 
				
			||||||
              bindingResult.getResultCode().getName(), ResultCode.INVALID_CREDENTIALS.getName())) {
 | 
					 | 
				
			||||||
        recordFailedLoginAttempt(dummy.getEmail(), dummy.getName());
 | 
					        recordFailedLoginAttempt(dummy.getEmail(), dummy.getName());
 | 
				
			||||||
        throw new CustomExceptionMessage(
 | 
					        throw new CustomExceptionMessage(
 | 
				
			||||||
            UNAUTHORIZED, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD);
 | 
					            UNAUTHORIZED, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (Exception ex) {
 | 
				
			||||||
 | 
					      handleLdapException(ex);
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      if (userConnection != null) {
 | 
				
			||||||
 | 
					        userConnection.close();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (bindingResult != null) {
 | 
					
 | 
				
			||||||
      throw new CustomExceptionMessage(
 | 
					    LOG.error(
 | 
				
			||||||
          INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, bindingResult.getResultCode().getName());
 | 
					        "LDAP bind failed with unexpected result: {}", bindingResult.getResultCode().getName());
 | 
				
			||||||
    } else {
 | 
					    throw new CustomExceptionMessage(
 | 
				
			||||||
      throw new CustomExceptionMessage(
 | 
					        INTERNAL_SERVER_ERROR,
 | 
				
			||||||
          INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD);
 | 
					        "LDAP_AUTH_ERROR",
 | 
				
			||||||
    }
 | 
					        "Authentication failed: " + bindingResult.getResultCode().getName());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
@ -237,6 +285,24 @@ public class LdapAuthenticator implements AuthenticatorHandler {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private String getUserDnFromLdap(String email) {
 | 
					  private String getUserDnFromLdap(String email) {
 | 
				
			||||||
 | 
					    // Retry configuration (will be made configurable in future)
 | 
				
			||||||
 | 
					    final int maxRetries = 3;
 | 
				
			||||||
 | 
					    final int baseDelayMs = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int attempt = 1; attempt <= maxRetries; attempt++) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        return performLdapUserSearch(email);
 | 
				
			||||||
 | 
					      } catch (CustomExceptionMessage e) {
 | 
				
			||||||
 | 
					        handleRetryableException(e, attempt, "LDAP connection failed for user lookup");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					        SERVICE_UNAVAILABLE,
 | 
				
			||||||
 | 
					        "LDAP_CONNECTION_ERROR",
 | 
				
			||||||
 | 
					        "Unable to connect to authentication server after " + maxRetries + " attempts.");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private String performLdapUserSearch(String email) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      Filter emailFilter =
 | 
					      Filter emailFilter =
 | 
				
			||||||
          Filter.createEqualityFilter(ldapConfiguration.getMailAttributeName(), email);
 | 
					          Filter.createEqualityFilter(ldapConfiguration.getMailAttributeName(), email);
 | 
				
			||||||
@ -270,7 +336,26 @@ public class LdapAuthenticator implements AuthenticatorHandler {
 | 
				
			|||||||
            INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD);
 | 
					            INTERNAL_SERVER_ERROR, INVALID_USER_OR_PASSWORD, INVALID_EMAIL_PASSWORD);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (LDAPException ex) {
 | 
					    } catch (LDAPException ex) {
 | 
				
			||||||
      throw new CustomExceptionMessage(INTERNAL_SERVER_ERROR, "LDAP_ERROR", ex.getMessage());
 | 
					      ResultCode resultCode = ex.getResultCode();
 | 
				
			||||||
 | 
					      String errorMessage = ex.getMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if it's a connection/network error
 | 
				
			||||||
 | 
					      if (resultCode == ResultCode.CONNECT_ERROR
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.SERVER_DOWN
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.UNAVAILABLE
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.TIMEOUT
 | 
				
			||||||
 | 
					          || errorMessage.contains("Connection refused")
 | 
				
			||||||
 | 
					          || errorMessage.contains("Connection reset")
 | 
				
			||||||
 | 
					          || errorMessage.contains("No route to host")) {
 | 
				
			||||||
 | 
					        LOG.error("LDAP connection error for user lookup: {}", errorMessage);
 | 
				
			||||||
 | 
					        throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					            SERVICE_UNAVAILABLE,
 | 
				
			||||||
 | 
					            "LDAP_CONNECTION_ERROR",
 | 
				
			||||||
 | 
					            "Unable to connect to authentication server. Please try again later.");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      LOG.error("LDAP error during user lookup: {}", errorMessage);
 | 
				
			||||||
 | 
					      throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					          INTERNAL_SERVER_ERROR, "LDAP_ERROR", "Authentication server error: " + errorMessage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -486,4 +571,59 @@ public class LdapAuthenticator implements AuthenticatorHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return newRefreshToken;
 | 
					    return newRefreshToken;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void handleRetryableException(CustomExceptionMessage e, int attempt, String logMessage) {
 | 
				
			||||||
 | 
					    if (e.getMessage().contains("Unable to connect to authentication server")
 | 
				
			||||||
 | 
					        && attempt < MAX_RETRIES) {
 | 
				
			||||||
 | 
					      int delayMs = BASE_DELAY_MS * attempt;
 | 
				
			||||||
 | 
					      LOG.warn(
 | 
				
			||||||
 | 
					          "{} (attempt {}/{}). Retrying in {}ms...", logMessage, attempt, MAX_RETRIES, delayMs);
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        Thread.sleep(delayMs);
 | 
				
			||||||
 | 
					      } catch (InterruptedException ie) {
 | 
				
			||||||
 | 
					        Thread.currentThread().interrupt();
 | 
				
			||||||
 | 
					        LOG.error("LDAP retry interrupted");
 | 
				
			||||||
 | 
					        throw e;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw e;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void handleLdapException(Exception ex) throws TemplateException, IOException {
 | 
				
			||||||
 | 
					    if (ex instanceof LDAPException ldapEx) {
 | 
				
			||||||
 | 
					      ResultCode resultCode = ldapEx.getResultCode();
 | 
				
			||||||
 | 
					      String errorMessage = ldapEx.getMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if it's a connection/network error
 | 
				
			||||||
 | 
					      if (resultCode == ResultCode.CONNECT_ERROR
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.SERVER_DOWN
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.UNAVAILABLE
 | 
				
			||||||
 | 
					          || resultCode == ResultCode.TIMEOUT
 | 
				
			||||||
 | 
					          || errorMessage.contains("Connection refused")
 | 
				
			||||||
 | 
					          || errorMessage.contains("Connection reset")
 | 
				
			||||||
 | 
					          || errorMessage.contains("No route to host")) {
 | 
				
			||||||
 | 
					        LOG.error("LDAP connection error during authentication: {}", errorMessage);
 | 
				
			||||||
 | 
					        throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					            SERVICE_UNAVAILABLE,
 | 
				
			||||||
 | 
					            "LDAP_CONNECTION_ERROR",
 | 
				
			||||||
 | 
					            "Unable to connect to authentication server. Please try again later.");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      LOG.error("LDAP error during authentication: {}", errorMessage);
 | 
				
			||||||
 | 
					      throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					          INTERNAL_SERVER_ERROR, "LDAP_ERROR", "Authentication server error: " + errorMessage);
 | 
				
			||||||
 | 
					    } else if (ex instanceof GeneralSecurityException) {
 | 
				
			||||||
 | 
					      LOG.error("SSL/TLS error during LDAP authentication", ex);
 | 
				
			||||||
 | 
					      throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					          INTERNAL_SERVER_ERROR,
 | 
				
			||||||
 | 
					          "LDAP_SSL_ERROR",
 | 
				
			||||||
 | 
					          "SSL/TLS configuration error: " + ex.getMessage());
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      LOG.error("Unexpected error during LDAP authentication", ex);
 | 
				
			||||||
 | 
					      throw new CustomExceptionMessage(
 | 
				
			||||||
 | 
					          INTERNAL_SERVER_ERROR,
 | 
				
			||||||
 | 
					          "LDAP_UNEXPECTED_ERROR",
 | 
				
			||||||
 | 
					          "Unexpected authentication error: " + ex.getMessage());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,412 @@
 | 
				
			|||||||
 | 
					package org.openmetadata.service.security.auth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
 | 
					import jakarta.ws.rs.client.Entity;
 | 
				
			||||||
 | 
					import jakarta.ws.rs.client.Invocation;
 | 
				
			||||||
 | 
					import jakarta.ws.rs.core.MediaType;
 | 
				
			||||||
 | 
					import jakarta.ws.rs.core.NewCookie;
 | 
				
			||||||
 | 
					import jakarta.ws.rs.core.Response;
 | 
				
			||||||
 | 
					import java.time.Duration;
 | 
				
			||||||
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.glassfish.jersey.client.ClientProperties;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.*;
 | 
				
			||||||
 | 
					import org.openmetadata.schema.auth.LoginRequest;
 | 
				
			||||||
 | 
					import org.openmetadata.service.OpenMetadataApplicationTest;
 | 
				
			||||||
 | 
					import org.openmetadata.service.util.TestUtils;
 | 
				
			||||||
 | 
					import org.testcontainers.containers.GenericContainer;
 | 
				
			||||||
 | 
					import org.testcontainers.utility.DockerImageName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 | 
				
			||||||
 | 
					@TestInstance(TestInstance.Lifecycle.PER_CLASS)
 | 
				
			||||||
 | 
					class LdapAuthCompleteFlowTest extends OpenMetadataApplicationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final String AUTH_LOGIN_ENDPOINT = "/api/v1/auth/login";
 | 
				
			||||||
 | 
					  private static final String AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static GenericContainer<?> ldapContainer;
 | 
				
			||||||
 | 
					  private static String ldapHost;
 | 
				
			||||||
 | 
					  private static int ldapPort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final String TEST_USER_EMAIL = "testuser@testcompany.com";
 | 
				
			||||||
 | 
					  private static final String TEST_USER_PASSWORD = "testpass123";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @BeforeAll
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public void createApplication() throws Exception {
 | 
				
			||||||
 | 
					    startLdapContainer();
 | 
				
			||||||
 | 
					    super.createApplication();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void startLdapContainer() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Starting LDAP container...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ldapContainer =
 | 
				
			||||||
 | 
					        new GenericContainer<>(DockerImageName.parse("osixia/openldap:1.5.0"))
 | 
				
			||||||
 | 
					            .withExposedPorts(389)
 | 
				
			||||||
 | 
					            .withEnv("LDAP_ORGANISATION", "TestCompany")
 | 
				
			||||||
 | 
					            .withEnv("LDAP_DOMAIN", "testcompany.com")
 | 
				
			||||||
 | 
					            .withEnv("LDAP_ADMIN_PASSWORD", "adminpassword")
 | 
				
			||||||
 | 
					            .withEnv("LDAP_CONFIG_PASSWORD", "config")
 | 
				
			||||||
 | 
					            .withEnv("LDAP_READONLY_USER", "false")
 | 
				
			||||||
 | 
					            .withEnv("LDAP_TLS", "false")
 | 
				
			||||||
 | 
					            .withCommand("--copy-service", "--loglevel", "debug")
 | 
				
			||||||
 | 
					            .withStartupTimeout(Duration.ofMinutes(2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ldapContainer.start();
 | 
				
			||||||
 | 
					    ldapHost = ldapContainer.getHost();
 | 
				
			||||||
 | 
					    ldapPort = ldapContainer.getMappedPort(389);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("LDAP container started at {}:{}", ldapHost, ldapPort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Thread.sleep(10000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loadTestLdapData();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void loadTestLdapData() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Loading test LDAP data...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Step 1: Creating organizational units");
 | 
				
			||||||
 | 
					    String ouLdif =
 | 
				
			||||||
 | 
					        "dn: ou=users,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: organizationalUnit\n"
 | 
				
			||||||
 | 
					            + "ou: users\n\n"
 | 
				
			||||||
 | 
					            + "dn: ou=groups,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: organizationalUnit\n"
 | 
				
			||||||
 | 
					            + "ou: groups";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execLdapAdd(ouLdif, "organizational units");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Step 2: Creating test user");
 | 
				
			||||||
 | 
					    String userLdif =
 | 
				
			||||||
 | 
					        "dn: uid=testuser,ou=users,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: inetOrgPerson\n"
 | 
				
			||||||
 | 
					            + "objectClass: posixAccount\n"
 | 
				
			||||||
 | 
					            + "objectClass: shadowAccount\n"
 | 
				
			||||||
 | 
					            + "uid: testuser\n"
 | 
				
			||||||
 | 
					            + "sn: User\n"
 | 
				
			||||||
 | 
					            + "givenName: Test\n"
 | 
				
			||||||
 | 
					            + "cn: Test User\n"
 | 
				
			||||||
 | 
					            + "displayName: Test User\n"
 | 
				
			||||||
 | 
					            + "uidNumber: 10001\n"
 | 
				
			||||||
 | 
					            + "gidNumber: 5000\n"
 | 
				
			||||||
 | 
					            + "userPassword: testpass123\n"
 | 
				
			||||||
 | 
					            + "loginShell: /bin/bash\n"
 | 
				
			||||||
 | 
					            + "homeDirectory: /home/testuser\n"
 | 
				
			||||||
 | 
					            + "mail: testuser@testcompany.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execLdapAdd(userLdif, "test user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Step 3: Creating admin user");
 | 
				
			||||||
 | 
					    String adminLdif =
 | 
				
			||||||
 | 
					        "dn: uid=admin,ou=users,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: inetOrgPerson\n"
 | 
				
			||||||
 | 
					            + "objectClass: posixAccount\n"
 | 
				
			||||||
 | 
					            + "objectClass: shadowAccount\n"
 | 
				
			||||||
 | 
					            + "uid: admin\n"
 | 
				
			||||||
 | 
					            + "sn: Admin\n"
 | 
				
			||||||
 | 
					            + "givenName: System\n"
 | 
				
			||||||
 | 
					            + "cn: System Admin\n"
 | 
				
			||||||
 | 
					            + "displayName: System Admin\n"
 | 
				
			||||||
 | 
					            + "uidNumber: 10000\n"
 | 
				
			||||||
 | 
					            + "gidNumber: 5000\n"
 | 
				
			||||||
 | 
					            + "userPassword: admin123\n"
 | 
				
			||||||
 | 
					            + "loginShell: /bin/bash\n"
 | 
				
			||||||
 | 
					            + "homeDirectory: /home/admin\n"
 | 
				
			||||||
 | 
					            + "mail: admin@testcompany.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execLdapAdd(adminLdif, "admin user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Step 4: Creating groups");
 | 
				
			||||||
 | 
					    String groupsLdif =
 | 
				
			||||||
 | 
					        "dn: cn=DataConsumer,ou=groups,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: groupOfNames\n"
 | 
				
			||||||
 | 
					            + "cn: DataConsumer\n"
 | 
				
			||||||
 | 
					            + "description: Data Consumer group\n"
 | 
				
			||||||
 | 
					            + "member: uid=testuser,ou=users,dc=testcompany,dc=com\n\n"
 | 
				
			||||||
 | 
					            + "dn: cn=admin,ou=groups,dc=testcompany,dc=com\n"
 | 
				
			||||||
 | 
					            + "objectClass: groupOfNames\n"
 | 
				
			||||||
 | 
					            + "cn: admin\n"
 | 
				
			||||||
 | 
					            + "description: Administrator group\n"
 | 
				
			||||||
 | 
					            + "member: uid=admin,ou=users,dc=testcompany,dc=com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    execLdapAdd(groupsLdif, "groups");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("✓ All test LDAP data loaded successfully");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void execLdapAdd(String ldifContent, String description) throws Exception {
 | 
				
			||||||
 | 
					    String[] ldapAddCommand = {
 | 
				
			||||||
 | 
					      "/bin/bash",
 | 
				
			||||||
 | 
					      "-c",
 | 
				
			||||||
 | 
					      "echo '"
 | 
				
			||||||
 | 
					          + ldifContent
 | 
				
			||||||
 | 
					          + "' | ldapadd -x -H ldap://localhost:389 -D 'cn=admin,dc=testcompany,dc=com' -w adminpassword"
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    org.testcontainers.containers.Container.ExecResult result =
 | 
				
			||||||
 | 
					        ldapContainer.execInContainer(ldapAddCommand);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (result.getExitCode() != 0) {
 | 
				
			||||||
 | 
					      LOG.error("Failed to add {}: {}", description, result.getStderr());
 | 
				
			||||||
 | 
					      throw new RuntimeException(
 | 
				
			||||||
 | 
					          "Failed to load " + description + ". Exit code: " + result.getExitCode());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    LOG.info("✓ Added {}", description);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @BeforeAll
 | 
				
			||||||
 | 
					  void setupLdapConfiguration() throws Exception {
 | 
				
			||||||
 | 
					    Thread.sleep(3000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Application running on port: {}", APP.getLocalPort());
 | 
				
			||||||
 | 
					    LOG.info("LDAP server running at: {}:{}", ldapHost, ldapPort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateAuthenticationConfigToLdap();
 | 
				
			||||||
 | 
					    LOG.info("Test setup complete - LDAP authentication configured");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void updateAuthenticationConfigToLdap() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Updating authentication configuration to LDAP...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, Object> ldapConfig = new HashMap<>();
 | 
				
			||||||
 | 
					    ldapConfig.put("host", ldapHost);
 | 
				
			||||||
 | 
					    ldapConfig.put("port", ldapPort);
 | 
				
			||||||
 | 
					    ldapConfig.put("dnAdminPrincipal", "cn=admin,dc=testcompany,dc=com");
 | 
				
			||||||
 | 
					    ldapConfig.put("dnAdminPassword", "adminpassword");
 | 
				
			||||||
 | 
					    ldapConfig.put("userBaseDN", "ou=users,dc=testcompany,dc=com");
 | 
				
			||||||
 | 
					    ldapConfig.put("groupBaseDN", "ou=groups,dc=testcompany,dc=com");
 | 
				
			||||||
 | 
					    ldapConfig.put("mailAttributeName", "mail");
 | 
				
			||||||
 | 
					    ldapConfig.put("groupAttributeName", "objectClass");
 | 
				
			||||||
 | 
					    ldapConfig.put("groupAttributeValue", "groupOfNames");
 | 
				
			||||||
 | 
					    ldapConfig.put("groupMemberAttributeName", "member");
 | 
				
			||||||
 | 
					    ldapConfig.put("allAttributeName", "*");
 | 
				
			||||||
 | 
					    ldapConfig.put("roleAdminName", "admin");
 | 
				
			||||||
 | 
					    ldapConfig.put("maxPoolSize", 10);
 | 
				
			||||||
 | 
					    ldapConfig.put("sslEnabled", false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, List<String>> roleMapping = new HashMap<>();
 | 
				
			||||||
 | 
					    roleMapping.put("cn=admin,ou=groups,dc=testcompany,dc=com", Arrays.asList("admin"));
 | 
				
			||||||
 | 
					    roleMapping.put(
 | 
				
			||||||
 | 
					        "cn=DataConsumer,ou=groups,dc=testcompany,dc=com", Arrays.asList("DataConsumer"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ldapConfig.put("authRolesMapping", new ObjectMapper().writeValueAsString(roleMapping));
 | 
				
			||||||
 | 
					    ldapConfig.put("authReassignRoles", Arrays.asList("*"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, Object> authConfig = new HashMap<>();
 | 
				
			||||||
 | 
					    authConfig.put("provider", "ldap");
 | 
				
			||||||
 | 
					    authConfig.put("providerName", "LDAP");
 | 
				
			||||||
 | 
					    authConfig.put("publicKeyUrls", new ArrayList<>());
 | 
				
			||||||
 | 
					    authConfig.put("clientId", "open-metadata");
 | 
				
			||||||
 | 
					    authConfig.put("authority", "http://localhost:" + APP.getLocalPort());
 | 
				
			||||||
 | 
					    authConfig.put("callbackUrl", "http://localhost:" + APP.getLocalPort() + "/callback");
 | 
				
			||||||
 | 
					    authConfig.put("jwtPrincipalClaims", Arrays.asList("email", "preferred_username", "sub"));
 | 
				
			||||||
 | 
					    authConfig.put("enableSelfSignup", true);
 | 
				
			||||||
 | 
					    authConfig.put("ldapConfiguration", ldapConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, Object> authorizerConfig = new HashMap<>();
 | 
				
			||||||
 | 
					    authorizerConfig.put("className", "org.openmetadata.service.security.DefaultAuthorizer");
 | 
				
			||||||
 | 
					    authorizerConfig.put("containerRequestFilter", "org.openmetadata.service.security.JwtFilter");
 | 
				
			||||||
 | 
					    authorizerConfig.put("adminPrincipals", Arrays.asList("admin"));
 | 
				
			||||||
 | 
					    authorizerConfig.put("testPrincipals", new ArrayList<>());
 | 
				
			||||||
 | 
					    authorizerConfig.put("allowedEmailRegistrationDomains", Arrays.asList("all"));
 | 
				
			||||||
 | 
					    authorizerConfig.put("principalDomain", "open-metadata.org");
 | 
				
			||||||
 | 
					    authorizerConfig.put("allowedDomains", new ArrayList<>());
 | 
				
			||||||
 | 
					    authorizerConfig.put("enforcePrincipalDomain", false);
 | 
				
			||||||
 | 
					    authorizerConfig.put("enableSecureSocketConnection", false);
 | 
				
			||||||
 | 
					    authorizerConfig.put("useRolesFromProvider", false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, Object> securityConfig = new HashMap<>();
 | 
				
			||||||
 | 
					    securityConfig.put("authenticationConfiguration", authConfig);
 | 
				
			||||||
 | 
					    securityConfig.put("authorizerConfiguration", authorizerConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Invocation.Builder request =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + "/api/v1/system/security/config")
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (Map.Entry<String, String> entry : TestUtils.ADMIN_AUTH_HEADERS.entrySet()) {
 | 
				
			||||||
 | 
					      request = request.header(entry.getKey(), entry.getValue());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response response = request.put(Entity.json(securityConfig));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Auth config update response status: {}", response.getStatus());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response.getStatus() == 200 || response.getStatus() == 201) {
 | 
				
			||||||
 | 
					      LOG.info("✓ Successfully updated authentication configuration to LDAP");
 | 
				
			||||||
 | 
					      Thread.sleep(3000);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      String error = response.readEntity(String.class);
 | 
				
			||||||
 | 
					      LOG.error("Failed to update authentication configuration: {}", error);
 | 
				
			||||||
 | 
					      fail("Failed to update authentication configuration - Status: " + response.getStatus());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(1)
 | 
				
			||||||
 | 
					  void testLdapContainerStartup() {
 | 
				
			||||||
 | 
					    LOG.info("Testing LDAP container startup");
 | 
				
			||||||
 | 
					    assertNotNull(ldapContainer);
 | 
				
			||||||
 | 
					    assertTrue(ldapContainer.isRunning());
 | 
				
			||||||
 | 
					    assertNotNull(ldapHost);
 | 
				
			||||||
 | 
					    assertTrue(ldapPort > 0);
 | 
				
			||||||
 | 
					    LOG.info("LDAP container is running at {}:{}", ldapHost, ldapPort);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(3)
 | 
				
			||||||
 | 
					  void testSuccessfulLoginWithValidCredentials() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Testing successful LDAP login with valid credentials");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LoginRequest loginRequest = new LoginRequest();
 | 
				
			||||||
 | 
					    loginRequest.setEmail(TEST_USER_EMAIL);
 | 
				
			||||||
 | 
					    loginRequest.setPassword(Base64.getEncoder().encodeToString(TEST_USER_PASSWORD.getBytes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response response =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + AUTH_LOGIN_ENDPOINT)
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					            .property(ClientProperties.FOLLOW_REDIRECTS, false)
 | 
				
			||||||
 | 
					            .post(Entity.json(loginRequest));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Login response status: {}", response.getStatus());
 | 
				
			||||||
 | 
					    assertEquals(200, response.getStatus(), "Login should succeed with valid credentials");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String responseBody = response.readEntity(String.class);
 | 
				
			||||||
 | 
					    LOG.info("Login response: {}", responseBody);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, Object> jwtResponse = new ObjectMapper().readValue(responseBody, Map.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assertNotNull(jwtResponse.get("accessToken"), "Access token should be present");
 | 
				
			||||||
 | 
					    assertEquals("Bearer", jwtResponse.get("tokenType"), "Token type should be Bearer");
 | 
				
			||||||
 | 
					    assertNotNull(jwtResponse.get("expiryDuration"), "Expiry duration should be present");
 | 
				
			||||||
 | 
					    assertNull(jwtResponse.get("refreshToken"), "Refresh token should not be sent to client");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, NewCookie> cookies = response.getCookies();
 | 
				
			||||||
 | 
					    LOG.info("Session cookies: {}", cookies.keySet());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(4)
 | 
				
			||||||
 | 
					  void testFailedLoginWithInvalidPassword() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Testing failed LDAP login with invalid password");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LoginRequest loginRequest = new LoginRequest();
 | 
				
			||||||
 | 
					    loginRequest.setEmail(TEST_USER_EMAIL);
 | 
				
			||||||
 | 
					    loginRequest.setPassword(Base64.getEncoder().encodeToString("wrongpassword".getBytes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response response =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + AUTH_LOGIN_ENDPOINT)
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					            .property(ClientProperties.FOLLOW_REDIRECTS, false)
 | 
				
			||||||
 | 
					            .post(Entity.json(loginRequest));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Login response status: {}", response.getStatus());
 | 
				
			||||||
 | 
					    assertNotEquals(200, response.getStatus(), "Login should fail with invalid password");
 | 
				
			||||||
 | 
					    assertTrue(
 | 
				
			||||||
 | 
					        response.getStatus() == 401 || response.getStatus() == 500,
 | 
				
			||||||
 | 
					        "Should return 401 or 500 for invalid credentials");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(5)
 | 
				
			||||||
 | 
					  void testFailedLoginWithNonExistentUser() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Testing failed LDAP login with non-existent user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LoginRequest loginRequest = new LoginRequest();
 | 
				
			||||||
 | 
					    loginRequest.setEmail("nonexistent@testcompany.com");
 | 
				
			||||||
 | 
					    loginRequest.setPassword(Base64.getEncoder().encodeToString("password".getBytes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response response =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + AUTH_LOGIN_ENDPOINT)
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					            .property(ClientProperties.FOLLOW_REDIRECTS, false)
 | 
				
			||||||
 | 
					            .post(Entity.json(loginRequest));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Login response status: {}", response.getStatus());
 | 
				
			||||||
 | 
					    assertNotEquals(200, response.getStatus(), "Login should fail with non-existent user");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(7)
 | 
				
			||||||
 | 
					  void testLogout() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Testing logout flow");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LoginRequest loginRequest = new LoginRequest();
 | 
				
			||||||
 | 
					    loginRequest.setEmail(TEST_USER_EMAIL);
 | 
				
			||||||
 | 
					    loginRequest.setPassword(Base64.getEncoder().encodeToString(TEST_USER_PASSWORD.getBytes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response loginResponse =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + AUTH_LOGIN_ENDPOINT)
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					            .property(ClientProperties.FOLLOW_REDIRECTS, false)
 | 
				
			||||||
 | 
					            .post(Entity.json(loginRequest));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assertEquals(200, loginResponse.getStatus(), "Login should succeed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, NewCookie> cookies = loginResponse.getCookies();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Invocation.Builder logoutRequest =
 | 
				
			||||||
 | 
					        APP.client()
 | 
				
			||||||
 | 
					            .target(getServerUrl() + AUTH_LOGOUT_ENDPOINT)
 | 
				
			||||||
 | 
					            .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					            .property(ClientProperties.FOLLOW_REDIRECTS, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (NewCookie cookie : cookies.values()) {
 | 
				
			||||||
 | 
					      logoutRequest = logoutRequest.cookie(cookie);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response logoutResponse = logoutRequest.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Logout response status: {}", logoutResponse.getStatus());
 | 
				
			||||||
 | 
					    assertEquals(200, logoutResponse.getStatus(), "Logout should succeed");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  @Order(9)
 | 
				
			||||||
 | 
					  void testMultipleLoginAttempts() throws Exception {
 | 
				
			||||||
 | 
					    LOG.info("Testing multiple failed login attempts");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String testEmail = "multitest@testcompany.com";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < 3; i++) {
 | 
				
			||||||
 | 
					      LoginRequest loginRequest = new LoginRequest();
 | 
				
			||||||
 | 
					      loginRequest.setEmail(testEmail);
 | 
				
			||||||
 | 
					      loginRequest.setPassword(Base64.getEncoder().encodeToString("wrongpassword".getBytes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Response response =
 | 
				
			||||||
 | 
					          APP.client()
 | 
				
			||||||
 | 
					              .target(getServerUrl() + AUTH_LOGIN_ENDPOINT)
 | 
				
			||||||
 | 
					              .request(MediaType.APPLICATION_JSON)
 | 
				
			||||||
 | 
					              .property(ClientProperties.FOLLOW_REDIRECTS, false)
 | 
				
			||||||
 | 
					              .post(Entity.json(loginRequest));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      LOG.info("Attempt {} - Response status: {}", i + 1, response.getStatus());
 | 
				
			||||||
 | 
					      assertNotEquals(200, response.getStatus(), "Login should fail with wrong password");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Thread.sleep(500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LoginAttemptCache.getInstance().recordSuccessfulLogin(testEmail);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private String getServerUrl() {
 | 
				
			||||||
 | 
					    return "http://localhost:" + APP.getLocalPort();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @AfterAll
 | 
				
			||||||
 | 
					  void cleanup() {
 | 
				
			||||||
 | 
					    if (ldapContainer != null && ldapContainer.isRunning()) {
 | 
				
			||||||
 | 
					      LOG.info("Stopping LDAP container");
 | 
				
			||||||
 | 
					      ldapContainer.stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user