cartodb/lib/tasks/ldap.rake
2020-06-15 10:58:47 +08:00

164 lines
6.5 KiB
Ruby

namespace :cartodb do
namespace :ldap do
desc "Tests an LDAP connection. Returns a summary of all tests ran, with success status and error messages."
task :test_ldap_connection, [:from_database] => :environment do |_t, args|
args.with_defaults(from_database: false)
test_user = ENV['TEST_USER']
test_password = ENV['TEST_PASSWORD']
test_search = ENV['TEST_SEARCH']
ldap = if args.from_database
Carto::Ldap::Configuration.first
else
ldap_configuration_from_environment(Carto::Organization.new)
end
# Test configuration
config_result = if ldap.valid?
{ success: true }
else
{ success: false, error: ldap.errors.to_h }
end
result = { config: config_result }
REQUIRED_FIELDS = [:host, :port, :connection_user, :connection_password].freeze
if !config_result[:success] && config_result[:error].keys.any? { |k| REQUIRED_FIELDS.include?(k) }
result[:connection] = { success: false, error: 'Configuration not valid' }
end
# Test connection
result[:connection] = ldap.test_connection unless result[:connection]
CONNECTION_DEPENDENT_TESTS = [:login, :user_search, :group_search, :search].freeze
if result[:connection][:success]
result[:connection].delete(:connection)
if ldap.domain_bases.present?
result[:login] = if test_user.present? && test_password.present?
test_ldap_user(ldap, test_user, test_password)
else
{ success: false, error: 'Test credentials not provided' }
end
result[:user_search] = if ldap.user_object_class.present?
user_count = ldap.users.count
{ success: user_count > 0, count: user_count }
else
{ success: false, error: 'User class not provided' }
end
result[:group_search] = if ldap.group_object_class.present?
group_count = ldap.groups.count
{ success: group_count > 0, count: group_count }
else
{ success: false, error: 'Group class not provided' }
end
result[:search] = if test_search.present?
object_count = ldap.send(:search_in_domain_bases, Net::LDAP::Filter.eq('objectClass', test_search)).count
{ success: true, count: object_count }
else
{ success: false, error: 'Object class not provided' }
end
else
CONNECTION_DEPENDENT_TESTS.each do |test|
result[test] = { success: false, error: 'Domain bases not provided' }
end
end
else
CONNECTION_DEPENDENT_TESTS.each do |test|
result[test] = { success: false, error: 'Connection failed' }
end
end
puts JSON.pretty_generate(result)
end
# INFO: Separate multiple domain names by commas
desc "Creates an LDAP Configuration entry"
task :create_ldap_configuration, [] => :environment do |_t, _args|
if ENV['ORGANIZATION_ID'].blank?
if ENV['ORGANIZATION_NAME'].blank?
raise "Missing ORGANIZATION_ID and ORGANIZATION_NAME. Must provide one of both"
else
organization = Carto::Organization.where(name: ENV['ORGANIZATION_NAME']).first
end
else
organization = Carto::Organization.find(ENV['ORGANIZATION_ID'])
end
ldap = ldap_configuration_from_environment(organization)
unless ldap.valid?
missing = ldap.errors.keys.reject { |k| k == :domain_bases_list }
raise "Missing: " + missing.join(', ').upcase
end
if ldap.save
puts "LDAP configuration created with id: #{ldap.id}"
else
puts "Error saving LDAP configuration"
end
end
desc "Deletes existing LDAP Configuration entries"
task :reset_ldap_configuration, [] => :environment do |_t, _args|
Carto::Ldap::Configuration.delete_all
end
end
private
def ldap_configuration_from_environment(organization)
# Mandatory: connection parameters
host = ENV['HOST']
port = ENV['PORT']
ssl_version = ENV['SSL_VERSION'].blank? ? nil : ENV['SSL_VERSION']
encryption = ENV['ENCRYPTION'].blank? ? nil : ENV['ENCRYPTION']
connection_user = ENV['CONNECTION_USER']
connection_password = ENV['CONNECTION_PASSWORD']
# Optional: for testing auth/searches
user_id_field = ENV['USER_ID_FIELD']
domain_bases = ENV['DOMAIN_BASES']
additional_search_filter = ENV['ADDITIONAL_SEARCH_FILTER']
# Optional: for testing searches
username_field = ENV['USERNAME_FIELD']
email_field = ENV['EMAIL_FIELD']
user_object_class = ENV['USER_OBJECT_CLASS']
group_object_class = ENV['GROUP_OBJECT_CLASS']
Carto::Ldap::Configuration.new(
organization: organization,
host: host,
port: port,
encryption: encryption,
ssl_version: ssl_version,
connection_user: connection_user,
connection_password: connection_password,
user_id_field: user_id_field,
username_field: username_field,
additional_search_filter: additional_search_filter,
email_field: email_field,
domain_bases: domain_bases,
user_object_class: user_object_class,
group_object_class: group_object_class
)
end
def test_ldap_user(ldap, test_user, test_password)
unless ldap.user_id_field.present? && ldap.username_field.present? && ldap.email_field.present?
return { success: false, error: 'User id, username or email attribute names not specified.' }
end
entry = ldap.authenticate(test_user, test_password)
return { success: false, error: 'Cannot login with test credentials' } unless entry
if entry.email && entry.username
{ success: true, email: entry.email, username: entry.username }
else
{ success: false, error: 'Cannot retrieve user attributes. Check the email and username attribute names' }
end
end
end