]>
Commit | Line | Data |
---|---|---|
1 | commit 936a14e225037aca4cdeac11c843c7985e636c88 | |
2 | Author: Ismaël Bouya <ismael.bouya@normalesup.org> | |
3 | Date: Mon Jul 24 19:58:24 2017 +0200 | |
4 | ||
5 | Add LDAP to diaspora | |
6 | ||
7 | diff --git a/Gemfile b/Gemfile | |
8 | index 414b0138d..2a934e9c9 100644 | |
9 | --- a/Gemfile | |
10 | +++ b/Gemfile | |
11 | @@ -217,6 +217,9 @@ gem "thor", "0.19.1" | |
12 | ||
13 | # gem "therubyracer", :platform => :ruby | |
14 | ||
15 | +# LDAP | |
16 | +gem 'net-ldap', '~> 0.16' | |
17 | + | |
18 | group :production do # we don"t install these on travis to speed up test runs | |
19 | # Analytics | |
20 | ||
21 | diff --git a/Gemfile.lock b/Gemfile.lock | |
22 | index 84f8172e4..cdbf19fcd 100644 | |
23 | --- a/Gemfile.lock 2019-01-13 19:55:52.538561762 +0100 | |
24 | +++ b/Gemfile.lock 2019-01-13 19:58:11.087099067 +0100 | |
25 | @@ -398,6 +398,7 @@ | |
26 | mysql2 (0.5.2) | |
27 | naught (1.1.0) | |
28 | nenv (0.3.0) | |
29 | + net-ldap (0.16.1) | |
30 | nio4r (2.3.1) | |
31 | nokogiri (1.8.5) | |
32 | mini_portile2 (~> 2.3.0) | |
33 | @@ -820,6 +821,7 @@ | |
34 | minitest | |
35 | mobile-fu (= 1.4.0) | |
36 | mysql2 (= 0.5.2) | |
37 | + net-ldap (~> 0.16) | |
38 | nokogiri (= 1.8.5) | |
39 | omniauth (= 1.8.1) | |
40 | omniauth-tumblr (= 1.2) | |
41 | diff --git a/app/models/user.rb b/app/models/user.rb | |
42 | index 940a48f25..d1e2beeee 100644 | |
43 | --- a/app/models/user.rb | |
44 | +++ b/app/models/user.rb | |
45 | @@ -337,6 +337,12 @@ class User < ActiveRecord::Base | |
46 | end | |
47 | ||
48 | def send_confirm_email | |
49 | + if skip_email_confirmation? | |
50 | + self.email = unconfirmed_email | |
51 | + self.unconfirmed_email = nil | |
52 | + save | |
53 | + end | |
54 | + | |
55 | return if unconfirmed_email.blank? | |
56 | Workers::Mail::ConfirmEmail.perform_async(id) | |
57 | end | |
58 | @@ -554,6 +560,14 @@ class User < ActiveRecord::Base | |
59 | end | |
60 | end | |
61 | ||
62 | + def ldap_user? | |
63 | + AppConfig.ldap.enable? && ldap_dn.present? | |
64 | + end | |
65 | + | |
66 | + def skip_email_confirmation? | |
67 | + ldap_user? && AppConfig.ldap.skip_email_confirmation? | |
68 | + end | |
69 | + | |
70 | private | |
71 | ||
72 | def clearable_fields | |
73 | diff --git a/config/defaults.yml b/config/defaults.yml | |
74 | index c046aff07..66e9afa13 100644 | |
75 | --- a/config/defaults.yml | |
76 | +++ b/config/defaults.yml | |
77 | @@ -202,6 +202,20 @@ defaults: | |
78 | scope: tags | |
79 | include_user_tags: false | |
80 | pod_tags: | |
81 | + ldap: | |
82 | + enable: false | |
83 | + host: localhost | |
84 | + port: 389 | |
85 | + only_ldap: true | |
86 | + mail_attribute: mail | |
87 | + skip_email_confirmation: true | |
88 | + use_bind_dn: true | |
89 | + bind_dn: "cn=diaspora,dc=example,dc=com" | |
90 | + bind_pw: "password" | |
91 | + search_base: "dc=example,dc=com" | |
92 | + search_filter: "uid=%{username}" | |
93 | + bind_template: "uid=%{username},dc=example,dc=com" | |
94 | + | |
95 | ||
96 | development: | |
97 | environment: | |
98 | diff --git a/config/diaspora.yml.example b/config/diaspora.yml.example | |
99 | index b2573625d..c357c8651 100644 | |
100 | --- a/config/diaspora.yml.example | |
101 | +++ b/config/diaspora.yml.example | |
102 | @@ -710,6 +710,36 @@ configuration: ## Section | |
103 | ## If scope is 'tags', a comma separated list of tags here can be set. | |
104 | ## For example "linux,diaspora", to receive posts related to these tags | |
105 | #pod_tags: | |
106 | + ldap: | |
107 | + # Uncomment next line if you want to use LDAP on your instance | |
108 | + enable: true | |
109 | + host: localhost | |
110 | + port: 389 | |
111 | + # Use only LDAP authentication (don't try other means) | |
112 | + only_ldap: true | |
113 | + # LDAP attribute to find the user's e-mail. Necessary to create accounts | |
114 | + # for not existing users | |
115 | + mail_attribute: mail | |
116 | + # Skip e-mail confirmation when creating an account via LDAP. | |
117 | + skip_email_confirmation: true | |
118 | + # ----- Using bind_dn and bind_pw | |
119 | + # bind_dn and bind_pw may be used if the diaspora instance | |
120 | + # should be able to connect to LDAP to find and search for users. | |
121 | + | |
122 | + use_bind_dn: true | |
123 | + bind_dn: "cn=diaspora,dc=example,dc=com" | |
124 | + bind_pw: "password" | |
125 | + search_base: "dc=example,dc=com" | |
126 | + # This is the filter with which to search for the user. %{username} will | |
127 | + # be replaced by the given login. | |
128 | + search_filter: "uid=%{username}" | |
129 | + # | |
130 | + # ----- Using template | |
131 | + # This setting doesn't require a diaspora LDAP user. Use a template, and | |
132 | + # diaspora will try to login with the templated dn and password | |
133 | + # | |
134 | + # bind_template: "uid=%{username},dc=example,dc=com" | |
135 | + | |
136 | ||
137 | ## Here you can override settings defined above if you need | |
138 | ## to have them different in different environments. | |
139 | diff --git a/config/initializers/0_ldap_authenticatable.rb b/config/initializers/0_ldap_authenticatable.rb | |
140 | new file mode 100644 | |
141 | index 000000000..49846502f | |
142 | --- /dev/null | |
143 | +++ b/config/initializers/0_ldap_authenticatable.rb | |
144 | @@ -0,0 +1,82 @@ | |
145 | +require 'net/ldap' | |
146 | +require 'devise/strategies/authenticatable' | |
147 | + | |
148 | +module Devise | |
149 | + module Strategies | |
150 | + class LdapAuthenticatable < Authenticatable | |
151 | + def valid? | |
152 | + AppConfig.ldap.enable? && params[:user].present? | |
153 | + end | |
154 | + | |
155 | + def authenticate! | |
156 | + ldap = Net::LDAP.new( | |
157 | + host: AppConfig.ldap.host, | |
158 | + port: AppConfig.ldap.port, | |
159 | + encryption: :simple_tls, | |
160 | + ) | |
161 | + | |
162 | + if AppConfig.ldap.use_bind_dn? | |
163 | + ldap.auth AppConfig.ldap.bind_dn, AppConfig.ldap.bind_pw | |
164 | + | |
165 | + if !ldap.bind | |
166 | + return fail(:ldap_configuration_error) | |
167 | + end | |
168 | + | |
169 | + search_filter = AppConfig.ldap.search_filter % { username: params[:user][:username] } | |
170 | + | |
171 | + result = ldap.search(base: AppConfig.ldap.search_base, filter: search_filter, result_set: true) | |
172 | + | |
173 | + if result.count != 1 | |
174 | + return login_fail | |
175 | + end | |
176 | + | |
177 | + user_dn = result.first.dn | |
178 | + user_email = result.first[AppConfig.ldap.mail_attribute].first | |
179 | + else | |
180 | + user_dn = AppConfig.ldap.bind_template % { username: params[:user][:username] } | |
181 | + end | |
182 | + | |
183 | + ldap.auth user_dn, params[:user][:password] | |
184 | + | |
185 | + if ldap.bind | |
186 | + user = User.find_by(ldap_dn: user_dn) | |
187 | + | |
188 | + # We don't want to trust too much the email attribute from | |
189 | + # LDAP: if the user can edit it himself, he may login as | |
190 | + # anyone | |
191 | + if user.nil? | |
192 | + if !AppConfig.ldap.use_bind_dn? | |
193 | + result = ldap.search(base: user_dn, scope: Net::LDAP::SearchScope_BaseObject, filter: "(objectClass=*)", result_set: true) | |
194 | + user_email = result.first[AppConfig.ldap.mail_attribute].first | |
195 | + end | |
196 | + | |
197 | + if user_email.present? && User.find_by(email: user_email).nil? | |
198 | + # Password is used for remember_me token | |
199 | + user = User.build(email: user_email, ldap_dn: user_dn, password: SecureRandom.hex, username: params[:user][:username]) | |
200 | + user.save | |
201 | + user.seed_aspects | |
202 | + elsif User.find_by(email: user_email).present? | |
203 | + return fail(:ldap_existing_email) | |
204 | + else | |
205 | + return fail(:ldap_cannot_create_account_without_email) | |
206 | + end | |
207 | + end | |
208 | + | |
209 | + success!(user) | |
210 | + else | |
211 | + return login_fail | |
212 | + end | |
213 | + end | |
214 | + | |
215 | + def login_fail | |
216 | + if AppConfig.ldap.only_ldap? | |
217 | + return fail(:ldap_invalid_login) | |
218 | + else | |
219 | + return pass | |
220 | + end | |
221 | + end | |
222 | + end | |
223 | + end | |
224 | +end | |
225 | + | |
226 | +Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable) | |
227 | diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb | |
228 | index 3698e2373..14e88063e 100644 | |
229 | --- a/config/initializers/devise.rb | |
230 | +++ b/config/initializers/devise.rb | |
231 | @@ -250,10 +250,9 @@ Devise.setup do |config| | |
232 | # If you want to use other strategies, that are not supported by Devise, or | |
233 | # change the failure app, you can configure them inside the config.warden block. | |
234 | # | |
235 | - # config.warden do |manager| | |
236 | - # manager.intercept_401 = false | |
237 | - # manager.default_strategies(:scope => :user).unshift :some_external_strategy | |
238 | - # end | |
239 | + config.warden do |manager| | |
240 | + manager.default_strategies(scope: :user).unshift :ldap_authenticatable | |
241 | + end | |
242 | ||
243 | # ==> Mountable engine configurations | |
244 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine | |
245 | diff --git a/db/migrate/20170724182100_add_ldap_dn_to_users.rb b/db/migrate/20170724182100_add_ldap_dn_to_users.rb | |
246 | new file mode 100644 | |
247 | index 000000000..f5cc84d11 | |
248 | --- /dev/null | |
249 | +++ b/db/migrate/20170724182100_add_ldap_dn_to_users.rb | |
250 | @@ -0,0 +1,6 @@ | |
251 | +class AddLdapDnToUsers < ActiveRecord::Migration | |
252 | + def change | |
253 | + add_column :users, :ldap_dn, :text, null: true, default: nil | |
254 | + add_index :users, ['ldap_dn'], :length => { "ldap_dn" => 191 } | |
255 | + end | |
256 | +end |