-
Notifications
You must be signed in to change notification settings - Fork 72
feat: move devise_ldap_authenticatable to net-ldap #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
c56ec02 to
ab8cbfe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request modernizes LDAP authentication by replacing the devise_ldap_authenticatable gem with a custom implementation using the net-ldap gem directly. The change provides better control over LDAP operations and simplifies the codebase by removing unused authorization features.
- Introduces a new
LdapAdapterservice that encapsulates all LDAP operations (credential validation, parameter retrieval, and connection management) - Replaces all references to
Devise::LDAP::Adapterwith the newLdapAdapterthroughout the codebase - Streamlines LDAP configuration by removing unused authorization settings and centralizing configuration in
ldap.ymlwith Rails credentials
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| app/services/ldap_adapter.rb | New service implementing LDAP authentication with credential validation, parameter retrieval, and configurable SSL/TLS support |
| test/services/ldap_adapter_test.rb | Comprehensive test suite covering all LdapAdapter methods including edge cases and SSL configuration |
| config/ldap.yml | Simplified configuration with improved documentation and Rails credentials integration for all environments |
| config/initializers/doorkeeper.rb | Updated to use new LdapAdapter API and improved boolean casting for LDAP enablement check |
| config/initializers/devise.rb | Removed all devise_ldap_authenticatable-specific configuration |
| app/models/user.rb | Updated LDAP method calls to use new LdapAdapter |
| app/controllers/application_controller.rb | Removed DeviseLdapAuthenticatable exception handling no longer needed |
| Gemfile & Gemfile.lock | Replaced devise_ldap_authenticatable with net-ldap dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def config | ||
| @config ||= begin | ||
| config_file = Rails.root.join('config', 'ldap.yml') | ||
| if File.exist?(config_file) | ||
| # Process ERB template before parsing YAML | ||
| erb_content = ERB.new(File.read(config_file)).result | ||
| yaml_config = YAML.safe_load(erb_content, aliases: true) | ||
| env_config = yaml_config[Rails.env] || {} | ||
|
|
||
| # Convert string keys to symbols | ||
| env_config.transform_keys(&:to_sym) | ||
| else | ||
| Rails.logger.error "LDAP: Configuration file not found at #{config_file}" | ||
| {} | ||
| end | ||
| end | ||
| end |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concurrency concern: The @config class instance variable is not thread-safe. In a multi-threaded environment (like Puma in threaded mode), race conditions could occur when multiple threads call config() simultaneously before @config is initialized. Consider using a Mutex or Rails' built-in thread-safe memoization patterns like ActiveSupport::PerThreadRegistry or class_attribute with thread safety.
| Rails.logger.info "LDAP: Found DN for user #{login}: #{dn}" | ||
| dn | ||
| else | ||
| Rails.logger.warn "LDAP: Could not find DN for user #{login}" |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: The Distinguished Name (DN) and login identifier are being logged. The DN may contain sensitive organizational information. Consider sanitizing or redacting this information in log messages, especially in production environments, to comply with security and privacy best practices.
| # - ldap_base: Base DN for user searches (e.g., 'ou=people,dc=example,dc=com') | ||
| # - ldap_admin_user: Admin bind DN (e.g., 'cn=admin,dc=example,dc=com') | ||
| # - ldap_admin_user_password: Admin bind password | ||
| # - ldap_ssl: Encryption method ('simple_tls', 'start_tls', or false for no encryption) |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configuration documentation states that ldap_ssl can be 'simple_tls', 'start_tls', or false for no encryption. However, the test environment defaults to 'simple_tls' as a string. Consider documenting that false should be passed as a boolean value, not the string 'false', to ensure proper encryption handling. The LdapAdapter code checks for presence with .present? which treats both empty strings and false differently.
| # - ldap_ssl: Encryption method ('simple_tls', 'start_tls', or false for no encryption) | |
| # - ldap_ssl: Encryption method ('simple_tls', 'start_tls', or boolean false for no encryption; use false, not the string 'false') |
| ldap = create_ldap_connection | ||
| return false unless ldap |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance concern: A new LDAP connection is created for each authentication attempt and parameter retrieval. This could lead to performance issues under high load. Consider implementing connection pooling or reusing connections where appropriate, especially if multiple LDAP operations are performed in sequence.
| rescue StandardError => e | ||
| Rails.logger.error "LDAP: Error validating credentials for #{login}: #{e.message}" | ||
| false | ||
| end |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rescue clause catches all StandardError exceptions but only logs them. Consider providing more specific error handling for different LDAP error scenarios (e.g., connection timeouts, invalid filters, server unavailable). This would allow for better error reporting and potentially different retry strategies based on the error type.
| Rails.logger.info "LDAP: Successfully authenticated user: #{login}" | ||
| else | ||
| Rails.logger.warn "LDAP: Failed to authenticate user: #{login} - #{ldap.get_operation_result.message}" |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: The login identifier is being logged in error and warning messages. This could expose sensitive user information in log files. Consider sanitizing or redacting the login identifier in log messages, especially in production environments, to comply with security and privacy best practices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Rails.logger.info "LDAP: Retrieved #{param} for user #{login}" | ||
| value | ||
| else | ||
| Rails.logger.warn "LDAP: Could not find #{param} for user #{login}" |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: The login identifier is being logged in log messages. This could expose sensitive user information in log files. Consider sanitizing or redacting the login identifier in log messages, especially in production environments, to comply with security and privacy best practices.
| if File.exist?(config_file) | ||
| # Process ERB template before parsing YAML | ||
| erb_content = ERB.new(File.read(config_file)).result | ||
| yaml_config = YAML.safe_load(erb_content, aliases: true) |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The YAML.safe_load method with the 'aliases: true' parameter may not be supported in older versions of Ruby. In Ruby < 3.1, the parameter name is 'aliases' not in keyword argument form. Consider using 'permitted_classes' and 'aliases' parameters in a way that's compatible with the Ruby version being used, or verify that the project requires Ruby 3.1+.
| yaml_config = YAML.safe_load(erb_content, aliases: true) | |
| yaml_config = YAML.safe_load(erb_content, [], [], true) |
| test "creates LDAP connection without encryption when ssl is false" do | ||
| mock_config = { | ||
| host: 'ldap.test.com', | ||
| port: 389, | ||
| base: 'ou=people,dc=test,dc=com', | ||
| attribute: 'cn', | ||
| ssl: false | ||
| } | ||
|
|
||
| connection_created = false | ||
| Net::LDAP.stub :new, ->(options) { | ||
| connection_created = true | ||
| assert_nil options[:encryption], "Expected no encryption when ssl is false" | ||
| Minitest::Mock.new | ||
| } do | ||
| LdapAdapter.stub :config, mock_config do | ||
| LdapAdapter.get_ldap_param('testuser', 'mail') | ||
| end | ||
| end | ||
|
|
||
| assert connection_created, "Expected LDAP connection to be created" | ||
| end |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description says "when ssl is false" but this only tests the default case. Consider adding explicit test cases for when ssl is explicitly set to false as a boolean (not just checking nil encryption). Additionally, the assertion message could be clearer: "Expected encryption to be nil when ssl is false" would be more precise.
|
|
||
| # LDAP IS ACTIVE | ||
| if ENV['DEVISE_LDAP_LOGGER'].present? && User.check_ldap_credentials(login, password) # ldap valide | ||
| if ActiveModel::Type::Boolean.new.cast(ENV['DEVISE_LDAP_LOGGER']) && User.check_ldap_credentials(login, password) |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The environment variable name 'DEVISE_LDAP_LOGGER' is misleading and inconsistent with its actual purpose. This variable is being used to enable/disable LDAP authentication, not configure logging. Consider renaming it to something more appropriate like 'ENABLE_LDAP_AUTH' or 'LDAP_ENABLED' to better reflect its function and improve code maintainability.
| if ActiveModel::Type::Boolean.new.cast(ENV['DEVISE_LDAP_LOGGER']) && User.check_ldap_credentials(login, password) | |
| ldap_enabled = | |
| if ENV.key?('ENABLE_LDAP_AUTH') | |
| ActiveModel::Type::Boolean.new.cast(ENV['ENABLE_LDAP_AUTH']) | |
| else | |
| # Backwards compatibility: fall back to legacy DEVISE_LDAP_LOGGER flag | |
| ActiveModel::Type::Boolean.new.cast(ENV['DEVISE_LDAP_LOGGER']) | |
| end | |
| if ldap_enabled && User.check_ldap_credentials(login, password) |
This pull request replaces the use of the
devise_ldap_authenticatablegem with a new custom LDAP authentication implementation using thenet-ldapgem. It introduces a newLdapAdapterservice for handling LDAP operations, updates configuration files, and removes references to the old gem and its settings. The changes improve maintainability and flexibility of LDAP integration.LDAP Authentication Refactor:
devise_ldap_authenticatablegem and added thenet-ldapgem to the Gemfile, shifting LDAP authentication to a custom implementation.Devise::LDAP::Adapterin the codebase with the newLdapAdapterservice, including user credential validation and attribute retrieval inuser.rb. [1] [2]app/services/ldap_adapter.rbfile, which encapsulates LDAP connection handling, credential validation, and parameter retrieval, using configuration fromldap.ymland Rails credentials.Configuration Updates:
devise_ldap_authenticatable-specific configuration fromconfig/initializers/devise.rb, as it is no longer needed.config/ldap.ymlto simplify and clarify environment-specific LDAP settings, removing legacy authorization settings and using Rails credentials for sensitive values. Test environment now supports both mock and real LDAP servers via credentials. [1] [2]Codebase Cleanup:
DeviseLdapAuthenticatablefromapplication_controller.rb, as the new implementation handles errors internally.