devise_ldap_authenticatable 0.8.1 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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