active_directory_login 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/active_directory_login.gemspec +35 -0
- data/lib/active_directory_login.rb +54 -0
- data/lib/active_directory_login/client.rb +302 -0
- data/lib/active_directory_login/group_resource.rb +41 -0
- data/lib/active_directory_login/health_check.rb +37 -0
- data/lib/active_directory_login/version.rb +3 -0
- data/lib/devise/strategies/active_directory_authenticatable.rb +35 -0
- data/spec/active_directory_login_spec.rb +94 -0
- data/spec/client_spec.rb +52 -0
- data/spec/models/user.rb +23 -0
- data/spec/spec_helper.rb +27 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 63d211b6c19283ade2b7a91ecde8921897628c65
|
4
|
+
data.tar.gz: 86cbf612ecbe3cae984232fad88e0407b10b5ad2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ef4e694e3546391df61ac16852e7648189e0f2673f40d336d9d8aed19ac89051560eff7c896436bcdeff9c2a16271d7c4e04b04be20bed1798aef31c997a536
|
7
|
+
data.tar.gz: f0a7c74005dbb3b6de2127611ba9013ddad988a788f5a16ae8ca3b1e0ce947dfa68525df1426e95cc1c2171029f191d6aa91d8acc48e7701923ab1664c4e839b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Brad Murray
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# ActiveDirectoryLogin
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'active_directory_login'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install active_directory_login
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_directory_login/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_directory_login"
|
8
|
+
spec.version = ActiveDirectoryLogin::VERSION
|
9
|
+
spec.authors = ["Brad Murray"]
|
10
|
+
spec.email = ["wyaeld@gmail.com"]
|
11
|
+
spec.description = "Devise based AD User Logins"
|
12
|
+
spec.summary = "Devise based AD User Logins"
|
13
|
+
spec.homepage = %q{http://github.com/datacom/active_directory_login}
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_development_dependency "rails", "~> 4.0"
|
25
|
+
spec.add_development_dependency "devise", "3.0.0"
|
26
|
+
spec.add_development_dependency "warden"
|
27
|
+
|
28
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
29
|
+
spec.add_development_dependency 'rspec-rails', '~> 2.14'
|
30
|
+
spec.add_development_dependency 'flexmock'
|
31
|
+
spec.add_development_dependency 'pry'
|
32
|
+
|
33
|
+
spec.add_dependency "datacom_active_directory", '1.5.5.datacom'
|
34
|
+
spec.add_dependency "datacom-net-ldap", '0.5.0.datacom'
|
35
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
autoload 'Logger', 'logger'
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require "active_directory"
|
5
|
+
require "active_directory_login/client"
|
6
|
+
|
7
|
+
|
8
|
+
require 'warden'
|
9
|
+
require 'devise/strategies/authenticatable'
|
10
|
+
require 'devise/strategies/active_directory_authenticatable'
|
11
|
+
|
12
|
+
module ActiveDirectoryLogin
|
13
|
+
|
14
|
+
class Error < RuntimeError; end
|
15
|
+
class AuthenticationError < Error; end
|
16
|
+
class NoUserError < Error; end
|
17
|
+
class ConfigurationError < Error; end
|
18
|
+
class NoSearchKey < Error; end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
extend Forwardable
|
22
|
+
|
23
|
+
def_delegators :default_client, :auth_method, :username, :password, :host, :port, :base, :auth
|
24
|
+
def_delegators :default_client, :auth_method=, :username=, :password=, :host=, :port=, :base=, :auth=
|
25
|
+
|
26
|
+
def_delegators :default_client, :staff_dn, :superuser_dn, :user_dn
|
27
|
+
def_delegators :default_client, :staff_dn=, :superuser_dn=, :user_dn=
|
28
|
+
|
29
|
+
def_delegators :default_client, :validate!, :sync_groups, :benched
|
30
|
+
|
31
|
+
def_delegators :default_client, :with_user, :lock_user, :authenticate_user, :has_member_access?, :create_or_update_user
|
32
|
+
|
33
|
+
#logger modelled on https://github.com/pusher/pusher-gem/blob/master/lib/pusher.rb
|
34
|
+
attr_writer :logger
|
35
|
+
|
36
|
+
def logger
|
37
|
+
@logger ||= begin
|
38
|
+
log = Logger.new($stdout)
|
39
|
+
log.level = Logger::INFO
|
40
|
+
log
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def default_client
|
45
|
+
@default_client ||= ActiveDirectoryLogin::Client.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
end
|
51
|
+
require "active_directory_login/version"
|
52
|
+
require "active_directory_login/health_check"
|
53
|
+
|
54
|
+
Warden::Strategies.add(:active_directory_authenticatable, Devise::Strategies::ActiveDirectoryAuthenticatable)
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'active_directory'
|
2
|
+
# require 'secure_random'
|
3
|
+
|
4
|
+
module ActiveDirectoryLogin
|
5
|
+
class Client
|
6
|
+
|
7
|
+
attr_accessor :host, :auth_method, :username, :password, :port, :base
|
8
|
+
attr_accessor :staff_dn, :superuser_dn, :user_dn
|
9
|
+
attr_reader :auth
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
options = {
|
15
|
+
auth_method: 'anonymous',
|
16
|
+
port: '389',
|
17
|
+
}.merge(options)
|
18
|
+
@host, @port, @base, @auth_method, @password, @username, @staff_dn, @superuser_dn, @user_dn = options.values_at(
|
19
|
+
:host, :port, :base, :auth_method, :password, :username, :staff_dn, :superuser_dn, :user_dn
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
%W(
|
25
|
+
host
|
26
|
+
port
|
27
|
+
base
|
28
|
+
auth_method
|
29
|
+
username
|
30
|
+
password
|
31
|
+
staff_dn
|
32
|
+
superuser_dn
|
33
|
+
user_dn
|
34
|
+
).each do |key|
|
35
|
+
# puts "KEY #{key} VALUE #{self.send(key)}"
|
36
|
+
raise ConfigurationError if self.send(key).nil?
|
37
|
+
end
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def settings
|
42
|
+
{
|
43
|
+
host: host,
|
44
|
+
port: port,
|
45
|
+
base: base,
|
46
|
+
auth: {
|
47
|
+
method: auth_method.to_sym,
|
48
|
+
username: username,
|
49
|
+
password: password
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_connection
|
56
|
+
if !ActiveDirectory::Base.connected?
|
57
|
+
validate!
|
58
|
+
ActiveDirectory::Base.setup(settings)
|
59
|
+
# ActiveDirectory::Base.enable_cache
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_user(username_or_email, password, &block)
|
64
|
+
raise Error, "block required" if block.nil?
|
65
|
+
|
66
|
+
@ad_user = find_user(username_or_email)
|
67
|
+
@ad_user_password = password
|
68
|
+
@query = query(username_or_email)
|
69
|
+
@app_user = @query.first
|
70
|
+
|
71
|
+
value = block.call
|
72
|
+
|
73
|
+
reset_ivars
|
74
|
+
|
75
|
+
return value
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset_ivars
|
79
|
+
@ad_user = nil
|
80
|
+
@app_user = nil
|
81
|
+
@query = nil
|
82
|
+
@groups = nil
|
83
|
+
@ad_user_password = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def lock_user
|
87
|
+
raise NoUserError, "need to call inside a .with_user block" if @ad_user.nil?
|
88
|
+
@app_user.try(:lock_access!) #its ok if no user
|
89
|
+
end
|
90
|
+
|
91
|
+
def authenticate_user
|
92
|
+
raise NoUserError, "need to call inside a .with_user block" if @ad_user.nil?
|
93
|
+
@ad_user.try(:authenticate, @ad_user_password)
|
94
|
+
end
|
95
|
+
|
96
|
+
def query(username_or_email)
|
97
|
+
@query ||= (username_or_email =~ /@/) ?
|
98
|
+
User.where { (provider == User::LDAP) & (email =~ username_or_email) } :
|
99
|
+
User.where { (provider == User::LDAP) & (username =~ username_or_email) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_or_update_user
|
103
|
+
|
104
|
+
user = @query.first_or_initialize(
|
105
|
+
name: "#{@ad_user.givenname} #{@ad_user.sn}",
|
106
|
+
email: @ad_user.mail.downcase,
|
107
|
+
username: @ad_user[:sAMAccountName]
|
108
|
+
)
|
109
|
+
|
110
|
+
# Update the user's password and permissions (can't use update_attributes as it might not be saved yet)
|
111
|
+
user.password = @ad_user_password
|
112
|
+
user.password_confirmation = @ad_user_password
|
113
|
+
|
114
|
+
user.staff = staff_access?
|
115
|
+
user.superuser = superuser_access?
|
116
|
+
user.locked_at = nil # unlock_access! saves without validating so no good
|
117
|
+
user.save! # only saves if changed or new
|
118
|
+
|
119
|
+
return user
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
def find_user username_or_email
|
125
|
+
ensure_connection
|
126
|
+
raise NoSearchKey unless username_or_email && !username_or_email.empty?
|
127
|
+
|
128
|
+
ad_key = (username_or_email =~ /@/) ? :mail : :sAMAccountName
|
129
|
+
ad_user = ActiveDirectory::User.find(:first, ad_key => username_or_email)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def find_group group_dn
|
134
|
+
ensure_connection
|
135
|
+
raise NoSearchKey unless group_dn && !group_dn.empty?
|
136
|
+
|
137
|
+
ActiveDirectory::Group.find(:first, distinguishedname: group_dn)
|
138
|
+
end
|
139
|
+
|
140
|
+
# def groups
|
141
|
+
# @staff_group ||= ActiveDirectory::Group.find(:first, distinguishedname: staff_dn)
|
142
|
+
# @superuser_group ||= ActiveDirectory::Group.find(:first, distinguishedname: superuser_dn)
|
143
|
+
# @user_group ||= ActiveDirectory::Group.find(:first, distinguishedname: user_dn)
|
144
|
+
# [@staff_group, @superuser_group, @user_group]
|
145
|
+
# end
|
146
|
+
|
147
|
+
|
148
|
+
def has_member_access?
|
149
|
+
raise NoUserError, "need to call inside a .with_user block" if @ad_user.nil?
|
150
|
+
staff_access? || superuser_access? || user_access?
|
151
|
+
end
|
152
|
+
|
153
|
+
def sync_all
|
154
|
+
[staff_dn, superuser_dn, user_dn].each do |dn|
|
155
|
+
sync_group(dn)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def benched
|
160
|
+
bench = Benchmark.realtime{ Devise.stretches = 2; sync_groups }
|
161
|
+
ActiveDirectoryLogin.logger.info "Benchmark"
|
162
|
+
ActiveDirectoryLogin.logger.info bench
|
163
|
+
end
|
164
|
+
|
165
|
+
def sync_groups
|
166
|
+
ensure_connection
|
167
|
+
|
168
|
+
ActiveRecord::Base.transaction do
|
169
|
+
|
170
|
+
# update staff
|
171
|
+
staff_group = find_group(staff_dn)
|
172
|
+
staff_member_emails = staff_group.member.collect(&:mail).map(&:downcase)
|
173
|
+
|
174
|
+
allowed = User.where(email: staff_member_emails, staff: false)
|
175
|
+
staff_allowed_emails = allowed.collect(&:email)
|
176
|
+
allowed.update_all(staff: true)
|
177
|
+
pending = staff_member_emails - staff_allowed_emails
|
178
|
+
|
179
|
+
disallowed = User.where.not(email: staff_member_emails).where(staff: true)
|
180
|
+
staff_disallowed_emails = disallowed.collect(&:email)
|
181
|
+
disallowed.update_all(staff: false)
|
182
|
+
pending = pending - staff_disallowed_emails
|
183
|
+
|
184
|
+
staff_unchanged = User.where(email: staff_member_emails, staff: true)
|
185
|
+
staff_unchanged_emails = staff_unchanged.collect(&:email)
|
186
|
+
pending = pending - staff_unchanged_emails
|
187
|
+
|
188
|
+
# create any new staff
|
189
|
+
pending.each do |pending_email|
|
190
|
+
with_user(pending_email, SecureRandom.hex){ create_or_update_user }
|
191
|
+
end
|
192
|
+
staff_created_emails = pending
|
193
|
+
|
194
|
+
|
195
|
+
## update superuser
|
196
|
+
superuser_group = find_group(superuser_dn)
|
197
|
+
superuser_member_emails = superuser_group.member.collect(&:mail).map(&:downcase)
|
198
|
+
superuser_member_emails = superuser_member_emails - staff_member_emails
|
199
|
+
|
200
|
+
allowed = User.where(email: superuser_member_emails, superuser: false)
|
201
|
+
superuser_allowed_emails = allowed.collect(&:email)
|
202
|
+
allowed.update_all(superuser: true)
|
203
|
+
pending = superuser_member_emails - superuser_allowed_emails
|
204
|
+
|
205
|
+
disallowed = User.where.not(email: superuser_member_emails).where(superuser: true)
|
206
|
+
superuser_disallowed_emails = disallowed.collect(&:email)
|
207
|
+
disallowed.update_all(superuser: false)
|
208
|
+
pending = pending - superuser_disallowed_emails
|
209
|
+
|
210
|
+
superuser_unchanged = User.where(email: superuser_member_emails, superuser: true)
|
211
|
+
superuser_unchanged_emails = superuser_unchanged.collect(&:email)
|
212
|
+
pending = pending - superuser_unchanged_emails
|
213
|
+
|
214
|
+
# create any new superuser
|
215
|
+
pending.each do |pending_email|
|
216
|
+
with_user(pending_email, SecureRandom.hex){ create_or_update_user }
|
217
|
+
end
|
218
|
+
superuser_created_emails = pending
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
|
223
|
+
## update users
|
224
|
+
user_group = find_group(user_dn)
|
225
|
+
# member_cns = user_group.entry[:member]
|
226
|
+
|
227
|
+
member_emails = user_group.member.collect(&:mail).map(&:downcase)
|
228
|
+
member_emails = member_emails - superuser_member_emails - staff_member_emails
|
229
|
+
|
230
|
+
allowed = User.where(email: member_emails, staff: false, superuser: false).where.not(locked_at: nil)
|
231
|
+
user_allowed_emails = allowed.collect(&:email)
|
232
|
+
allowed.update_all(locked_at: nil)
|
233
|
+
pending = member_emails - user_allowed_emails
|
234
|
+
|
235
|
+
disallowed = User.where.not(email: member_emails, staff: false, superuser: false)
|
236
|
+
user_disallowed_emails = disallowed.collect(&:email)
|
237
|
+
disallowed.each {|u| u.lock_access! }
|
238
|
+
|
239
|
+
user_unchanged = User.where(email: member_emails, staff: false, superuser: false)
|
240
|
+
user_unchanged_emails = user_unchanged.collect(&:email)
|
241
|
+
pending = pending - user_unchanged_emails
|
242
|
+
|
243
|
+
# binding.pry
|
244
|
+
|
245
|
+
#create any new superuser
|
246
|
+
pending.each do |pending_email|
|
247
|
+
with_user(pending_email, SecureRandom.hex){ create_or_update_user }
|
248
|
+
end
|
249
|
+
user_created_emails = pending
|
250
|
+
|
251
|
+
#report on changes
|
252
|
+
ActiveDirectoryLogin.logger.info "Staff allowed: #{staff_allowed_emails}"
|
253
|
+
ActiveDirectoryLogin.logger.info "Staff disallowed: #{staff_disallowed_emails}"
|
254
|
+
ActiveDirectoryLogin.logger.info "Staff created: #{staff_created_emails}"
|
255
|
+
ActiveDirectoryLogin.logger.info "Staff unchanged: #{staff_unchanged_emails}"
|
256
|
+
|
257
|
+
ActiveDirectoryLogin.logger.info "Superusers allowed: #{superuser_allowed_emails}"
|
258
|
+
ActiveDirectoryLogin.logger.info "Superusers disallowed: #{superuser_disallowed_emails}"
|
259
|
+
ActiveDirectoryLogin.logger.info "Superusers created: #{superuser_created_emails}"
|
260
|
+
ActiveDirectoryLogin.logger.info "Superusers unchanged: #{superuser_unchanged_emails}"
|
261
|
+
|
262
|
+
ActiveDirectoryLogin.logger.info "User allowed: #{user_allowed_emails}"
|
263
|
+
ActiveDirectoryLogin.logger.info "User disallowed: #{user_disallowed_emails}"
|
264
|
+
ActiveDirectoryLogin.logger.info "User created: #{user_created_emails}"
|
265
|
+
ActiveDirectoryLogin.logger.info "User unchanged: #{user_unchanged_emails}"
|
266
|
+
|
267
|
+
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
ActiveDirectoryLogin.logger.info "Staff: #{User.staff.count}"
|
274
|
+
ActiveDirectoryLogin.logger.info "Superusers: #{User.superusers.count}"
|
275
|
+
ActiveDirectoryLogin.logger.info "Users: #{User.normals.count}"
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
|
282
|
+
def group_names
|
283
|
+
@groups ||= @ad_user.entry[:memberof]
|
284
|
+
end
|
285
|
+
|
286
|
+
def staff_access?
|
287
|
+
group_names.include? staff_dn
|
288
|
+
end
|
289
|
+
|
290
|
+
def superuser_access?
|
291
|
+
group_names.include? superuser_dn
|
292
|
+
end
|
293
|
+
|
294
|
+
def user_access?
|
295
|
+
group_names.include? user_dn
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
|
300
|
+
|
301
|
+
end #class
|
302
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveDirectoryLogin
|
2
|
+
class GroupResource
|
3
|
+
|
4
|
+
def initialize(client)
|
5
|
+
@client = client
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find group_name
|
10
|
+
ActiveDirectory::Group.find(:first, distinguishedname: group_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.member_names group
|
14
|
+
group.member.map &:name
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.member_emails group
|
18
|
+
group.member.map &:mail
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.has_group_access? groups
|
22
|
+
staff = staff_group? groups
|
23
|
+
system = system_group? groups
|
24
|
+
general = general_group? groups
|
25
|
+
staff || system || general
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.staff_group? groups
|
29
|
+
groups.include? ENV['DG_STAFF_DN']
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.system_group? groups
|
33
|
+
groups.include? ENV['DG_SYSTEM_DN']
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.general_group? groups
|
37
|
+
groups.include? ENV['DG_USER_DN']
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ActiveDirectoryLogin
|
2
|
+
module HealthCheck
|
3
|
+
def self.valid_config?
|
4
|
+
ActiveDirectoryLogin.default_client.validate!
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.health_check_errors
|
8
|
+
# We use these twice
|
9
|
+
output = []
|
10
|
+
|
11
|
+
begin
|
12
|
+
ActiveDirectoryLogin.default_client.ensure_connection
|
13
|
+
rescue => e
|
14
|
+
output << "LDAP: Connecting: #{e.message}"
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
ActiveDirectoryLogin.default_client.ensure_connection
|
19
|
+
rescue => e
|
20
|
+
output << "LDAP: Authorizing: #{e.message}" unless output.include?("LDAP: Connecting: #{e.message}")
|
21
|
+
end
|
22
|
+
output << "LDAP: Authorizing: #{ActiveDirectory::Base.error}" if ActiveDirectory::Base.error?
|
23
|
+
|
24
|
+
begin
|
25
|
+
ActiveDirectory::Group.find(:first, distinguishedname: ActiveDirectoryLogin.user_dn)
|
26
|
+
rescue
|
27
|
+
output << 'LDAP: Searching failed; check base and authorisation config'
|
28
|
+
end
|
29
|
+
|
30
|
+
output.to_sentence
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.healthy?
|
34
|
+
health_check_errors.empty?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'devise/strategies/authenticatable'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
module Devise
|
5
|
+
module Strategies
|
6
|
+
class ActiveDirectoryAuthenticatable < Authenticatable
|
7
|
+
|
8
|
+
def authenticate!
|
9
|
+
#FIXME enterprise mode was here
|
10
|
+
if params[:user]
|
11
|
+
|
12
|
+
username_or_email = params[:user][:login]
|
13
|
+
|
14
|
+
ActiveDirectoryLogin.with_user(username_or_email, params[:user][:password]) do
|
15
|
+
|
16
|
+
if ActiveDirectoryLogin.authenticate_user
|
17
|
+
if ActiveDirectoryLogin.has_member_access?
|
18
|
+
user = ActiveDirectoryLogin.create_or_update_user
|
19
|
+
success!(user)
|
20
|
+
else
|
21
|
+
ActiveDirectoryLogin.lock_user
|
22
|
+
fail(:locked)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
fail(:invalid)
|
26
|
+
end
|
27
|
+
|
28
|
+
end #with_user
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_directory_login'
|
3
|
+
require 'active_directory_login/group_resource'
|
4
|
+
require 'active_directory_login/user_resource'
|
5
|
+
require 'devise/strategies/authenticatable'
|
6
|
+
require 'devise'
|
7
|
+
describe ActiveDirectoryLogin do
|
8
|
+
|
9
|
+
before do
|
10
|
+
@client = ActiveDirectoryLogin::Client.new({
|
11
|
+
auth_method: 'simple',
|
12
|
+
username: 'svcdgauth',
|
13
|
+
password: 'Friday10',
|
14
|
+
host: 'dnzdc3.datacom.co.nz',
|
15
|
+
port: '389',
|
16
|
+
base: 'DC=datacom,DC=co,DC=nz'
|
17
|
+
})
|
18
|
+
@client.connect!
|
19
|
+
@user_resource = ActiveDirectoryLogin::UserResource
|
20
|
+
@group_resource = ActiveDirectoryLogin::GroupResource
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:staff_group) {"CN=DocGenie-Staff,OU=Datacom Systems Wellington,OU=Datacom Systems,OU=Groups - Universal Distribution Lists,DC=datacom,DC=co,DC=nz"}
|
25
|
+
|
26
|
+
describe '.user' do
|
27
|
+
|
28
|
+
it 'requires a value' do
|
29
|
+
expect { @user_resource.find nil
|
30
|
+
}.to raise_error ActiveDirectoryLogin::NoSearchKey
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'requires a value' do
|
34
|
+
expect { @user_resource.find ''
|
35
|
+
}.to raise_error ActiveDirectoryLogin::NoSearchKey
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'finds me by username' do
|
41
|
+
login = 'bradmu'
|
42
|
+
user = @user_resource.find login
|
43
|
+
user[:cn].should == "Brad Murray [DATACOM]"
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'finds me by email' do
|
47
|
+
login = 'brad.murray@datacom.co.nz'
|
48
|
+
user = @user_resource.find login
|
49
|
+
user[:cn].should == "Brad Murray [DATACOM]"
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it 'finds me by email' do
|
54
|
+
login = 'brad.murray@datacom.co.nz'
|
55
|
+
user = @user_resource.find login
|
56
|
+
user[:cn].should == "Brad Murray [DATACOM]"
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'finds DocGenie staff group' do
|
60
|
+
group = @group_resource.find staff_group
|
61
|
+
group.name.should == "DocGenie-Staff"
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can get members of DocGenie staff group' do
|
65
|
+
group = @group_resource.find staff_group
|
66
|
+
group.member.count.should == 5
|
67
|
+
@group_resource.member_names(group).should == ["Patrick Copeland [DATACOM]",
|
68
|
+
"Owen Bannister [DATACOM]",
|
69
|
+
"Tatyana Kudiyarova [DATACOM]",
|
70
|
+
"Brad Murray [DATACOM]",
|
71
|
+
"Blair Nilsson [DATACOM]"]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can find user groups' do
|
75
|
+
login = 'owen.bannister@datacom.co.nz'
|
76
|
+
user = @user_resource.find login
|
77
|
+
groups = @user_resource.groups user
|
78
|
+
groups.count.should > 0
|
79
|
+
groups.include?(staff_group).should == true
|
80
|
+
end
|
81
|
+
|
82
|
+
# let(:User) { stub 'User'}
|
83
|
+
it 'can validate me by my groups' do
|
84
|
+
login = 'owen.bannister@datacom.co.nz'
|
85
|
+
user = @user_resource.find login
|
86
|
+
|
87
|
+
permission = @group_resource.has_group_access? @user_resource.groups(user)
|
88
|
+
permission.should == false
|
89
|
+
|
90
|
+
ENV['DG_STAFF_DN'] = staff_group
|
91
|
+
permission = @group_resource.has_group_access? @user_resource.groups(user)
|
92
|
+
permission.should == true
|
93
|
+
end
|
94
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_directory_login/client'
|
3
|
+
|
4
|
+
describe ActiveDirectoryLogin::Client do
|
5
|
+
|
6
|
+
def valid_options
|
7
|
+
options = {}
|
8
|
+
options[:auth_method] = 'simple'
|
9
|
+
options[:username] = 'foo'
|
10
|
+
options[:password] = 'bar'
|
11
|
+
options[:host] = 'ldap.example.com'
|
12
|
+
options[:port] = '633'
|
13
|
+
options[:base] = 'DC=example,DC=com'
|
14
|
+
options
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe 'initialize' do
|
19
|
+
|
20
|
+
subject { ActiveDirectoryLogin::Client.new options}
|
21
|
+
|
22
|
+
describe 'with defaults' do
|
23
|
+
let(:options) { Hash.new }
|
24
|
+
|
25
|
+
its(:host) { should == nil}
|
26
|
+
its(:port) { should == '389'}
|
27
|
+
its(:base) { should == nil}
|
28
|
+
its(:username) { should == nil }
|
29
|
+
its(:password) { should == nil}
|
30
|
+
its(:auth_method) { should == 'anonymous'}
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'with values' do
|
34
|
+
let(:options) { valid_options }
|
35
|
+
|
36
|
+
its(:host) { should == 'ldap.example.com'}
|
37
|
+
its(:port) { should == '633'}
|
38
|
+
its(:base) { should == 'DC=example,DC=com'}
|
39
|
+
its(:username) { should == 'foo'}
|
40
|
+
its(:password) { should == 'bar'}
|
41
|
+
its(:auth_method) { should == 'simple'}
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
end
|
data/spec/models/user.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# require 'active_model/naming'
|
2
|
+
# require 'active_model/model'
|
3
|
+
require 'active_model'
|
4
|
+
class User
|
5
|
+
include ActiveModel::Model
|
6
|
+
|
7
|
+
# after_initialize :defaults
|
8
|
+
|
9
|
+
# def defaults
|
10
|
+
# binding.pry
|
11
|
+
# end
|
12
|
+
|
13
|
+
# def init(args)
|
14
|
+
# args.each_pair do |k,v|
|
15
|
+
# self[k] = v
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
|
19
|
+
attr_accessor :name, :email, :staff
|
20
|
+
|
21
|
+
alias_method :staff?, :staff
|
22
|
+
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'although not required, it is recommended that you use bundler when running the tests'
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'rails'
|
10
|
+
require 'rspec'
|
11
|
+
# require 'rspec/rails'
|
12
|
+
require 'rspec/autorun'
|
13
|
+
# require 'flexmock/rspec/configure'
|
14
|
+
|
15
|
+
require 'pry'
|
16
|
+
|
17
|
+
|
18
|
+
unless Object.const_defined? 'User'
|
19
|
+
# binding.pry
|
20
|
+
require 'models/user'
|
21
|
+
# mock_model 'User'
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
|
26
|
+
# co
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_directory_login
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brad Murray
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: devise
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: warden
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.14'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.14'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.14'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: flexmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: datacom_active_directory
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.5.5.datacom
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.5.5.datacom
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: datacom-net-ldap
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.5.0.datacom
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.5.0.datacom
|
167
|
+
description: Devise based AD User Logins
|
168
|
+
email:
|
169
|
+
- wyaeld@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- .gitignore
|
175
|
+
- Gemfile
|
176
|
+
- LICENSE.txt
|
177
|
+
- README.md
|
178
|
+
- Rakefile
|
179
|
+
- active_directory_login.gemspec
|
180
|
+
- lib/active_directory_login.rb
|
181
|
+
- lib/active_directory_login/client.rb
|
182
|
+
- lib/active_directory_login/group_resource.rb
|
183
|
+
- lib/active_directory_login/health_check.rb
|
184
|
+
- lib/active_directory_login/version.rb
|
185
|
+
- lib/devise/strategies/active_directory_authenticatable.rb
|
186
|
+
- spec/active_directory_login_spec.rb
|
187
|
+
- spec/client_spec.rb
|
188
|
+
- spec/models/user.rb
|
189
|
+
- spec/spec_helper.rb
|
190
|
+
homepage: http://github.com/datacom/active_directory_login
|
191
|
+
licenses:
|
192
|
+
- MIT
|
193
|
+
metadata: {}
|
194
|
+
post_install_message:
|
195
|
+
rdoc_options: []
|
196
|
+
require_paths:
|
197
|
+
- lib
|
198
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - '>='
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
208
|
+
requirements: []
|
209
|
+
rubyforge_project:
|
210
|
+
rubygems_version: 2.2.2
|
211
|
+
signing_key:
|
212
|
+
specification_version: 4
|
213
|
+
summary: Devise based AD User Logins
|
214
|
+
test_files:
|
215
|
+
- spec/active_directory_login_spec.rb
|
216
|
+
- spec/client_spec.rb
|
217
|
+
- spec/models/user.rb
|
218
|
+
- spec/spec_helper.rb
|