devise_ldap_authenticatable 0.8.1 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- require 'devise_ldap_authenticatable/strategy'
1
+ require 'devise_ldap_authenticatable/strategy'
2
2
 
3
3
  module Devise
4
4
  module Models
@@ -18,7 +18,7 @@ module Devise
18
18
  end
19
19
 
20
20
  def login_with
21
- @login_with ||= Devise.mappings[self.class.to_s.underscore.to_sym].to.authentication_keys.first
21
+ @login_with ||= Devise.mappings.find {|k,v| v.class_name == self.class.name}.last.to.authentication_keys.first
22
22
  self[@login_with]
23
23
  end
24
24
 
@@ -45,15 +45,15 @@ module Devise
45
45
 
46
46
  # Checks if a resource is valid upon authentication.
47
47
  def valid_ldap_authentication?(password)
48
- if Devise::LDAP::Adapter.valid_credentials?(login_with, password)
49
- return true
50
- else
51
- return false
52
- end
48
+ Devise::LDAP::Adapter.valid_credentials?(login_with, password)
49
+ end
50
+
51
+ def ldap_entry
52
+ @ldap_entry ||= Devise::LDAP::Adapter.get_ldap_entry(login_with)
53
53
  end
54
54
 
55
55
  def ldap_groups
56
- Devise::LDAP::Adapter.get_groups(login_with)
56
+ @ldap_groups ||= Devise::LDAP::Adapter.get_groups(login_with)
57
57
  end
58
58
 
59
59
  def in_ldap_group?(group_name, group_attribute = LDAP::DEFAULT_GROUP_UNIQUE_MEMBER_LIST_KEY)
@@ -61,11 +61,15 @@ module Devise
61
61
  end
62
62
 
63
63
  def ldap_dn
64
- Devise::LDAP::Adapter.get_dn(login_with)
64
+ ldap_entry ? ldap_entry.dn : nil
65
65
  end
66
66
 
67
- def ldap_get_param(login_with, param)
68
- Devise::LDAP::Adapter.get_ldap_param(login_with,param)
67
+ def ldap_get_param(param)
68
+ if ldap_entry && !ldap_entry[param].empty?
69
+ value = ldap_entry.send(param)
70
+ else
71
+ nil
72
+ end
69
73
  end
70
74
 
71
75
  #
@@ -76,34 +80,34 @@ module Devise
76
80
  # def ldap_before_save
77
81
  # end
78
82
 
83
+ # Called after a successful LDAP authentication
84
+ def after_ldap_authentication
85
+ end
86
+
79
87
 
80
88
  module ClassMethods
81
- # Authenticate a user based on configured attribute keys. Returns the
82
- # authenticated user if it's valid or nil.
83
- def authenticate_with_ldap(attributes={})
89
+ # Find a user for ldap authentication.
90
+ def find_for_ldap_authentication(attributes={})
84
91
  auth_key = self.authentication_keys.first
85
92
  return nil unless attributes[auth_key].present?
86
93
 
87
94
  auth_key_value = (self.case_insensitive_keys || []).include?(auth_key) ? attributes[auth_key].downcase : attributes[auth_key]
95
+ auth_key_value = (self.strip_whitespace_keys || []).include?(auth_key) ? auth_key_value.strip : auth_key_value
88
96
 
89
- # resource = find_for_ldap_authentication(conditions)
90
97
  resource = where(auth_key => auth_key_value).first
91
98
 
92
- if (resource.blank? and ::Devise.ldap_create_user)
99
+ if resource.blank?
93
100
  resource = new
94
101
  resource[auth_key] = auth_key_value
95
102
  resource.password = attributes[:password]
96
103
  end
97
104
 
98
- if resource.try(:valid_ldap_authentication?, attributes[:password])
99
- if resource.new_record?
100
- resource.ldap_before_save if resource.respond_to?(:ldap_before_save)
101
- resource.save!
102
- end
103
- return resource
104
- else
105
- return nil
105
+ if ::Devise.ldap_create_user && resource.new_record? && resource.valid_ldap_authentication?(attributes[:password])
106
+ resource.ldap_before_save if resource.respond_to?(:ldap_before_save)
107
+ resource.save!
106
108
  end
109
+
110
+ resource
107
111
  end
108
112
 
109
113
  def update_with_password(resource)
@@ -3,16 +3,37 @@ require 'devise/strategies/authenticatable'
3
3
  module Devise
4
4
  module Strategies
5
5
  class LdapAuthenticatable < Authenticatable
6
+
7
+ # Tests whether the returned resource exists in the database and the
8
+ # credentials are valid. If the resource is in the database and the credentials
9
+ # are valid, the user is authenticated. Otherwise failure messages are returned
10
+ # indicating whether the resource is not found in the database or the credentials
11
+ # are invalid.
6
12
  def authenticate!
7
- resource = valid_password? && mapping.to.authenticate_with_ldap(authentication_hash.merge(password: password))
13
+ resource = mapping.to.find_for_ldap_authentication(authentication_hash.merge(:password => password))
14
+
8
15
  return fail(:invalid) unless resource
9
16
 
10
- if validate(resource)
11
- success!(resource)
17
+ if resource.persisted?
18
+ if validate(resource) { resource.valid_ldap_authentication?(password) }
19
+ remember_me(resource)
20
+ resource.after_ldap_authentication
21
+ success!(resource)
22
+ else
23
+ return fail(:invalid) # Invalid credentials
24
+ end
25
+ end
26
+
27
+ if resource.new_record?
28
+ if validate(resource) { resource.valid_ldap_authentication?(password) }
29
+ return fail(:not_found_in_database) # Valid credentials
30
+ else
31
+ return fail(:invalid) # Invalid credentials
32
+ end
12
33
  end
13
34
  end
14
35
  end
15
36
  end
16
37
  end
17
38
 
18
- Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
39
+ Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
@@ -1,3 +1,3 @@
1
1
  module DeviseLdapAuthenticatable
2
- VERSION = "0.8.1".freeze
3
- end
2
+ VERSION = "0.8.7".freeze
3
+ end
@@ -1,55 +1,57 @@
1
1
  module DeviseLdapAuthenticatable
2
2
  class InstallGenerator < Rails::Generators::Base
3
3
  source_root File.expand_path("../templates", __FILE__)
4
-
4
+
5
5
  class_option :user_model, :type => :string, :default => "user", :desc => "Model to update"
6
6
  class_option :update_model, :type => :boolean, :default => true, :desc => "Update model to change from database_authenticatable to ldap_authenticatable"
7
7
  class_option :add_rescue, :type => :boolean, :default => true, :desc => "Update Application Controller with resuce_from for DeviseLdapAuthenticatable::LdapException"
8
8
  class_option :advanced, :type => :boolean, :desc => "Add advanced config options to the devise initializer"
9
-
10
-
9
+
10
+
11
11
  def create_ldap_config
12
12
  copy_file "ldap.yml", "config/ldap.yml"
13
13
  end
14
-
14
+
15
15
  def create_default_devise_settings
16
- inject_into_file "config/initializers/devise.rb", default_devise_settings, :after => "Devise.setup do |config|\n"
16
+ inject_into_file "config/initializers/devise.rb", default_devise_settings, :after => "Devise.setup do |config|\n"
17
17
  end
18
-
18
+
19
19
  def update_user_model
20
20
  gsub_file "app/models/#{options.user_model}.rb", /:database_authenticatable/, ":ldap_authenticatable" if options.update_model?
21
21
  end
22
-
22
+
23
23
  def update_application_controller
24
24
  inject_into_class "app/controllers/application_controller.rb", ApplicationController, rescue_from_exception if options.add_rescue?
25
25
  end
26
-
26
+
27
27
  private
28
-
28
+
29
29
  def default_devise_settings
30
30
  settings = <<-eof
31
- # ==> LDAP Configuration
31
+ # ==> LDAP Configuration
32
32
  # config.ldap_logger = true
33
33
  # config.ldap_create_user = false
34
34
  # config.ldap_update_password = true
35
35
  # config.ldap_config = "\#{Rails.root}/config/ldap.yml"
36
36
  # config.ldap_check_group_membership = false
37
+ # config.ldap_check_group_membership_without_admin = false
37
38
  # config.ldap_check_attributes = false
39
+ # config.ldap_check_attributes_presence = false
38
40
  # config.ldap_use_admin_to_bind = false
39
41
  # config.ldap_ad_group_check = false
40
-
42
+
41
43
  eof
42
- if options.advanced?
43
- settings << <<-eof
44
+ if options.advanced?
45
+ settings << <<-eof
44
46
  # ==> Advanced LDAP Configuration
45
47
  # config.ldap_auth_username_builder = Proc.new() {|attribute, login, ldap| "\#{attribute}=\#{login},\#{ldap.base}" }
46
-
48
+
47
49
  eof
48
50
  end
49
-
51
+
50
52
  settings
51
53
  end
52
-
54
+
53
55
  def rescue_from_exception
54
56
  <<-eof
55
57
  rescue_from DeviseLdapAuthenticatable::LdapException do |exception|
@@ -57,6 +59,6 @@ module DeviseLdapAuthenticatable
57
59
  end
58
60
  eof
59
61
  end
60
-
62
+
61
63
  end
62
64
  end
@@ -1,8 +1,9 @@
1
1
  ## Authorizations
2
2
  # Uncomment out the merging for each environment that you'd like to include.
3
3
  # You can also just copy and paste the tree (do not include the "authorizations") to each
4
- # environment if you need something different per enviornment.
4
+ # environment if you need something different per environment.
5
5
  authorizations: &AUTHORIZATIONS
6
+ allow_unauthenticated_bind: false
6
7
  group_base: ou=groups,dc=test,dc=com
7
8
  ## Requires config.ldap_check_group_membership in devise.rb be true
8
9
  # Can have multiple values, must match all to be authorized
@@ -17,6 +18,12 @@ authorizations: &AUTHORIZATIONS
17
18
  require_attribute:
18
19
  objectClass: inetOrgPerson
19
20
  authorizationRole: postsAdmin
21
+ ## Requires config.ldap_check_attributes_presence in devise.rb to be true
22
+ ## Can have multiple attributes set to true or false to check presence, all must match all to be authorized
23
+ require_attribute_presence:
24
+ mail: true
25
+ telephoneNumber: true
26
+ serviceAccount: false
20
27
 
21
28
  ## Environment
22
29
 
@@ -16,6 +16,11 @@ Devise.setup do |config|
16
16
  # note that it will be overwritten if you use your own mailer class with default "from" parameter.
17
17
  config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
18
18
 
19
+
20
+ if ::Devise.respond_to?(:secret_key)
21
+ ::Devise.secret_key = '012a16191a7f61e84e55704e34b73db991a23ba396b6b7760596a3e80073e4464c55421c42a1a34327dee44828bec6745c48eba10cc0866799ec95c09ea27ada'
22
+ end
23
+
19
24
  # Configure the class responsible to send e-mails.
20
25
  # config.mailer = "Devise::Mailer"
21
26
 
@@ -7,7 +7,9 @@ authorizations: &AUTHORIZATIONS
7
7
  require_attribute:
8
8
  objectClass: inetOrgPerson
9
9
  authorizationRole: blogAdmin
10
-
10
+ require_attribute_presence:
11
+ mail: true
12
+
11
13
  test: &TEST
12
14
  host: localhost
13
15
  port: 3389
@@ -17,6 +19,6 @@ test: &TEST
17
19
  admin_password: secret
18
20
  ssl: false
19
21
  <<: *AUTHORIZATIONS
20
-
22
+
21
23
  development:
22
24
  <<: *TEST
@@ -6,9 +6,11 @@ authorizations: &AUTHORIZATIONS
6
6
  required_groups:
7
7
  - cn=admins,<%= "ou=groups,#{@base}" %>
8
8
  require_attribute:
9
- objectClass: inetOrgPerson
9
+ objectClass:
10
+ - inetOrgPerson
11
+ - organizationalPerson
10
12
  authorizationRole: blogAdmin
11
-
13
+
12
14
  test: &TEST
13
15
  host: <%= "localhost" %>
14
16
  port: 3389
@@ -18,6 +20,6 @@ test: &TEST
18
20
  admin_password: secret
19
21
  ssl: false
20
22
  <<: *AUTHORIZATIONS
21
-
23
+
22
24
  development:
23
25
  <<: *TEST
@@ -17,6 +17,7 @@ en:
17
17
  unauthenticated: 'You need to sign in or sign up before continuing.'
18
18
  unconfirmed: 'You have to confirm your account before continuing.'
19
19
  locked: 'Your account is locked.'
20
+ not_found_in_database: "Your account is not present in this application's database."
20
21
  invalid: 'Invalid email or password.'
21
22
  invalid_token: 'Invalid authentication token.'
22
23
  timeout: 'Your session expired, please sign in again to continue.'
@@ -1,4 +1,4 @@
1
- class DeviseCreateUsers < ActiveRecord::Migration
1
+ class DeviseCreateUsers < ActiveRecord::Migration[5.1]
2
2
  def self.up
3
3
  create_table(:users) do |t|
4
4
  ## Database authenticatable
@@ -1,4 +1,3 @@
1
- # encoding: UTF-8
2
1
  # This file is auto-generated from the current state of the database. Instead
3
2
  # of editing this file, please use the migrations feature of Active Record to
4
3
  # incrementally modify your database, and then regenerate this schema definition.
@@ -13,23 +12,22 @@
13
12
 
14
13
  ActiveRecord::Schema.define(version: 20100708120448) do
15
14
 
16
- create_table "users", force: true do |t|
17
- t.string "email", default: "", null: false
18
- t.string "encrypted_password", default: "", null: false
19
- t.string "reset_password_token"
15
+ create_table "users", force: :cascade do |t|
16
+ t.string "email", default: "", null: false
17
+ t.string "encrypted_password", default: "", null: false
18
+ t.string "reset_password_token"
20
19
  t.datetime "reset_password_sent_at"
21
20
  t.datetime "remember_created_at"
22
- t.integer "sign_in_count", default: 0
21
+ t.integer "sign_in_count", default: 0
23
22
  t.datetime "current_sign_in_at"
24
23
  t.datetime "last_sign_in_at"
25
- t.string "current_sign_in_ip"
26
- t.string "last_sign_in_ip"
27
- t.string "uid"
28
- t.datetime "created_at"
29
- t.datetime "updated_at"
24
+ t.string "current_sign_in_ip"
25
+ t.string "last_sign_in_ip"
26
+ t.string "uid"
27
+ t.datetime "created_at", null: false
28
+ t.datetime "updated_at", null: false
29
+ t.index ["email"], name: "index_users_on_email", unique: true
30
+ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
30
31
  end
31
32
 
32
- add_index "users", ["email"], name: "index_users_on_email", unique: true
33
- add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
34
-
35
33
  end
@@ -2,9 +2,16 @@ ENV["RAILS_ENV"] = "test"
2
2
 
3
3
  require File.expand_path("rails_app/config/environment.rb", File.dirname(__FILE__))
4
4
  require 'rspec/rails'
5
- require 'rspec/autorun'
6
5
  require 'factory_girl' # not sure why this is not already required
7
6
 
7
+ # Rails 4.1 and RSpec are a bit on different pages on who should run migrations
8
+ # on the test db and when.
9
+ #
10
+ # https://github.com/rspec/rspec-rails/issues/936
11
+ if defined?(ActiveRecord::Migration) && ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
12
+ ActiveRecord::Migration.maintain_test_schema!
13
+ end
14
+
8
15
  Dir[File.expand_path("support/**/*.rb", File.dirname(__FILE__))].each {|f| require f}
9
16
 
10
17
  RSpec.configure do |config|
@@ -42,6 +49,7 @@ def default_devise_settings!
42
49
  ::Devise.ldap_config = "#{Rails.root}/config/#{"ssl_" if ENV["LDAP_SSL"]}ldap.yml"
43
50
  ::Devise.ldap_check_group_membership = false
44
51
  ::Devise.ldap_check_attributes = false
52
+ ::Devise.ldap_check_attributes_presence = false
45
53
  ::Devise.ldap_auth_username_builder = Proc.new() {|attribute, login, ldap| "#{attribute}=#{login},#{ldap.base}" }
46
54
  ::Devise.authentication_keys = [:email]
47
55
  end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Devise::LDAP::Adapter do
4
+ describe '#expired_valid_credentials?' do
5
+ before do
6
+ ::Devise.ldap_use_admin_to_bind = true
7
+ expect_any_instance_of(Devise::LDAP::Connection).to receive(:expired_valid_credentials?)
8
+ end
9
+
10
+ it 'can bind as the admin user' do
11
+ expect(Devise::LDAP::Connection).to receive(:new)
12
+ .with(hash_including(
13
+ :login => 'test.user@test.com',
14
+ :password => 'pass',
15
+ :ldap_auth_username_builder => kind_of(Proc),
16
+ :admin => true)).and_call_original
17
+
18
+ Devise::LDAP::Adapter.expired_valid_credentials?('test.user@test.com', 'pass')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,121 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe 'Connection' do
4
+ it 'accepts a proc for ldap_config' do
5
+ ::Devise.ldap_config = Proc.new() {{
6
+ 'host' => 'localhost',
7
+ 'port' => 3389,
8
+ 'base' => 'ou=testbase,dc=test,dc=com',
9
+ 'attribute' => 'cn',
10
+ }}
11
+ connection = Devise::LDAP::Connection.new()
12
+ expect(connection.ldap.base).to eq('ou=testbase,dc=test,dc=com')
13
+ end
14
+
15
+ it 'allows encryption options to be set in ldap_config' do
16
+ ::Devise.ldap_config = Proc.new() {{
17
+ 'host' => 'localhost',
18
+ 'port' => 3389,
19
+ 'base' => 'ou=testbase,dc=test,dc=com',
20
+ 'attribute' => 'cn',
21
+ 'encryption' => {
22
+ :method => :simple_tls,
23
+ :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
24
+ }
25
+ }}
26
+ connection = Devise::LDAP::Connection.new()
27
+ expect(connection.ldap.instance_variable_get(:@encryption)).to eq({
28
+ :method => :simple_tls,
29
+ :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
30
+ })
31
+ end
32
+
33
+ class TestOpResult
34
+ attr_accessor :error_message
35
+ end
36
+
37
+ describe '#expired_valid_credentials?' do
38
+ let(:conn) { double(Net::LDAP).as_null_object }
39
+ let(:error) { }
40
+ let(:is_authed) { false }
41
+ before do
42
+ expect(Net::LDAP).to receive(:new).and_return(conn)
43
+ allow(conn).to receive(:get_operation_result).and_return(TestOpResult.new.tap{|r| r.error_message = error})
44
+ allow_any_instance_of(Devise::LDAP::Connection).to receive(:authenticated?).and_return(is_authed)
45
+ allow_any_instance_of(Devise::LDAP::Connection).to receive(:dn).and_return('any dn')
46
+ expect(DeviseLdapAuthenticatable::Logger).to receive(:send).with('Authorizing user any dn')
47
+ end
48
+ subject do
49
+ Devise::LDAP::Connection.new.expired_valid_credentials?
50
+ end
51
+
52
+ context do
53
+ let(:error) { 'THIS PART CAN BE ANYTHING AcceptSecurityContext error, data 773 SO CAN THIS' }
54
+ it 'is true when expired credential error is returned and not already authenticated' do
55
+ expect(subject).to be true
56
+ end
57
+ end
58
+
59
+ context do
60
+ it 'is false when expired credential error is not returned and not already authenticated' do
61
+ expect(subject).to be false
62
+ end
63
+ end
64
+
65
+ context do
66
+ let(:is_authed) { true }
67
+ it 'is false when expired credential error is not returned and already authenticated' do
68
+ expect(subject).to be false
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#authorized?' do
74
+ let(:conn) { double(Net::LDAP).as_null_object }
75
+ let(:error) { }
76
+ let(:log_message) { }
77
+ let(:is_authed) { false }
78
+ before do
79
+ expect(Net::LDAP).to receive(:new).and_return(conn)
80
+ allow(conn).to receive(:get_operation_result).and_return(TestOpResult.new.tap{|r| r.error_message = error})
81
+ allow_any_instance_of(Devise::LDAP::Connection).to receive(:authenticated?).and_return(is_authed)
82
+ allow_any_instance_of(Devise::LDAP::Connection).to receive(:dn).and_return('any dn')
83
+ expect(DeviseLdapAuthenticatable::Logger).to receive(:send).with('Authorizing user any dn')
84
+ end
85
+ subject do
86
+ Devise::LDAP::Connection.new.authorized?
87
+ end
88
+ context do
89
+ before { expect(DeviseLdapAuthenticatable::Logger).to receive(:send).with(log_message) }
90
+
91
+ context do
92
+ let(:error) { 'THIS PART CAN BE ANYTHING AcceptSecurityContext error, data 52e SO CAN THIS' }
93
+ let(:log_message) { 'Not authorized because of invalid credentials.' }
94
+ it 'is false when credential error is returned' do
95
+ expect(subject).to be false
96
+ end
97
+ end
98
+ context do
99
+ let(:error) { 'THIS PART CAN BE ANYTHING AcceptSecurityContext error, data 773 SO CAN THIS' }
100
+ let(:log_message) { 'Not authorized because of expired credentials.' }
101
+ it 'is false when expired error is returned' do
102
+ expect(subject).to be false
103
+ end
104
+ end
105
+ context do
106
+ let(:error) { 'any error' }
107
+ let(:log_message) { 'Not authorized because not authenticated.' }
108
+ it 'is false when any other error is returned' do
109
+ expect(subject).to be false
110
+ end
111
+ end
112
+ end
113
+
114
+ context do
115
+ let(:is_authed) { true }
116
+ it 'is true when already authenticated' do
117
+ expect(subject).to be true
118
+ end
119
+ end
120
+ end
121
+ end