holman_active_directory 2.0.0
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 +15 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/README.md +20 -0
- data/Rakefile +7 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/holman_active_directory.gemspec +26 -0
- data/lib/holman_active_directory.rb +121 -0
- data/lib/holman_active_directory/access_control.rb +23 -0
- data/lib/holman_active_directory/authenticatable.rb +50 -0
- data/lib/holman_active_directory/directory.rb +103 -0
- data/lib/holman_active_directory/filter.rb +27 -0
- data/lib/holman_active_directory/version.rb +3 -0
- data/spec/holman_active_directory_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0c0cbeffe1cb8df305951839cf5d945e42e95ed0
|
4
|
+
data.tar.gz: dbb87eceef4d58b9e6adbaaaf8ca73a15aa351f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aa3657e63e23e1b218a396e358832ada4f1c29e8aad46c3cf73baf3676fea5109ff39562171764f9085744cd0b40f756f3de4198b1e07fa48202d82481b691b3
|
7
|
+
data.tar.gz: da4546810688f0f074691ee213c2ccfe140fb560b806d022e227fdf9bf32d2db2b47c3d626e4b04365042a8376e28b320a2c3cd1e8f0631c9d228c9fd9bfdb37
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# HolmanActiveDirectory
|
2
|
+
|
3
|
+
Holman Active Directory Services
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'holman_active_directory'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install holman_active_directory
|
20
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'holman_active_directory/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "holman_active_directory"
|
8
|
+
spec.version = HolmanActiveDirectory::VERSION
|
9
|
+
spec.authors = ["Benjamin Cavileer"]
|
10
|
+
spec.email = ["bcavileer@holmanauto.com"]
|
11
|
+
spec.summary = "Holman Active Directory Services"
|
12
|
+
spec.description = "Holman Active Directory Services"
|
13
|
+
spec.homepage = ""
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'net-ldap', '~> 0.9'
|
21
|
+
spec.add_dependency 'pmap'
|
22
|
+
spec.add_dependency 'net-ping'
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "holman_active_directory/version"
|
2
|
+
|
3
|
+
require 'net/ldap'
|
4
|
+
require 'pmap'
|
5
|
+
|
6
|
+
require 'holman_active_directory/directory'
|
7
|
+
require 'holman_active_directory/filter'
|
8
|
+
|
9
|
+
module HolmanActiveDirectory
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def search_all_directories(options)
|
14
|
+
peach_directory(options) { |directory| directory.search(options) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_all_directories_for_employee_id(employee_id, options={})
|
18
|
+
peach_directory(options) { |directory| directory.search_for_employee_id(employee_id, options) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def search_all_directories_for_username(username, options={})
|
22
|
+
peach_directory(options) { |directory| directory.search_for_username(username, options) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_password?(dn, password)
|
26
|
+
return false unless password.present?
|
27
|
+
directory(dn).valid_password?(dn, password)
|
28
|
+
end
|
29
|
+
|
30
|
+
def active_account?(dn)
|
31
|
+
directory(dn).active_account?(dn)
|
32
|
+
end
|
33
|
+
|
34
|
+
def sso_connection(dn)
|
35
|
+
directory(dn).sso_connection
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(dn, operations)
|
39
|
+
directory(dn).update(dn, operations)
|
40
|
+
end
|
41
|
+
|
42
|
+
def upload_photo(dn, photo)
|
43
|
+
directory(dn).update(dn, [[:replace, :thumbnailPhoto, photo]])
|
44
|
+
end
|
45
|
+
|
46
|
+
def sso_connection_for_dn(dn)
|
47
|
+
directory(dn).sso_connection
|
48
|
+
end
|
49
|
+
|
50
|
+
def directory(dn)
|
51
|
+
d = directories.find { |directory| dn.end_with? directory.base }
|
52
|
+
raise NoDirectory, "no directory for dn: #{dn}" unless d
|
53
|
+
d
|
54
|
+
end
|
55
|
+
|
56
|
+
def peach_directory(options)
|
57
|
+
options.fetch(:directories) { directories }.flat_pmap { |directory| yield directory }.compact
|
58
|
+
end
|
59
|
+
|
60
|
+
def server_configs=(hash)
|
61
|
+
@server_configs = hash
|
62
|
+
initialize_directories
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize_directories
|
66
|
+
@directories = server_configs.map { |name, attrs| Directory.new(name, Filter.send(attrs[:filter])) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def server_configs
|
70
|
+
@server_configs || {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_fastest_hosts
|
74
|
+
require 'net/ping/tcp'
|
75
|
+
|
76
|
+
domain = Struct.new(:directory, :addresses) do
|
77
|
+
def fastest_address
|
78
|
+
addresses.sort_by(&:avg).first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
address = Struct.new(:ip, :avg)
|
83
|
+
|
84
|
+
domains = directories.map { |d| domain.new(d) }
|
85
|
+
|
86
|
+
domains.each do |d|
|
87
|
+
d.addresses = Resolv::DNS.new.getaddresses(d.directory.config[:host]).map { |ip| address.new(ip.to_s) }
|
88
|
+
end
|
89
|
+
|
90
|
+
domains.each do |d|
|
91
|
+
d.addresses.each do |a|
|
92
|
+
pings = 5.times.map { Net::Ping::TCP.new(a.ip, 'ldap', 0.05).ping || 60 }
|
93
|
+
a.avg = pings.sum / 5
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
domains.each do |d|
|
98
|
+
d.directory.config[:host] = d.fastest_address.ip
|
99
|
+
end
|
100
|
+
|
101
|
+
domains
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_reader :directories
|
105
|
+
|
106
|
+
def test_all_directories
|
107
|
+
directories.map do |directory|
|
108
|
+
[
|
109
|
+
directory.name,
|
110
|
+
directory.test_bind,
|
111
|
+
directory.test_search_for_employee_id,
|
112
|
+
directory.test_search_for_username,
|
113
|
+
directory.test_active_account?,
|
114
|
+
]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class NoDirectory < RuntimeError
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HolmanActiveDirectory
|
2
|
+
class AccessControl # A class so the following method can be handled in delayed job
|
3
|
+
def self.ldap_session_timeout
|
4
|
+
1.hour.from_now
|
5
|
+
end
|
6
|
+
|
7
|
+
def check(dn, session_id)
|
8
|
+
@dn, @session_id = dn, session_id
|
9
|
+
return false unless dn
|
10
|
+
destroy_session unless HolmanActiveDirectory.active_account?(dn)
|
11
|
+
end
|
12
|
+
|
13
|
+
handle_asynchronously :check if defined?(DelayedJob)
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :dn, :session_id
|
18
|
+
|
19
|
+
def destroy_session(session_store=ActiveRecord::SessionStore::Session)
|
20
|
+
session_store.where(session_id: session_id).delete_all
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'holman_active_directory/access_control'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Strategies
|
5
|
+
class HolmanAuthenticatable < Authenticatable
|
6
|
+
def authenticate!
|
7
|
+
use_http_authenticate if defined?(ActionController)
|
8
|
+
|
9
|
+
return fail(:invalid) unless user.present? && password.present?
|
10
|
+
|
11
|
+
if user.valid_password?(password) # invited users may create their own password and login without AD
|
12
|
+
success!(user)
|
13
|
+
elsif active_directory_password?(user, password)
|
14
|
+
session[:ldap_session] = true
|
15
|
+
session[:expires_at] = HolmanActiveDirectory::AccessControl.ldap_session_timeout
|
16
|
+
success!(user)
|
17
|
+
else
|
18
|
+
fail(:invalid)
|
19
|
+
end
|
20
|
+
rescue Net::LDAP::Error
|
21
|
+
fail(:no_ad)
|
22
|
+
end
|
23
|
+
|
24
|
+
def user
|
25
|
+
@cached_user ||= User.find_for_authentication(email)
|
26
|
+
end
|
27
|
+
|
28
|
+
def email
|
29
|
+
http_user || params[:user][:email]
|
30
|
+
end
|
31
|
+
|
32
|
+
def password
|
33
|
+
http_pass || super
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :http_user, :http_pass
|
37
|
+
|
38
|
+
def use_http_authenticate
|
39
|
+
return unless request.authorization
|
40
|
+
@http_user, @http_pass = ActionController::HttpAuthentication::Basic::user_name_and_password(request)
|
41
|
+
end
|
42
|
+
|
43
|
+
def active_directory_password?(user, password)
|
44
|
+
dn = user.active_directory_distinguished_name
|
45
|
+
return false unless dn
|
46
|
+
HolmanActiveDirectory.valid_password?(dn, password)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module HolmanActiveDirectory
|
2
|
+
class Directory
|
3
|
+
attr_reader :name, :config, :base
|
4
|
+
|
5
|
+
def initialize(name, employee_id_filter, configs=HolmanActiveDirectory.server_configs)
|
6
|
+
@name = name
|
7
|
+
@employee_id_filter = employee_id_filter
|
8
|
+
@config = configs[name]
|
9
|
+
@base = config[:base]
|
10
|
+
end
|
11
|
+
|
12
|
+
def search(options)
|
13
|
+
connection.search({ base: base }.merge(options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def search_for_employee_id(employee_id, options={})
|
17
|
+
search({ filter: employee_id_filter(employee_id) & Filter.user }.merge(options))
|
18
|
+
end
|
19
|
+
|
20
|
+
def search_for_username(username, options={})
|
21
|
+
search({ filter: Filter.eq('samaccountname', username) & Filter.user }.merge options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def active_account?(dn)
|
25
|
+
!!search(base: dn, filter: Filter.active_user, attributes: '')
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_password?(dn, password)
|
29
|
+
connection.bind method: :simple, username: dn, password: password
|
30
|
+
end
|
31
|
+
|
32
|
+
def update(dn, operations)
|
33
|
+
return false unless updatable?
|
34
|
+
connection.modify dn: dn, operations: operations
|
35
|
+
last_operation_result
|
36
|
+
end
|
37
|
+
|
38
|
+
def updatable?
|
39
|
+
!!config[:updatable]
|
40
|
+
end
|
41
|
+
|
42
|
+
def employee_id_filter(employee_id)
|
43
|
+
@employee_id_filter.(employee_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def connection(service=Net::LDAP)
|
47
|
+
@connection ||= service.new config
|
48
|
+
end
|
49
|
+
|
50
|
+
def sso_connection(service=Net::LDAP)
|
51
|
+
@sso_connection ||= service.new sso_config
|
52
|
+
end
|
53
|
+
|
54
|
+
SSO_AUTH = { auth: { method: :sasl, mechanism: 'GSS-SPNEGO' } }
|
55
|
+
|
56
|
+
def sso_config
|
57
|
+
config.merge SSO_AUTH
|
58
|
+
end
|
59
|
+
|
60
|
+
# Last Operation helpers
|
61
|
+
|
62
|
+
def last_operation_success?
|
63
|
+
last_operation_status == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_operation_message
|
67
|
+
last_operation_result.message
|
68
|
+
end
|
69
|
+
|
70
|
+
def last_operation_status
|
71
|
+
last_operation_result.code
|
72
|
+
end
|
73
|
+
|
74
|
+
def last_operation_result
|
75
|
+
connection.get_operation_result
|
76
|
+
end
|
77
|
+
|
78
|
+
# Some simple tests to check connections
|
79
|
+
|
80
|
+
def test_bind
|
81
|
+
connection.bind
|
82
|
+
last_operation_message
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_search_for_employee_id
|
86
|
+
search_for_employee_id(0) == []
|
87
|
+
last_operation_message
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_search_for_username
|
91
|
+
search_for_username('arglebargle') == []
|
92
|
+
last_operation_message
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_active_account?
|
96
|
+
results = search(filter: Filter.active_user, attributes: '', size: 1)
|
97
|
+
user = results && results.first
|
98
|
+
return last_operation_message if user.nil?
|
99
|
+
active_account?(user.dn)
|
100
|
+
last_operation_message
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HolmanActiveDirectory
|
2
|
+
class Filter < Net::LDAP::Filter
|
3
|
+
EMPLOYEE_ID_FIELD = 'employeeid'
|
4
|
+
|
5
|
+
USER = construct "(&(objectCategory=organizationalPerson)(objectClass=User))"
|
6
|
+
ACTIVE = construct "(!(userAccountControl:1.2.840.113556.1.4.803:=2))"
|
7
|
+
EMPLOYEE_ID = ->(val) { eq EMPLOYEE_ID_FIELD, val }
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def active_user
|
11
|
+
ACTIVE & USER
|
12
|
+
end
|
13
|
+
|
14
|
+
def user
|
15
|
+
USER
|
16
|
+
end
|
17
|
+
|
18
|
+
def active
|
19
|
+
ACTIVE
|
20
|
+
end
|
21
|
+
|
22
|
+
def employee_id_filter
|
23
|
+
EMPLOYEE_ID
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: holman_active_directory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Benjamin Cavileer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ldap
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pmap
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: net-ping
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.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: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Holman Active Directory Services
|
98
|
+
email:
|
99
|
+
- bcavileer@holmanauto.com
|
100
|
+
executables:
|
101
|
+
- console
|
102
|
+
- setup
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- ".travis.yml"
|
109
|
+
- Gemfile
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- holman_active_directory.gemspec
|
115
|
+
- lib/holman_active_directory.rb
|
116
|
+
- lib/holman_active_directory/access_control.rb
|
117
|
+
- lib/holman_active_directory/authenticatable.rb
|
118
|
+
- lib/holman_active_directory/directory.rb
|
119
|
+
- lib/holman_active_directory/filter.rb
|
120
|
+
- lib/holman_active_directory/version.rb
|
121
|
+
- spec/holman_active_directory_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
homepage: ''
|
124
|
+
licenses: []
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.4.8
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Holman Active Directory Services
|
146
|
+
test_files:
|
147
|
+
- spec/holman_active_directory_spec.rb
|
148
|
+
- spec/spec_helper.rb
|