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 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
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /bin/directories.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in holman_active_directory.gemspec
4
+ gemspec
5
+ gem 'pry'
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
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'holman_active_directory'
5
+ require 'yaml'
6
+
7
+ HolmanActiveDirectory.server_configs = YAML.load_file 'bin/directories.yml'
8
+
9
+ require 'pry'
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,3 @@
1
+ module HolmanActiveDirectory
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe HolmanActiveDirectory do
4
+ it 'has a version number' do
5
+ expect(HolmanActiveDirectory::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'holman_active_directory'
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