prathe_devise_ldap_authenticatable 0.4.10

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.
Files changed (93) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +187 -0
  3. data/Rakefile +52 -0
  4. data/VERSION +1 -0
  5. data/devise_ldap_authenticatable.gemspec +133 -0
  6. data/lib/devise_ldap_authenticatable.rb +48 -0
  7. data/lib/devise_ldap_authenticatable/exception.rb +6 -0
  8. data/lib/devise_ldap_authenticatable/ldap_adapter.rb +242 -0
  9. data/lib/devise_ldap_authenticatable/logger.rb +11 -0
  10. data/lib/devise_ldap_authenticatable/model.rb +101 -0
  11. data/lib/devise_ldap_authenticatable/routes.rb +8 -0
  12. data/lib/devise_ldap_authenticatable/schema.rb +14 -0
  13. data/lib/devise_ldap_authenticatable/strategy.rb +36 -0
  14. data/lib/devise_ldap_authenticatable/version.rb +4 -0
  15. data/lib/generators/devise_ldap_authenticatable/install_generator.rb +62 -0
  16. data/lib/generators/devise_ldap_authenticatable/templates/ldap.yml +51 -0
  17. data/rails/init.rb +2 -0
  18. data/test/devise_ldap_authenticatable_test.rb +8 -0
  19. data/test/ldap/base.ldif +73 -0
  20. data/test/ldap/clear.ldif +26 -0
  21. data/test/ldap/local.schema +6 -0
  22. data/test/ldap/run-server.sh +10 -0
  23. data/test/ldap/server.pem +38 -0
  24. data/test/ldap/slapd-ssl-test.conf +107 -0
  25. data/test/ldap/slapd-test.conf +107 -0
  26. data/test/rails_app/Gemfile +22 -0
  27. data/test/rails_app/Gemfile.lock +159 -0
  28. data/test/rails_app/Rakefile +7 -0
  29. data/test/rails_app/app/controllers/application_controller.rb +4 -0
  30. data/test/rails_app/app/controllers/posts_controller.rb +15 -0
  31. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  32. data/test/rails_app/app/helpers/posts_helper.rb +2 -0
  33. data/test/rails_app/app/models/post.rb +2 -0
  34. data/test/rails_app/app/models/user.rb +10 -0
  35. data/test/rails_app/app/views/layouts/application.html.erb +26 -0
  36. data/test/rails_app/app/views/posts/index.html.erb +2 -0
  37. data/test/rails_app/config.ru +4 -0
  38. data/test/rails_app/config/application.rb +46 -0
  39. data/test/rails_app/config/boot.rb +13 -0
  40. data/test/rails_app/config/cucumber.yml +8 -0
  41. data/test/rails_app/config/database.yml +25 -0
  42. data/test/rails_app/config/environment.rb +5 -0
  43. data/test/rails_app/config/environments/development.rb +22 -0
  44. data/test/rails_app/config/environments/production.rb +46 -0
  45. data/test/rails_app/config/environments/test.rb +34 -0
  46. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  47. data/test/rails_app/config/initializers/devise.rb +140 -0
  48. data/test/rails_app/config/initializers/inflections.rb +10 -0
  49. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  50. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  51. data/test/rails_app/config/initializers/session_store.rb +8 -0
  52. data/test/rails_app/config/ldap.yml +22 -0
  53. data/test/rails_app/config/ldap_with_erb.yml +23 -0
  54. data/test/rails_app/config/ldap_with_uid.yml +18 -0
  55. data/test/rails_app/config/locales/devise.en.yml +39 -0
  56. data/test/rails_app/config/locales/en.yml +5 -0
  57. data/test/rails_app/config/routes.rb +64 -0
  58. data/test/rails_app/config/ssl_ldap.yml +21 -0
  59. data/test/rails_app/config/ssl_ldap_with_erb.yml +23 -0
  60. data/test/rails_app/config/ssl_ldap_with_uid.yml +18 -0
  61. data/test/rails_app/db/migrate/20100708120302_create_posts.rb +14 -0
  62. data/test/rails_app/db/migrate/20100708120448_devise_create_users.rb +26 -0
  63. data/test/rails_app/db/schema.rb +41 -0
  64. data/test/rails_app/db/seeds.rb +7 -0
  65. data/test/rails_app/features/manage_logins.feature +35 -0
  66. data/test/rails_app/features/step_definitions/login_steps.rb +21 -0
  67. data/test/rails_app/features/step_definitions/web_steps.rb +219 -0
  68. data/test/rails_app/features/support/env.rb +58 -0
  69. data/test/rails_app/features/support/paths.rb +38 -0
  70. data/test/rails_app/lib/tasks/.gitkeep +0 -0
  71. data/test/rails_app/lib/tasks/cucumber.rake +53 -0
  72. data/test/rails_app/public/404.html +26 -0
  73. data/test/rails_app/public/422.html +26 -0
  74. data/test/rails_app/public/500.html +26 -0
  75. data/test/rails_app/public/images/rails.png +0 -0
  76. data/test/rails_app/public/javascripts/application.js +2 -0
  77. data/test/rails_app/public/javascripts/controls.js +965 -0
  78. data/test/rails_app/public/javascripts/dragdrop.js +974 -0
  79. data/test/rails_app/public/javascripts/effects.js +1123 -0
  80. data/test/rails_app/public/javascripts/prototype.js +4874 -0
  81. data/test/rails_app/public/javascripts/rails.js +118 -0
  82. data/test/rails_app/public/stylesheets/.gitkeep +0 -0
  83. data/test/rails_app/script/cucumber +10 -0
  84. data/test/rails_app/script/rails +6 -0
  85. data/test/rails_app/test/factories/users.rb +14 -0
  86. data/test/rails_app/test/functional/posts_controller_test.rb +58 -0
  87. data/test/rails_app/test/performance/browsing_test.rb +9 -0
  88. data/test/rails_app/test/test_helper.rb +36 -0
  89. data/test/rails_app/test/unit/helpers/posts_helper_test.rb +4 -0
  90. data/test/rails_app/test/unit/post_test.rb +4 -0
  91. data/test/rails_app/test/unit/user_test.rb +254 -0
  92. data/test/test_helper.rb +3 -0
  93. metadata +161 -0
@@ -0,0 +1,6 @@
1
+ module DeviseLdapAuthenticatable
2
+
3
+ class LdapException < Exception
4
+ end
5
+
6
+ end
@@ -0,0 +1,242 @@
1
+ require "net/ldap"
2
+
3
+ module Devise
4
+
5
+ module LdapAdapter
6
+
7
+ def self.valid_credentials?(login, password_plaintext)
8
+ options = {:login => login,
9
+ :password => password_plaintext,
10
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
11
+ :admin => ::Devise.ldap_use_admin_to_bind}
12
+
13
+ resource = LdapConnect.new(options)
14
+ resource.authorized?
15
+ end
16
+
17
+ def self.valid_login?(login)
18
+ options = {:login => login,
19
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
20
+ :admin => ::Devise.ldap_use_admin_to_bind}
21
+ resource = LdapConnect.new(options)
22
+ resource.valid_login?
23
+ end
24
+
25
+ def self.update_password(login, new_password)
26
+ options = {:login => login,
27
+ :new_password => new_password,
28
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
29
+ :admin => ::Devise.ldap_use_admin_to_bind}
30
+
31
+ resource = LdapConnect.new(options)
32
+ resource.change_password! if new_password.present?
33
+ end
34
+
35
+ def self.get_groups(login)
36
+ options = {:login => login,
37
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
38
+ :admin => ::Devise.ldap_use_admin_to_bind}
39
+
40
+ ldap = LdapConnect.new(options)
41
+ ldap.user_groups
42
+ end
43
+
44
+ def self.get_dn(login)
45
+ options = {:login => login,
46
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
47
+ :admin => ::Devise.ldap_use_admin_to_bind}
48
+ resource = LdapConnect.new(options)
49
+ resource.dn
50
+ end
51
+
52
+ def self.get_ldap_param(login,param)
53
+ options = {:login => login,
54
+ :ldap_auth_username_builder => ::Devise.ldap_auth_username_builder,
55
+ :admin => ::Devise.ldap_use_admin_to_bind}
56
+ resource = LdapConnect.new(options)
57
+ resource.ldap_param_value(param)
58
+ end
59
+
60
+ class LdapConnect
61
+
62
+ attr_reader :ldap, :login
63
+
64
+ def initialize(params = {})
65
+ ldap_config = YAML.load(ERB.new(File.read(::Devise.ldap_config || "#{Rails.root}/config/ldap.yml")).result)[Rails.env]
66
+ ldap_options = params
67
+ ldap_config["ssl"] = :simple_tls if ldap_config["ssl"] === true
68
+ ldap_options[:encryption] = ldap_config["ssl"].to_sym if ldap_config["ssl"]
69
+
70
+ @ldap = Net::LDAP.new(ldap_options)
71
+ @ldap.host = ldap_config["host"]
72
+ @ldap.port = ldap_config["port"]
73
+ @ldap.base = ldap_config["base"]
74
+ @attribute = ldap_config["attribute"]
75
+ @ldap_auth_username_builder = params[:ldap_auth_username_builder]
76
+
77
+ @group_base = ldap_config["group_base"]
78
+ @required_groups = ldap_config["required_groups"]
79
+ @required_attributes = ldap_config["require_attribute"]
80
+
81
+ @ldap.auth ldap_config["admin_user"], ldap_config["admin_password"] if params[:admin]
82
+
83
+ @login = params[:login]
84
+ @password = params[:password]
85
+ @new_password = params[:new_password]
86
+ end
87
+
88
+ def dn
89
+ DeviseLdapAuthenticatable::Logger.send("LDAP dn lookup: #{@attribute}=#{@login}")
90
+ ldap_entry = search_for_login
91
+ if ldap_entry.nil?
92
+ @ldap_auth_username_builder.call(@attribute,@login,@ldap)
93
+ else
94
+ ldap_entry.dn
95
+ end
96
+ end
97
+
98
+ def ldap_param_value(param)
99
+ filter = Net::LDAP::Filter.eq(@attribute.to_s, @login.to_s)
100
+ ldap_entry = nil
101
+ @ldap.search(:filter => filter) {|entry| ldap_entry = entry}
102
+
103
+ DeviseLdapAuthenticatable::Logger.send("Requested param #{param} has value #{ldap_entry.send(param)}")
104
+ ldap_entry.send(param)
105
+ end
106
+
107
+ def authenticate!
108
+ @ldap.auth(dn, @password)
109
+ @ldap.bind
110
+ end
111
+
112
+ def authenticated?
113
+ authenticate!
114
+ end
115
+
116
+ def authorized?
117
+ DeviseLdapAuthenticatable::Logger.send("Authorizing user #{dn}")
118
+ authenticated? && in_required_groups? && has_required_attribute?
119
+ end
120
+
121
+ def change_password!
122
+ update_ldap(:userpassword => Net::LDAP::Password.generate(:sha, @new_password))
123
+ end
124
+
125
+ def in_required_groups?
126
+ return true unless ::Devise.ldap_check_group_membership
127
+
128
+ ## FIXME set errors here, the ldap.yml isn't set properly.
129
+ return false if @required_groups.nil?
130
+
131
+ admin_ldap = LdapConnect.admin
132
+
133
+ for group in @required_groups
134
+ if group.is_a?(Array)
135
+ group_attribute, group_name = group
136
+ else
137
+ group_attribute = "uniqueMember"
138
+ group_name = group
139
+ end
140
+ unless ::Devise.ldap_ad_group_check
141
+ admin_ldap.search(:base => group_name, :scope => Net::LDAP::SearchScope_BaseObject) do |entry|
142
+ unless entry[group_attribute].include? dn
143
+ DeviseLdapAuthenticatable::Logger.send("User #{dn} is not in group: #{group_name }")
144
+ return false
145
+ end
146
+ end
147
+ else
148
+ # AD optimization - extension will recursively check sub-groups with one query
149
+ # "(memberof:1.2.840.113556.1.4.1941:=group_name)"
150
+ search_result = admin_ldap.search(:base => dn,
151
+ :filter => Net::LDAP::Filter.ex("memberof:1.2.840.113556.1.4.1941", group_name),
152
+ :scope => Net::LDAP::SearchScope_BaseObject)
153
+ # Will return the user entry if belongs to group otherwise nothing
154
+ unless search_result.length == 1 && search_result[0].dn.eql?(dn)
155
+ DeviseLdapAuthenticatable::Logger.send("User #{dn} is not in group: #{group_name }")
156
+ return false
157
+ end
158
+ end
159
+ end
160
+
161
+ return true
162
+ end
163
+
164
+ def has_required_attribute?
165
+ return true unless ::Devise.ldap_check_attributes
166
+
167
+ admin_ldap = LdapConnect.admin
168
+
169
+ user = find_ldap_user(admin_ldap)
170
+
171
+ @required_attributes.each do |key,val|
172
+ unless user[key].include? val
173
+ DeviseLdapAuthenticatable::Logger.send("User #{dn} did not match attribute #{key}:#{val}")
174
+ return false
175
+ end
176
+ end
177
+
178
+ return true
179
+ end
180
+
181
+ def user_groups
182
+ admin_ldap = LdapConnect.admin
183
+
184
+ DeviseLdapAuthenticatable::Logger.send("Getting groups for #{dn}")
185
+ filter = Net::LDAP::Filter.eq("uniqueMember", dn)
186
+ admin_ldap.search(:filter => filter, :base => @group_base).collect(&:dn)
187
+ end
188
+
189
+ def valid_login?
190
+ !search_for_login.nil?
191
+ end
192
+
193
+ private
194
+
195
+ def self.admin
196
+ ldap = LdapConnect.new(:admin => true).ldap
197
+
198
+ unless ldap.bind
199
+ DeviseLdapAuthenticatable::Logger.send("Cannot bind to admin LDAP user")
200
+ raise DeviseLdapAuthenticatable::LdapException, "Cannot connect to admin LDAP user"
201
+ end
202
+
203
+ return ldap
204
+ end
205
+
206
+ def find_ldap_user(ldap)
207
+ DeviseLdapAuthenticatable::Logger.send("Finding user: #{dn}")
208
+ ldap.search(:base => dn, :scope => Net::LDAP::SearchScope_BaseObject).try(:first)
209
+ end
210
+
211
+ # Searches the LDAP for the login
212
+ #
213
+ # @return [Object] the LDAP entry found; nil if not found
214
+ def search_for_login
215
+ DeviseLdapAuthenticatable::Logger.send("LDAP search for login: #{@attribute}=#{@login}")
216
+ filter = Net::LDAP::Filter.eq(@attribute.to_s, @login.to_s)
217
+ ldap_entry = nil
218
+ @ldap.search(:filter => filter) {|entry| ldap_entry = entry}
219
+ ldap_entry
220
+ end
221
+
222
+ def update_ldap(ops)
223
+ operations = []
224
+ if ops.is_a? Hash
225
+ ops.each do |key,value|
226
+ operations << [:replace,key,value]
227
+ end
228
+ elsif ops.is_a? Array
229
+ operations = ops
230
+ end
231
+
232
+ admin_ldap = LdapConnect.admin
233
+
234
+ DeviseLdapAuthenticatable::Logger.send("Modifying user #{dn}")
235
+ admin_ldap.modify(:dn => dn, :operations => operations)
236
+ end
237
+
238
+ end
239
+
240
+ end
241
+
242
+ end
@@ -0,0 +1,11 @@
1
+ module DeviseLdapAuthenticatable
2
+
3
+ class Logger
4
+ def self.send(message, logger = Rails.logger)
5
+ if ::Devise.ldap_logger
6
+ logger.add 0, " \e[36mLDAP:\e[0m #{message}"
7
+ end
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,101 @@
1
+ require 'devise_ldap_authenticatable/strategy'
2
+
3
+ module Devise
4
+ module Models
5
+ # LDAP Module, responsible for validating the user credentials via LDAP.
6
+ #
7
+ # Examples:
8
+ #
9
+ # User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
10
+ # User.find(1).valid_password?('password123') # returns true/false
11
+ #
12
+ module LdapAuthenticatable
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ attr_reader :current_password, :password
17
+ attr_accessor :password_confirmation
18
+ end
19
+
20
+ def login_with
21
+ @login_with ||= Devise.mappings[self.class.to_s.underscore.to_sym].to.authentication_keys.first
22
+ self[@login_with]
23
+ end
24
+
25
+ def reset_password!(new_password, new_password_confirmation)
26
+ if new_password == new_password_confirmation && ::Devise.ldap_update_password
27
+ Devise::LdapAdapter.update_password(login_with, new_password)
28
+ end
29
+ clear_reset_password_token if valid?
30
+ save
31
+ end
32
+
33
+ def password=(new_password)
34
+ @password = new_password
35
+ end
36
+
37
+ # Checks if a resource is valid upon authentication.
38
+ def valid_ldap_authentication?(password)
39
+ if Devise::LdapAdapter.valid_credentials?(login_with, password)
40
+ return true
41
+ else
42
+ return false
43
+ end
44
+ end
45
+
46
+ def ldap_groups
47
+ Devise::LdapAdapter.get_groups(login_with)
48
+ end
49
+
50
+ def ldap_dn
51
+ Devise::LdapAdapter.get_dn(login_with)
52
+ end
53
+
54
+ def ldap_get_param(login_with, param)
55
+ Devise::LdapAdapter.get_ldap_param(login_with,param)
56
+ end
57
+
58
+ #
59
+ # callbacks
60
+ #
61
+
62
+ # # Called before the ldap record is saved automatically
63
+ # def ldap_before_save
64
+ # end
65
+
66
+
67
+ module ClassMethods
68
+ # Authenticate a user based on configured attribute keys. Returns the
69
+ # authenticated user if it's valid or nil.
70
+ def authenticate_with_ldap(attributes={})
71
+ auth_key = self.authentication_keys.first
72
+ return nil unless attributes[auth_key].present?
73
+
74
+ # resource = find_for_ldap_authentication(conditions)
75
+ resource = where(auth_key => attributes[auth_key]).first
76
+
77
+ if (resource.blank? and ::Devise.ldap_create_user)
78
+ resource = new
79
+ resource[auth_key] = attributes[auth_key]
80
+ resource.password = attributes[:password]
81
+ end
82
+
83
+ if resource.try(:valid_ldap_authentication?, attributes[:password])
84
+ if resource.new_record?
85
+ resource.ldap_before_save if resource.respond_to?(:ldap_before_save)
86
+ resource.save
87
+ end
88
+ return resource
89
+ else
90
+ return nil
91
+ end
92
+ end
93
+
94
+ def update_with_password(resource)
95
+ puts "UPDATE_WITH_PASSWORD: #{resource.inspect}"
96
+ end
97
+
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,8 @@
1
+ ## No routes needed anymore since Devise.add_module with the :route parameter will take care of it.
2
+
3
+ # ActionController::Routing::RouteSet::Mapper.class_eval do
4
+ #
5
+ # protected
6
+ # # reuse the session routes and controller
7
+ # alias :ldap_authenticatable :database_authenticatable
8
+ # end
@@ -0,0 +1,14 @@
1
+ ## Using email now instead of login. Will add an option later on.
2
+
3
+ # Devise::Schema.class_eval do
4
+ # # Creates login
5
+ # #
6
+ # # == Options
7
+ # # * :null - When true, allow columns to be null.
8
+ # def ldap_authenticatable(options={})
9
+ # null = options[:null] || false
10
+ #
11
+ # apply_schema :login, String, :null => null
12
+ # end
13
+ #
14
+ # end
@@ -0,0 +1,36 @@
1
+ require 'devise/strategies/authenticatable'
2
+
3
+ module Devise
4
+ module Strategies
5
+ # Strategy for signing in a user based on his login and password using LDAP.
6
+ # Redirects to sign_in page if it's not authenticated
7
+ class LdapAuthenticatable < Authenticatable
8
+ def valid?
9
+ valid_controller? && valid_params? && mapping.to.respond_to?(:authenticate_with_ldap)
10
+ end
11
+
12
+ # Authenticate a user based on login and password params, returning to warden
13
+ # success and the authenticated user if everything is okay. Otherwise redirect
14
+ # to sign in page.
15
+ def authenticate!
16
+ if resource = mapping.to.authenticate_with_ldap(params[scope])
17
+ success!(resource)
18
+ else
19
+ fail(:invalid)
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def valid_controller?
26
+ params[:controller] == mapping.controllers[:sessions]
27
+ end
28
+
29
+ def valid_params?
30
+ params[scope] && params[scope][:password].present?
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
@@ -0,0 +1,4 @@
1
+ module DeviseLdapAuthenticatable
2
+ VERSION = "0.4.6"
3
+ end
4
+
@@ -0,0 +1,62 @@
1
+ module DeviseLdapAuthenticatable
2
+ class InstallGenerator < Rails::Generators::Base
3
+ source_root File.expand_path("../templates", __FILE__)
4
+
5
+ class_option :user_model, :type => :string, :default => "user", :desc => "Model to update"
6
+ class_option :update_model, :type => :boolean, :default => true, :desc => "Update model to change from database_authenticatable to ldap_authenticatable"
7
+ class_option :add_rescue, :type => :boolean, :default => true, :desc => "Update Application Controller with resuce_from for DeviseLdapAuthenticatable::LdapException"
8
+ class_option :advanced, :type => :boolean, :desc => "Add advanced config options to the devise initializer"
9
+
10
+
11
+ def create_ldap_config
12
+ copy_file "ldap.yml", "config/ldap.yml"
13
+ end
14
+
15
+ def create_default_devise_settings
16
+ inject_into_file "config/initializers/devise.rb", default_devise_settings, :after => "Devise.setup do |config|\n"
17
+ end
18
+
19
+ def update_user_model
20
+ gsub_file "app/models/#{options.user_model}.rb", /:database_authenticatable/, ":ldap_authenticatable" if options.update_model?
21
+ end
22
+
23
+ def update_application_controller
24
+ inject_into_class "app/controllers/application_controller.rb", ApplicationController, rescue_from_exception if options.add_rescue?
25
+ end
26
+
27
+ private
28
+
29
+ def default_devise_settings
30
+ settings = <<-eof
31
+ # ==> LDAP Configuration
32
+ # config.ldap_logger = true
33
+ # config.ldap_create_user = false
34
+ # config.ldap_update_password = true
35
+ # config.ldap_config = "\#{Rails.root}/config/ldap.yml"
36
+ # config.ldap_check_group_membership = false
37
+ # config.ldap_check_attributes = false
38
+ # config.ldap_use_admin_to_bind = false
39
+ # config.ldap_ad_group_check = false
40
+
41
+ eof
42
+ if options.advanced?
43
+ settings << <<-eof
44
+ # ==> Advanced LDAP Configuration
45
+ # config.ldap_auth_username_builder = Proc.new() {|attribute, login, ldap| "\#{attribute}=\#{login},\#{ldap.base}" }
46
+
47
+ eof
48
+ end
49
+
50
+ settings
51
+ end
52
+
53
+ def rescue_from_exception
54
+ <<-eof
55
+ rescue_from DeviseLdapAuthenticatable::LdapException do |exception|
56
+ render :text => exception, :status => 500
57
+ end
58
+ eof
59
+ end
60
+
61
+ end
62
+ end