From 2b0d817b0e40b0633a5a395dc517e00a4089dedb Mon Sep 17 00:00:00 2001 From: Francesco Coda Zabetta Date: Mon, 1 Apr 2019 11:35:39 +0200 Subject: [PATCH] LDAP enhancements --- app/controllers/chaltron/ldap_controller.rb | 10 +- .../chaltron/omniauth_callbacks_controller.rb | 1 + .../chaltron/sessions_controller.rb | 3 + app/models/user.rb | 4 + app/views/chaltron/ldap/search.html.erb | 2 +- app/views/chaltron/users/index.html.erb | 2 +- app/views/chaltron/users/show.html.erb | 4 +- app/views/locales/en.yml | 8 +- app/views/locales/it.yml | 4 +- config/chaltron_navigation.rb | 4 +- config/routes.rb | 9 +- lib/chaltron.rb | 25 +++- lib/chaltron/ldap/connection.rb | 129 +++++++++++++----- lib/chaltron/ldap/person.rb | 16 ++- lib/chaltron/ldap/user.rb | 6 +- .../templates/config/initializers/chaltron.rb | 61 ++++++++- spec/dummy/app/models/ability.rb | 4 + spec/dummy/config/initializers/chaltron.rb | 9 ++ spec/dummy/config/initializers/devise.rb | 4 +- spec/features/ldap_spec.rb | 35 ++++- spec/features/login_spec.rb | 53 ++++++- spec/lib/chaltron/ldap/connection_spec.rb | 73 ++++++++++ spec/lib/chaltron/ldap/person_spec.rb | 38 +++++- travis-ci/ldap/entry.ldif | 15 +- 24 files changed, 434 insertions(+), 85 deletions(-) create mode 100644 spec/lib/chaltron/ldap/connection_spec.rb diff --git a/app/controllers/chaltron/ldap_controller.rb b/app/controllers/chaltron/ldap_controller.rb index 39cbbb6..15ffeb9 100644 --- a/app/controllers/chaltron/ldap_controller.rb +++ b/app/controllers/chaltron/ldap_controller.rb @@ -15,11 +15,11 @@ def multi_new userid = params[:userid] if userid.present? entry = Chaltron::LDAP::Person.find_by_uid(userid) - @entries << entry unless entry.nil? + @entries << entry else - res = Chaltron::LDAP::Person.find_by_fields(find_options) - @entries = res + @entries = Chaltron::LDAP::Person.find_by_fields(find_options) end + @entries.compact! end def multi_create @@ -41,12 +41,12 @@ def multi_create private def find_options department = params[:department] - name = params[:fullname] + name = params[:lastname] limit = params[:limit].to_i ret = {} ret[:department] = "*#{department}*" unless department.blank? - ret[:cn] = "*#{name}*" unless name.blank? + ret[:last_name] = "*#{name}*" unless name.blank? ret[:limit] = limit.zero? ? default_limit : limit ret end diff --git a/app/controllers/chaltron/omniauth_callbacks_controller.rb b/app/controllers/chaltron/omniauth_callbacks_controller.rb index 6249b5f..407346a 100644 --- a/app/controllers/chaltron/omniauth_callbacks_controller.rb +++ b/app/controllers/chaltron/omniauth_callbacks_controller.rb @@ -12,6 +12,7 @@ def ldap # We only find ourselves here # if the authentication to LDAP was successful. user = Chaltron::LDAP::User.find_or_create(oauth, Chaltron.ldap_allow_all) + user = Chaltron.ldap_after_authenticate.call(user, Chaltron::LDAP::Connection.new) if user.nil? redirect_to root_url, alert: I18n.t('chaltron.not_allowed_to_sign_in') else diff --git a/app/controllers/chaltron/sessions_controller.rb b/app/controllers/chaltron/sessions_controller.rb index 38c0585..0af1aa2 100644 --- a/app/controllers/chaltron/sessions_controller.rb +++ b/app/controllers/chaltron/sessions_controller.rb @@ -1,3 +1,5 @@ +require 'chaltron/ldap/connection' + class Chaltron::SessionsController < Devise::SessionsController after_action :after_login, only: :create before_action :before_logout, only: :destroy @@ -9,6 +11,7 @@ def after_login end def before_logout + Chaltron.ldap_before_logout.call(current_user, Chaltron::LDAP::Connection.new) if current_user.ldap_user? info I18n.t('chaltron.logs.logout', user: current_user.display_name) end end diff --git a/app/models/user.rb b/app/models/user.rb index 368a807..0cf15ef 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,4 +26,8 @@ def self.find_for_database_authentication(warden_conditions) end end + def ldap_user? + provider == 'ldap' + end + end diff --git a/app/views/chaltron/ldap/search.html.erb b/app/views/chaltron/ldap/search.html.erb index b958a03..eae41c0 100644 --- a/app/views/chaltron/ldap/search.html.erb +++ b/app/views/chaltron/ldap/search.html.erb @@ -4,7 +4,7 @@ <%= bootstrap_form_tag(url: ldap_multi_new_path, method: :post, layout: :horizontal) do |f| %> <%= f.text_field :userid, label: t('.name_label'), help: t('.name_help') %> - <%= f.text_field :fullname, label: t('.fullname_label'), help: t('.fullname_help') %> + <%= f.text_field :lastname, label: t('.lastname_label'), help: t('.lastname_help') %> <%= f.text_field :department, label: t('.department_label'), help: t('.department_help') %> <%= f.text_field :limit, label: t('.limit_label'), help: t('.limit_help'), value: @limit %> diff --git a/app/views/chaltron/users/index.html.erb b/app/views/chaltron/users/index.html.erb index c4d89db..c5174f6 100644 --- a/app/views/chaltron/users/index.html.erb +++ b/app/views/chaltron/users/index.html.erb @@ -5,7 +5,7 @@ <%= render partial: 'side_filters', locals: { filters: @filters } %>
- <% if ldap_enabled? %> + <% if ldap_enabled? and !Chaltron.ldap_allow_all %>
<%= content_tag :button, type: 'button', class: 'btn btn-primary dropdown-toggle', data: {toggle: 'dropdown'}, aria: {expanded: false} do %> diff --git a/app/views/chaltron/users/show.html.erb b/app/views/chaltron/users/show.html.erb index f851d96..6fca59d 100644 --- a/app/views/chaltron/users/show.html.erb +++ b/app/views/chaltron/users/show.html.erb @@ -66,11 +66,11 @@
<%= link_to edit_user_path(@user), class: 'btn btn-default' do %> <%= icon :edit, t('.edit') %> - <% end %> + <% end if can? :edit, @user %> <%= link_to @user, method: :delete, class: 'btn btn-danger', disabled: current_user == @user, data: { confirm: t('.destroy_confirm', user: @user.username) } do %> <%= icon :trash, t('.destroy') %> - <% end %> + <% end if can? :destroy, @user %>
diff --git a/app/views/locales/en.yml b/app/views/locales/en.yml index 626d035..5a2a742 100644 --- a/app/views/locales/en.yml +++ b/app/views/locales/en.yml @@ -70,11 +70,11 @@ en: title: Search for LDAP sers submit_text: Search name_label: User - name_help: Search for user-id (exact match) - fullname_label: Fullname - fullname_help: Search for first name or surname (also partial match) + name_help: Search by user-id (exact match) + lastname_label: Last name + lastname_help: Search by last name or surname (also partial match) department_label: Department - department_help: Search for department (also partial match) + department_help: Search by department (also partial match) limit_label: Limit limit_help: Max shown results multi_new: diff --git a/app/views/locales/it.yml b/app/views/locales/it.yml index 46414b8..891788f 100644 --- a/app/views/locales/it.yml +++ b/app/views/locales/it.yml @@ -71,8 +71,8 @@ it: submit_text: Cerca name_label: Utente name_help: Ricerca per user-id (match esatto) - fullname_label: Nome - fullname_help: Ricerca per nome o cognome (anche match parziale) + lastname_label: Nome + lastname_help: Ricerca per cognome (anche match parziale) department_label: Funzione department_help: Ricerca per sigla di funzione (anche match parziale) limit_label: Limite diff --git a/config/chaltron_navigation.rb b/config/chaltron_navigation.rb index 8bc7151..9a2bff1 100644 --- a/config/chaltron_navigation.rb +++ b/config/chaltron_navigation.rb @@ -11,12 +11,12 @@ admin.item :users, { icon: 'fa fa-fw fa-users', text: I18n.t('chaltron.menu.users') - }, users_path, highlights_on: /\/(users|ldap)(?!\/self_(show|edit|update))/ if can?(:manage, User) + }, users_path, highlights_on: /\/(users|ldap)(?!\/self_(show|edit|update))/ if can?(:read, User) admin.item :logs, { icon: 'fa fa-fw fa-book', text: I18n.t('chaltron.menu.logs') }, logs_path, highlights_on: /\/logs/ if can?(:read, Log) - end if can?(:manage, User) or can?(:read, Log) + end if can?(:read, User) or can?(:read, Log) primary.item :logged, current_user.display_name.html_safe, nil do |user| user.dom_class = 'dropdown-menu-right' user.item :self_edit, { icon: 'fa fa-fw fa-user', diff --git a/config/routes.rb b/config/routes.rb index ed74938..f4aa47e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,8 +15,9 @@ resources :logs, controller: 'chaltron/logs', only: [:index, :show] # search and create LDAP users - get 'ldap/search' => 'chaltron/ldap#search' - post 'ldap/multi_new' => 'chaltron/ldap#multi_new' - post 'ldap/multi_create' => 'chaltron/ldap#multi_create' - + if Devise.omniauth_providers.include?(:ldap) and !Chaltron.ldap_allow_all + get 'ldap/search' => 'chaltron/ldap#search' + post 'ldap/multi_new' => 'chaltron/ldap#multi_new' + post 'ldap/multi_create' => 'chaltron/ldap#multi_create' + end end diff --git a/lib/chaltron.rb b/lib/chaltron.rb index 8e9b2f2..467574a 100644 --- a/lib/chaltron.rb +++ b/lib/chaltron.rb @@ -14,7 +14,7 @@ module Controllers @@default_roles = [] mattr_accessor :ldap_allow_all - @@ldap_allow_all = false + @@ldap_allow_all = true mattr_accessor :enable_syslog @@enable_syslog = false @@ -22,6 +22,29 @@ module Controllers mattr_accessor :syslog_facility @@syslog_facility = Syslog::LOG_SYSLOG + mattr_accessor :ldap_field_mappings + @@ldap_field_mappings = { + first_name: 'givenname', + last_name: 'cn', + department: 'department', + email: 'mail' + } + + mattr_accessor :ldap_group_base + @@ldap_group_base = nil + + mattr_accessor :ldap_group_member_filter + @@ldap_group_member_filter = -> (entry) { "uniquemember=#{entry.dn}" } + + mattr_accessor :ldap_role_mappings + @@ldap_role_mappings = {} + + mattr_accessor :ldap_after_authenticate + @@ldap_after_authenticate = -> (user, ldap) { user } + + mattr_accessor :ldap_before_logout + @@ldap_before_logout = -> (user, ldap) { } + def self.setup yield self end diff --git a/lib/chaltron/ldap/connection.rb b/lib/chaltron/ldap/connection.rb index 819de43..82d9100 100644 --- a/lib/chaltron/ldap/connection.rb +++ b/lib/chaltron/ldap/connection.rb @@ -4,6 +4,12 @@ module Chaltron module LDAP class Connection + NET_LDAP_ENCRYPTION_METHOD = { + simple_tls: :simple_tls, + start_tls: :start_tls, + plain: nil + }.freeze + attr_reader :ldap def initialize(params = {}) @@ -16,7 +22,9 @@ def auth(login, password) end def find_by_uid(id) - find_user(uid: id) + opts = {} + opts[uid.to_sym] = id + ret = find_user(opts) end def find_user(*args) @@ -47,35 +55,31 @@ def find_users(args) scope: Net::LDAP::SearchScope_BaseObject } else - filters = [] - fields.each do |field| - filters << Net::LDAP::Filter.eq(field, args[field]) + filters = fields.map do |field| + f = translate_field(field) + Net::LDAP::Filter.eq(f, args[field]) if f end options = { base: base, filter: filters.inject { |sum, n| Net::LDAP::Filter.join(sum, n) } } end + options.merge!(size: limit) unless limit.nil? + ldap_search(options).map do |entry| + Chaltron::LDAP::Person.new(entry, uid) if entry.respond_to? uid + end.compact + end -# if config.user_filter.present? -# user_filter = Net::LDAP::Filter.construct(config.user_filter) - -# options[:filter] = if options[:filter] -# Net::LDAP::Filter.join(options[:filter], user_filter) -# else -# user_filter -# end -# end - - options.merge!(size: limit) if limit.present? - - entries = ldap_search(options).select do |entry| - entry.respond_to? uid - end + def find_groups_by_member(entry) + options = { + base: Chaltron.ldap_group_base || base, + filter: Chaltron.ldap_group_member_filter.call(entry) + } + ldap_search(options) + end - entries.map do |entry| - Chaltron::LDAP::Person.new(entry, uid) - end + def update_attributes(dn, args) + ldap.modify dn: dn, operations: args.map { |k,v| [:replace, k, v] } end private @@ -84,15 +88,21 @@ def options Devise.omniauth_configs[:ldap].options end + def translate_field field + return uid if field.to_sym == :uid + return Chaltron.ldap_field_mappings[field.to_sym] unless Chaltron.ldap_field_mappings[field.to_sym].nil? + field + end + def adapter_options - { + opts = { host: options[:host], port: options[:port], - encryption: encryption, + encryption: encryption_options, verbose: true - }.tap do |options| - options.merge!(auth_options) if has_auth? - end + } + opts.merge!(auth_options) if has_auth? + opts end def base @@ -103,15 +113,64 @@ def uid options[:uid] end - def encryption - case options[:method].to_s - when 'ssl' - :simple_tls - when 'tls' - :start_tls - else - nil + def encryption_options + method = translate_method + return unless method + { + method: method, + tls_options: tls_options + } + end + + def translate_method + NET_LDAP_ENCRYPTION_METHOD[options[:encryption]&.to_sym] + end + + def tls_options + return @tls_options if defined?(@tls_options) + + method = translate_method + return unless method + + opts = if options[:verify_certificates] && method != 'plain' + # Dup so we don't accidentally overwrite the constant + OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.dup + else + # It is important to explicitly set verify_mode for two reasons: + # 1. The behavior of OpenSSL is undefined when verify_mode is not set. + # 2. The net-ldap gem implementation verifies the certificate hostname + # unless verify_mode is set to VERIFY_NONE. + { verify_mode: OpenSSL::SSL::VERIFY_NONE } + end + + opts.merge!(custom_tls_options) + + @tls_options = opts + end + + def custom_tls_options + return {} unless options['tls_options'] + + # Dup so we don't overwrite the original value + custom_options = options['tls_options'].dup.delete_if { |_, value| value.nil? || value.blank? } + custom_options.symbolize_keys! + + if custom_options[:cert] + begin + custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert]) + rescue OpenSSL::X509::CertificateError => e + Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}" + end + end + + if custom_options[:key] + begin + custom_options[:key] = OpenSSL::PKey.read(custom_options[:key]) + rescue OpenSSL::PKey::PKeyError => e + Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}" + end end + custom_options end def auth_options diff --git a/lib/chaltron/ldap/person.rb b/lib/chaltron/ldap/person.rb index f47a654..29a3b71 100644 --- a/lib/chaltron/ldap/person.rb +++ b/lib/chaltron/ldap/person.rb @@ -44,11 +44,17 @@ def create_user(roles = []) end def department - entry.department.first rescue nil + entry.send(Chaltron.ldap_field_mappings[:department]).first rescue nil end def name - entry.cn.first + if Chaltron.ldap_field_mappings[:full_name].nil? + first_name = entry.send(Chaltron.ldap_field_mappings[:first_name]).first + last_name = entry.send(Chaltron.ldap_field_mappings[:last_name]).first + "#{first_name} #{last_name}" + else + entry.send(Chaltron.ldap_field_mappings[:full_name]).first + end end def uid @@ -60,7 +66,7 @@ def username end def email - entry.mail.first rescue nil + entry.send(Chaltron.ldap_field_mappings[:email]).first rescue nil end def dn @@ -71,6 +77,10 @@ def provider 'ldap' end + def ldap_groups + self.class.ldap.find_groups_by_member(self) + end + private def self.ldap diff --git a/lib/chaltron/ldap/user.rb b/lib/chaltron/ldap/user.rb index afdef5e..3983680 100644 --- a/lib/chaltron/ldap/user.rb +++ b/lib/chaltron/ldap/user.rb @@ -20,7 +20,11 @@ def find_or_create(auth, create) entry = Chaltron::LDAP::Person.find_by_uid(username) if user.nil? and create # create user - user = entry.create_user Chaltron.default_roles + roles = Chaltron.default_roles + roles = entry.ldap_groups.map do |e| + Chaltron.ldap_role_mappings[e.dn] + end.compact if !Chaltron.ldap_role_mappings.blank? + user = entry.create_user(roles) end update_ldap_attributes(user, entry) unless user.nil? user diff --git a/lib/generators/chaltron/templates/config/initializers/chaltron.rb b/lib/generators/chaltron/templates/config/initializers/chaltron.rb index 7e8868a..60fc955 100644 --- a/lib/generators/chaltron/templates/config/initializers/chaltron.rb +++ b/lib/generators/chaltron/templates/config/initializers/chaltron.rb @@ -2,13 +2,66 @@ # Add new roles to the right and NEVER change role order, or you'll break every role bitmask # config.roles = %w( admin user_admin ) - # If ldap enabled (see config/initializers/devise.rb), set this to true to - # allow every ldap authenitcated users to access you application - # config.ldap_allow_all = false + # If LDAP enabled (see config/initializers/devise.rb), chaltron must use + # email field and may use first_name, last_name, full_name, department. + # Here is the field mapping on you own LDAP server. + # Default values are the following: + # config.ldap_field_mappings = { + # first_name: 'givenname', + # last_name: 'cn', + # department: 'department', + # email: 'mail' + # } - # Default roles granted to new users (if automatically created) + # If LDAP enabled, set this to true to allow every ldap authenitcated + # users to access you application + # config.ldap_allow_all = true + + # You may set here default roles granted to new users (if automatically created) # config.default_roles = [] + # Here you may specify a different base for your LDAP groups + # If not specified the :base parameter defined in Devise.omniauth_configs[:ldap] will be used + # config.ldap_group_base = 'ou=groups,dc=example,dc=com' + + # Here you may specify a filter to retrieve LDAP group membership + # Accept entry (an instance of Chaltron::LDAP::Person) as parameter + # Default is + # config.ldap_group_member_filter = -> (entry) { "uniquemember=#{entry.dn}" } + + # Roles granted to new users may be retrieved by LDAP group membership. + # config.ldap_role_mappings = { + # 'DN_of_LDAP_group1' => 'role1', + # 'DN_of_LDAP_group2' => 'role2' + # } + + # The following callback is called after a successful LDAP authentication + # The callback may manipulate the user instance and + # must return user if ok, nil if not allowed do login + # Takes two parameters: + # - user, current instance of User + # - ldap, a new instance of Chaltron::LDAP::Connection + # Default is the following (it does nothing and return user) + # config.ldap_after_authenticate = -> (user, ldap) { user } + # + # Example: + # config.ldap_after_authenticate = -> (user, ldap) { + # ldap.find_by_uid(user.username).entry.enabled == ['true'] ? user : nil + # } + + # The following callback is called before logout of an LDAP user + # Takes two parameters: + # - user, current instance of User + # - ldap, a new instance of Chaltron::LDAP::Connection + # Default is the following (does nothing) + # config.ldap_before_logout = -> (user, ldap) { } + # + # Example: + # config.ldap_before_logout = -> (user, ldap) { + # ldap.update_attributes(user.extern_uid, { lastLogout: Time.now.strftime('%Y%m%d%H%M%S%z') }) + # } + # + # If syslog enabled, all Log records will be available also in syslog flow # config.enable_syslog = false # config.syslog_facility = Syslog::LOG_SYSLOG diff --git a/spec/dummy/app/models/ability.rb b/spec/dummy/app/models/ability.rb index a8d31ee..3a27313 100644 --- a/spec/dummy/app/models/ability.rb +++ b/spec/dummy/app/models/ability.rb @@ -31,6 +31,10 @@ def initialize(user) user ||= User.new if user.is?(:user_admin) can :manage, User + if Chaltron.ldap_allow_all + cannot :edit, User, { provider: 'ldap' } + cannot :destroy, User, { provider: 'ldap' } + end can :read, Log, category: 'user_admin' end if user.is?(:admin) diff --git a/spec/dummy/config/initializers/chaltron.rb b/spec/dummy/config/initializers/chaltron.rb index 01f136e..7cc26c2 100644 --- a/spec/dummy/config/initializers/chaltron.rb +++ b/spec/dummy/config/initializers/chaltron.rb @@ -1,4 +1,13 @@ Chaltron.setup do |config| # Add new roles to the right and NEVER change role order, or you'll break every role bitmask # config.roles = %w[ admin user_admin ] + + config.ldap_allow_all = false + + config.ldap_field_mappings = { + first_name: 'givenName', + last_name: 'sn', + email: 'mail', + full_name: 'cn' + } end diff --git a/spec/dummy/config/initializers/devise.rb b/spec/dummy/config/initializers/devise.rb index c9ac6ef..0c8df58 100644 --- a/spec/dummy/config/initializers/devise.rb +++ b/spec/dummy/config/initializers/devise.rb @@ -1,10 +1,10 @@ Devise.setup do |config| config.omniauth :ldap, host: 'localhost', - base: 'ou=people,dc=azkaban,dc=co,dc=uk', + base: 'dc=azkaban,dc=co,dc=uk', uid: 'uid', port: 389, - method: :plain, + encryption: :plain, bind_dn: 'cn=admin,dc=azkaban,dc=co,dc=uk', password: 'admin' # filter: '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))' diff --git a/spec/features/ldap_spec.rb b/spec/features/ldap_spec.rb index 4638296..a238d86 100644 --- a/spec/features/ldap_spec.rb +++ b/spec/features/ldap_spec.rb @@ -5,7 +5,15 @@ context 'user_admin', js: true do let(:user_admin) { create :user_admin } - before { login_with user_admin.username, user_admin.password } + before { + login_with user_admin.username, user_admin.password + Chaltron.ldap_allow_all = false + Chaltron.ldap_field_mappings = { + first_name: 'givenName', + last_name: 'sn', + email: 'mail' + } + } subject { page } it 'creates LDAP user by uid' do @@ -36,7 +44,28 @@ click_button I18n.t('chaltron.users.index.new_user') click_link I18n.t('chaltron.users.index.new_ldap_user') - fill_in 'fullname', with: 'b' + fill_in 'lastname', with: 'bla' + click_button I18n.t('chaltron.ldap.search.submit_text') + + is_expected.to have_content 'sirius' + + find('table#ldap_create tr', text: 'sirius').click + + check :user_roles_admin + click_button I18n.t('chaltron.ldap.multi_new.submit_text') + + is_expected.to have_css 'li.list-group-item-success' + is_expected.to have_content 'Sirius Black' + is_expected.not_to have_css 'div.panel-danger' + + expect(User.find_by(username: 'sirius').is? :admin).to be_truthy + end + + it 'creates LDAP users without filters' do + visit users_path + + click_button I18n.t('chaltron.users.index.new_user') + click_link I18n.t('chaltron.users.index.new_ldap_user') click_button I18n.t('chaltron.ldap.search.submit_text') is_expected.to have_content 'sirius' @@ -59,4 +88,4 @@ end -end +end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 14e7b45..20d76d3 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -3,10 +3,19 @@ describe User do context 'ldap' do + subject { page } + before { + Chaltron.ldap_allow_all = true + Chaltron.ldap_field_mappings = { + first_name: 'givenName', + last_name: 'sn', + email: 'mail' + } + } + let(:fullname) { 'Sirius Black' } context 'when Chaltron.ldap_allow_all is true' do - before { Chaltron.ldap_allow_all = true } it 'allows login and logout' do login_with 'sirius', 'padfoot', :ldap is_expected.to have_content fullname @@ -16,7 +25,10 @@ end context 'when Chaltron.default_roles is set' do - before { Chaltron.default_roles = ['admin'] } + before { + Chaltron.ldap_allow_all = true + Chaltron.default_roles = ['admin'] + } it 'allows login and set roles' do login_with 'sirius', 'padfoot', :ldap is_expected.to have_content fullname @@ -28,6 +40,43 @@ expect(User.find_by(username: 'sirius').is?(:user_admin)).to be_falsey end end + + context 'when Chaltron.ldap_role_mappings is set' do + before do + Chaltron.ldap_allow_all = true + Chaltron.ldap_group_base = 'ou=groups,dc=azkaban,dc=co,dc=uk' + Chaltron.ldap_group_member_filter = -> (entry) { "memberUid=#{entry.uid}" } + Chaltron.ldap_role_mappings = { + 'cn=good,ou=groups,dc=azkaban,dc=co,dc=uk' => 'admin' + } + end + context 'admin' do + it 'allows login and set roles' do + login_with 'sirius', 'padfoot', :ldap + is_expected.to have_content fullname + logout + is_expected.to have_content 'Login' + is_expected.not_to have_content fullname + + expect(User.find_by(username: 'sirius').is?(:admin)).to be_truthy + expect(User.find_by(username: 'sirius').is?(:user_admin)).to be_falsey + end + end + context 'no roles' do + let(:fullname) { 'Bartemius Crouch' } + it 'allows login and set roles' do + login_with 'barty', 'darklord', :ldap + is_expected.to have_content fullname + logout + is_expected.to have_content 'Login' + is_expected.not_to have_content fullname + + expect(User.find_by(username: 'barty').is?(:admin)).to be_falsey + expect(User.find_by(username: 'barty').is?(:user_admin)).to be_falsey + end + end + end + end context 'when Chaltron.ldap_allow_all is not true' do diff --git a/spec/lib/chaltron/ldap/connection_spec.rb b/spec/lib/chaltron/ldap/connection_spec.rb new file mode 100644 index 0000000..fc1105f --- /dev/null +++ b/spec/lib/chaltron/ldap/connection_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' +require 'chaltron/ldap/connection' + +describe Chaltron::LDAP::Connection do + + describe 'LDAP' do + + let(:ldap) { Chaltron::LDAP::Connection.new } + + context 'find users' do + context 'by dn' do + subject(:res) { ldap.find_users(dn: dn) } + context 'returns right value' do + let(:dn) { 'uid=sirius,ou=people,dc=azkaban,dc=co,dc=uk' } + it { is_expected.to be_an_instance_of Array } + it { expect(res.size).to eq 1 } + it { expect(res.first.uid).to eq 'sirius' } + end + context 'returns empty' do + let(:dn) { 'somenthing here' } + it { is_expected.to be_an_instance_of Array } + it { expect(res.size).to eq 0 } + end + end + + context 'by custom field' do + subject(:res) { ldap.find_users(title: title) } + context 'returns right 2 values' do + let(:title) { '*guy' } + it { is_expected.to be_an_instance_of Array } + it { expect(res.size).to eq 2 } + end + context 'returns right value' do + let(:title) { 'Bad guy' } + it { is_expected.to be_an_instance_of Array } + it { expect(res.size).to eq 1 } + it { expect(res.first.uid).to eq 'barty' } + end + end + end + + context 'find_by_uid' do + subject(:res) { ldap.find_by_uid(uid) } + context 'returns right value' do + let(:uid) { 'sirius' } + it { is_expected.to be_an_instance_of Chaltron::LDAP::Person } + it { expect(res.uid).to eq 'sirius' } + end + context 'returns nil' do + let(:uid) { 'nothing' } + it { is_expected.to be_nil } + end + end + + context 'find_groups_by_member' do + before { + Chaltron.ldap_group_base = 'ou=groups,dc=azkaban,dc=co,dc=uk' + Chaltron.ldap_group_member_filter = -> (entry) { "memberUid=#{entry.uid}" } + } + subject(:res) { ldap.find_groups_by_member(entry) } + context 'returns right value' do + let(:entry) { Chaltron::LDAP::Person.find_by_uid('sirius') } + it { is_expected.not_to be_empty } + end + context 'returns empty' do + let(:entry) { Chaltron::LDAP::Person.find_by_uid('barty') } + it { is_expected.to be_empty } + end + end + + end + +end diff --git a/spec/lib/chaltron/ldap/person_spec.rb b/spec/lib/chaltron/ldap/person_spec.rb index 986abab..eed1d91 100644 --- a/spec/lib/chaltron/ldap/person_spec.rb +++ b/spec/lib/chaltron/ldap/person_spec.rb @@ -37,7 +37,7 @@ end describe 'search by field' do - subject(:res) { Chaltron::LDAP::Person.find_by_field 'title', title } + subject(:res) { Chaltron::LDAP::Person.find_by_fields(title: title) } context 'returning 2 values' do let(:title) { '* guy' } it { is_expected.to be_an_instance_of Array } @@ -68,11 +68,37 @@ describe 'create by uid' do subject(:barty) { Chaltron::LDAP::Person.find_by_uid('barty').create_user } - it { expect(barty.provider).to eq 'ldap' } - it { expect(barty.username).to eq 'barty' } - it { expect(barty.fullname).to eq 'Bartemius Crouch' } - it { expect(barty.email).to eq 'barty.crouch@azkaban.co.uk' } - it { expect(barty.department).to be_nil } + + context 'without fullname' do + before do + Chaltron.ldap_field_mappings = { + first_name: 'givenName', + last_name: 'sn', + email: 'mail' + } + end + it { expect(barty.provider).to eq 'ldap' } + it { expect(barty.username).to eq 'barty' } + it { expect(barty.fullname).to eq 'Bartemius Crouch' } + it { expect(barty.email).to eq 'barty.crouch@azkaban.co.uk' } + it { expect(barty.department).to be_nil } + end + context 'with fullname' do + before do + Chaltron.ldap_field_mappings = { + full_name: 'displayName', + first_name: 'givenName', + last_name: 'sn', + email: 'mail' + } + end + it { expect(barty.provider).to eq 'ldap' } + it { expect(barty.username).to eq 'barty' } + it { expect(barty.fullname).to eq 'Bartemius Crouch Jr.' } + it { expect(barty.email).to eq 'barty.crouch@azkaban.co.uk' } + it { expect(barty.department).to be_nil } + end + end end diff --git a/travis-ci/ldap/entry.ldif b/travis-ci/ldap/entry.ldif index 6b23cb2..57b62c1 100644 --- a/travis-ci/ldap/entry.ldif +++ b/travis-ci/ldap/entry.ldif @@ -1,15 +1,18 @@ dn: ou=people,dc=azkaban,dc=co,dc=uk objectClass: organizationalUnit +objectClass: top ou: people dn: ou=groups,dc=azkaban,dc=co,dc=uk objectClass: organizationalUnit +objectClass: top ou: groups dn: uid=sirius,ou=people,dc=azkaban,dc=co,dc=uk objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount +objectClass: top uid: sirius sn: Black givenName: Sirius @@ -37,6 +40,7 @@ dn: uid=barty,ou=people,dc=azkaban,dc=co,dc=uk objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount +objectClass: top uid: barty sn: Crouch givenName: Bartemius @@ -60,12 +64,9 @@ title: Bad guy postalAddress: Test Home initials: TT -dn: cn=example,ou=groups,dc=azkaban,dc=co,dc=uk +dn: cn=good,ou=groups,dc=azkaban,dc=co,dc=uk objectClass: posixGroup -cn: example +objectClass: top +cn: good gidNumber: 10000 - -dn: cn=example2,ou=groups,dc=azkaban,dc=co,dc=uk -objectClass: posixGroup -cn: example2 -gidNumber: 10001 +memberUid: sirius