active_directory 1.1.1 → 1.2.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.1
1
+ 1.2.0
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{active_directory}
8
- s.version = "1.1.1"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Adam T Kerr"]
@@ -31,8 +31,6 @@ Gem::Specification.new do |s|
31
31
  "lib/active_directory/field_type/timestamp.rb",
32
32
  "lib/active_directory/group.rb",
33
33
  "lib/active_directory/member.rb",
34
- "lib/active_directory/rails/synchronizer.rb",
35
- "lib/active_directory/rails/user.rb",
36
34
  "lib/active_directory/user.rb"
37
35
  ]
38
36
  s.homepage = %q{http://github.com/ajrkerr/active_directory}
@@ -45,24 +45,27 @@ module ActiveDirectory
45
45
 
46
46
  #All objects in the AD
47
47
  :Base => {
48
- :objectGUID => :binary,
49
- :whenCreated => :date,
50
- :whenChanged => :date
48
+ :objectguid => :binary,
49
+ :whencreated => :date,
50
+ :whenchanged => :date
51
51
  },
52
52
 
53
53
  #User objects
54
54
  :User => {
55
- :objectSID => :binary,
56
- :msExchMailboxGuid => :binary,
57
- :msExchMailboxSecurityDescriptor => :binary,
58
- :lastLogonTimestamp => :timestamp,
59
- :pwdLastSet => :timestamp,
60
- :accountExpires => :timestamp
55
+ :objectguid => :binary,
56
+ :whencreated => :date,
57
+ :whenchanged => :date,
58
+ :objectsid => :binary,
59
+ :msexchmailboxguid => :binary,
60
+ :msexchmailboxsecuritydescriptor => :binary,
61
+ :lastlogontimestamp => :timestamp,
62
+ :pwdlastset => :timestamp,
63
+ :accountexpires => :timestamp
61
64
  },
62
65
 
63
66
  #Group objects
64
67
  :Group => {
65
- :objectSID => :binary,
68
+ :objectsid => :binary,
66
69
  },
67
70
  }
68
71
  end
@@ -399,24 +399,26 @@ module ActiveDirectory
399
399
  end
400
400
 
401
401
  def get_field_type(name)
402
- ::Rails.logger.add 0, "Looking for spec_fields[#{self.class.name.to_sym}][#{name.to_sym}]"
403
- ::ActiveDirectory.special_fields[self.class.name.to_sym][name.to_sym].classify
402
+ #Extract class name
403
+ klass = self.class.name[/.*::(.*)/, 1]
404
+ ::Rails.logger.add 0, "special_fields[#{klass.classify.to_sym}][#{name.downcase.to_sym}] = #{::ActiveDirectory.special_fields[klass.classify.to_sym][name.downcase.to_sym]}"
405
+ type = ::ActiveDirectory.special_fields[klass.classify.to_sym][name.downcase.to_sym]
406
+ type.to_s.classify unless type.nil?
404
407
  end
405
408
 
406
409
  def decode_field(name, value)
407
- return value
408
- ::Rails.logger.add 0, "Encoding #{name}, #{value}"
410
+ ::Rails.logger.add 0, "Decoding #{name}, #{value}"
409
411
  type = get_field_type name
410
- return ::ActiveDirectory::const_get(type).decode(value) if ::ActiveDirectory::const_defined? type
412
+ ::Rails.logger.add 0, "Type: #{type} Const: #{::ActiveDirectory::FieldType::const_get type unless type.nil?}"
413
+ return ::ActiveDirectory::FieldType::const_get(type).decode(value) if !type.nil? and ::ActiveDirectory::FieldType::const_defined? type
411
414
  return value
412
415
  end
413
416
 
414
417
  def encode_field(name, value)
415
- return value
416
- ::Rails.logger.add 0, "Encoding #{first}, #{name}, #{value}"
418
+ ::Rails.logger.add 0, "Encoding #{name}, #{value}"
417
419
  type = get_field_type name
418
- return "Encode #{name} as #{type}"
419
- return ::ActiveDirectory::const_get(type).encode(value) if ::ActiveDirectory::const_defined? type
420
+ ::Rails.logger.add 0, "Type: #{type} Const: #{::ActiveDirectory::FieldType::const_get type}"
421
+ return ::ActiveDirectory::FieldType::const_get(type).encode(value) if ::ActiveDirectory::FieldType::const_defined? type
420
422
  return value
421
423
  end
422
424
 
@@ -435,6 +437,8 @@ module ActiveDirectory
435
437
  value = @entry.send(name.to_sym)
436
438
  value = value.first if value.kind_of?(Array) && value.size == 1
437
439
  value = value.to_s if value.nil? || value.size == 1
440
+ ::Rails.logger.add 0, "Decoded as #{decode_field(name, value)}\n"
441
+ ::Rails.logger.add 0, ""
438
442
  return decode_field(name, value)
439
443
  # rescue NoMethodError => e
440
444
  # ::Rails.logger.add 0, "#{e.inspect}"
@@ -23,7 +23,7 @@
23
23
 
24
24
  module ActiveDirectory
25
25
  module FieldType
26
- class Guid
26
+ class Binary
27
27
  #
28
28
  # Encodes a hex string into a GUID
29
29
  #
@@ -35,7 +35,7 @@ module ActiveDirectory
35
35
  # Decodes a binary GUID as a hex string
36
36
  #
37
37
  def self.decode(guid)
38
- guid.unpack("H*")
38
+ guid.unpack("H*").to_s
39
39
  end
40
40
  end
41
41
  end
@@ -22,21 +22,23 @@
22
22
  #++ license
23
23
 
24
24
  module ActiveDirectory
25
- class Password
26
- #
27
- # Encodes an unencrypted password into an encrypted password
28
- # that the Active Directory server will understand.
29
- #
30
- def self.encode(password)
31
- ("\"#{password}\"".split(//).collect { |c| "#{c}\000" }).join
32
- end
25
+ module FieldType
26
+ class Password
27
+ #
28
+ # Encodes an unencrypted password into an encrypted password
29
+ # that the Active Directory server will understand.
30
+ #
31
+ def self.encode(password)
32
+ ("\"#{password}\"".split(//).collect { |c| "#{c}\000" }).join
33
+ end
33
34
 
34
- #
35
- # Always returns nil, since you can't decrypt the User's encrypted
36
- # password.
37
- #
38
- def self.decode(hashed)
39
- nil
35
+ #
36
+ # Always returns nil, since you can't decrypt the User's encrypted
37
+ # password.
38
+ #
39
+ def self.decode(hashed)
40
+ nil
41
+ end
40
42
  end
41
43
  end
42
44
  end
@@ -138,7 +138,7 @@ module ActiveDirectory
138
138
  :dn => distinguishedName,
139
139
  :operations => [
140
140
  [ :replace, :lockoutTime, [ '0' ] ],
141
- [ :replace, :unicodePwd, [ Password.encode(new_password) ] ],
141
+ [ :replace, :unicodePwd, [ FieldType::Password.encode(new_password) ] ],
142
142
  [ :replace, :userAccountControl, [ UAC_NORMAL_ACCOUNT.to_s ] ],
143
143
  [ :replace, :pwdLastSet, [ (force_change ? '0' : '-1') ] ]
144
144
  ]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_directory
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 1
9
- - 1
10
- version: 1.1.1
8
+ - 2
9
+ - 0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam T Kerr
@@ -58,8 +58,6 @@ files:
58
58
  - lib/active_directory/field_type/timestamp.rb
59
59
  - lib/active_directory/group.rb
60
60
  - lib/active_directory/member.rb
61
- - lib/active_directory/rails/synchronizer.rb
62
- - lib/active_directory/rails/user.rb
63
61
  - lib/active_directory/user.rb
64
62
  has_rdoc: true
65
63
  homepage: http://github.com/ajrkerr/active_directory
@@ -1,234 +0,0 @@
1
- #-- license
2
- #
3
- # This file is part of the Ruby Active Directory Project
4
- # on the web at http://rubyforge.org/projects/activedirectory
5
- #
6
- # Copyright (c) 2008, James Hunt <filefrog@gmail.com>
7
- # based on original code by Justin Mecham
8
- #
9
- # This program is free software: you can redistribute it and/or modify
10
- # it under the terms of the GNU General Public License as published by
11
- # the Free Software Foundation, either version 3 of the License, or
12
- # (at your option) any later version.
13
- #
14
- # This program is distributed in the hope that it will be useful,
15
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
- # GNU General Public License for more details.
18
- #
19
- # You should have received a copy of the GNU General Public License
20
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
- #
22
- #++ license
23
-
24
- # UserSynchronizer is a utility class that encapsulates dealings
25
- # with the Active Directory backend. It is primarily responsible for
26
- # updating Active Directory people in the local database from the
27
- # Active Directory store. In this fashion, name changes, email address
28
- # changes and such are all handled invisibly.
29
- #
30
- # UserSynchronizer is also responsible for disabling people who are no
31
- # longer in the Sales Tracker group, and creating people who are in the group,
32
- # but not in the local database. This gives us another administrative
33
- # convenience, since new hires will be added to the system with some
34
- # regularity, and terminations are eventually cleaned out.
35
- #
36
- # UserSynchronizer.sync_users_in_group will return a hash with the following keys:
37
- # * :added - An array of ActiveDirectory::User objects that were added.
38
- # * :disabled - An array of ActiveDirectory::User objects that were disabled.
39
- # * :updated - An array of ActiveDirectory::User objects that were updated.
40
- #
41
- # The following method illustrates how this would be used to notify a site
42
- # administrator to changes brought about by synchronization:
43
- #
44
- # def report(results)
45
- # puts "#####################################################"
46
- # puts "# Active Directory People Synchronization Summary #"
47
- # puts "#####################################################"
48
- # puts
49
- #
50
- # puts "New People Added (#{results[:added].size})"
51
- # puts "-----------------------------------------------------"
52
- # results[:added].sort_by(&:name).each { |p| out.puts " + #{p.name}" }
53
- # puts
54
- #
55
- # puts "People Disabled (#{results[:disabled].size})"
56
- # puts "-----------------------------------------------------"
57
- # results[:disabled].sort_by(&:name).each { |p| out.puts " - #{p.name}" }
58
- # puts
59
- #
60
- # puts "Existing People Updated (#{results[:updated].size})"
61
- # puts "-----------------------------------------------------"
62
- # results[:updated].sort_by(&:name).each { |p| out.puts " u #{p.name}" }
63
- # puts
64
- # end
65
- #
66
- class ActiveDirectory::Rails::UserSynchronizer
67
- @@default_group = nil
68
- cattr_accessor :default_group
69
-
70
- @@run_handler = nil
71
- cattr_accessor :run_handler
72
-
73
- @@attribute_map = {
74
- :first_name => :givenName,
75
- :last_name => :sn,
76
- :username => :sAMAccountName,
77
- :email => :mail,
78
- }
79
- cattr_accessor :attribute_map
80
-
81
- @@person_class = Person
82
- cattr_accessor :person_class
83
-
84
- class << self
85
- # The primary interface to synchronization, run processes
86
- # all of the Active Directory changes, additions and removals
87
- # through sync_users_in_group, and then notifies administrators
88
- # if it finds anyone new.
89
- #
90
- # This is the preferred way to run the UserSynchronizer.
91
- #
92
- def run
93
- results = sync_users_in_group
94
- @@run_handler.nil? results : @@run_handler.call(results)
95
- end
96
-
97
- # Compares the membership of the Active Directory group named
98
- # `group_name' and AD-enabled accounts in the local database.
99
- #
100
- # This method is the workhorse of UserSynchronizer, handling
101
- # the addition, removal and updates of AD people.
102
- #
103
- # It will return either false, or a hash with three keys, :added, :updated
104
- # and :disabled, each of which contains an array of the Person
105
- # objects that were (respectively) added, updated and disabled.
106
- #
107
- # If the given group_name does not resolve to a valid
108
- # ActiveDirectory::Group object, sync_users_in_group will return
109
- # false.
110
- #
111
- # The return value (for example) can be used by a notification process
112
- # to construct a message detailing who was added, removed, etc.
113
- #
114
- def sync_users_in_group(group_name = nil)
115
- group_name ||= @@default_group
116
- return false unless group_name
117
-
118
- ad_group = ActiveDirectory::Group.find_by_sAMAccountName(group_name)
119
- return false unless ad_group
120
-
121
- @people = person_class.in_active_directory.index_by(&:guid)
122
-
123
- summary = {
124
- :added => [],
125
- :disabled => [],
126
- :updated => []
127
- }
128
-
129
- # Find all member users (recursively looking at member groups)
130
- # and synchronize! them with their Person counterparts.
131
- #
132
- ad_group.member_users(true).each do |ad_user|
133
- person = @people[ad_user.objectGUID]
134
- if person
135
- synchronize!(person, ad_user)
136
- @people.delete(ad_user.objectGUID)
137
- summary[:updated] << person
138
- else
139
- person = create_from(ad_user)
140
- summary[:added] << person
141
- end
142
- end
143
-
144
- # Disable AD users we didn't find in AD.
145
- # Because we are not clearing the GUID in the disable! call,
146
- # we may process someone more than once.
147
- #
148
- @people.each do |guid, person|
149
- disable!(person)
150
- summary[:disabled] << person
151
- end
152
-
153
- summary
154
- end
155
-
156
- # Synchronize a peron with AD store by looking up their username.
157
- #
158
- # This is used for the initial bootstrap, because we don't know
159
- # a person's objectGUID offhand. It will probably never be seen
160
- # in any production code.
161
- #
162
- def update_using_username(person)
163
- ad_user = ActiveDirectory::User.find_by_sAMAccountName(person.username)
164
- synchronize!(person, ad_user)
165
- end
166
-
167
- # Sync a person with AD store by looking up their GUID
168
- # (This is the most reliable option, as a username can change,
169
- # but the GUID will stay the same).
170
- #
171
- # This method is not used in production, but can be useful in
172
- # a console'd environment to selectively update just a few people.
173
- #
174
- def update_using_guid(person)
175
- ad_user = ActiveDirectory::User.find_by_objectGUID(person.guid)
176
- synchronize!(person, ad_user)
177
- end
178
-
179
- # Synchronize the attributes of the given Person with those
180
- # found in the (hopefully associated) Active Directory user
181
- #
182
- # Because we are managing a mixed database of both AD and non-AD
183
- # people, we have to be careful. We cannot assume that a nil
184
- # ad_user argument means the person should be disabled.
185
- #
186
- def synchronize!(person, ad_user)
187
- person.update_attributes(attributes_from(ad_user)) if ad_user
188
- end
189
-
190
- # Disable a person, and clear out their authentication information.
191
- # This is primarily used when we find terminated employees who are
192
- # still in the local database as AD users, but no longer have an
193
- # AD account.
194
- #
195
- # There is a special case for people who have not logged sales.
196
- # They are removed outright, to keep terminated trainees from
197
- # cluttering up the Person table.
198
- #
199
- # Note that we do not clear their GUID. Active Directory is not
200
- # supposed to re-use its GUIDs, so we should be safe there.
201
- #
202
- def disable!(person)
203
- if person.respond_to? :removable? and !person.removable?
204
- person.update_attribute(:username, '')
205
- person.update_attribute(:email, '')
206
- else
207
- person.destroy
208
- end
209
- end
210
-
211
- # Creates a new Person object based on the attributes of
212
- # an Active Directory user. sync_users_in_group uses this when
213
- # it finds new people.
214
- #
215
- # All Person objects will be created as generic Persons,
216
- # not CSRs or TeamLeaders. Administrators are responsible
217
- # for promoting and associating new people in the backend.
218
- #
219
- def create_from(ad_user)
220
- person = person_class.create(attributes_from(ad_user))
221
- end
222
-
223
- # Translates the attributes of ad_user into a hash that can
224
- # be used to create or update a Person object.
225
- #
226
- def attributes_from(ad_user)
227
- h = {}
228
- @@attribute_map.each { |local, remote| h[local] = ad_user.send(remote) }
229
- h[:guid] = ad_user.objectGUID
230
- h[:password => '']
231
- h
232
- end
233
- end
234
- end
@@ -1,141 +0,0 @@
1
- #-- license
2
- #
3
- # This file is part of the Ruby Active Directory Project
4
- # on the web at http://rubyforge.org/projects/activedirectory
5
- #
6
- # Copyright (c) 2008, James Hunt <filefrog@gmail.com>
7
- # based on original code by Justin Mecham
8
- #
9
- # This program is free software: you can redistribute it and/or modify
10
- # it under the terms of the GNU General Public License as published by
11
- # the Free Software Foundation, either version 3 of the License, or
12
- # (at your option) any later version.
13
- #
14
- # This program is distributed in the hope that it will be useful,
15
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
- # GNU General Public License for more details.
18
- #
19
- # You should have received a copy of the GNU General Public License
20
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
- #
22
- #++ license
23
-
24
- module ActiveDirectory
25
- module Rails
26
- module User
27
- def self.included(klass)
28
- klass.extend(ClassMethods)
29
- klass.send(:include, InstanceMethods)
30
- end
31
-
32
- module InstanceMethods
33
- # Is this Person active? Active people have valid
34
- # usernames. Inactive people have empty usernames.
35
- #
36
- def active?
37
- username != ""
38
- end
39
-
40
- # Whether or not this Person has a corresponding Active Directory
41
- # account that we can synchronize with, through the PeopleSynchronizer.
42
- #
43
- def in_active_directory?
44
- !guid.blank?
45
- end
46
-
47
- # Whether or not this Person can be authenticated with the
48
- # given password, against Active Directory.
49
- #
50
- # For Active Directory authentication, we attempt to bind to the
51
- # configured AD server as the user, and supply the password for
52
- # authentication.
53
- #
54
- # There are two special cases for authentication, related to the
55
- # environment the app is currently running in:
56
- #
57
- # *Development*
58
- #
59
- # In development, the blank password ('') will always cause this method
60
- # to return true, thereby allowing developers to test functionality
61
- # for a variety of roles.
62
- #
63
- # *Training*
64
- #
65
- # In training, a special training password ('trainme') will always
66
- # cause this method to return true, thereby allowing trainers to
67
- # use other people accounts to illustrate certain restricted processes.
68
- #
69
- def authenticates?(password)
70
- # Never allow inactive users.
71
- return false unless active?
72
-
73
- # Allow blank password for any account in development.
74
- return true if password == "" and ENV['RAILS_ENV'] == 'development'
75
- return true if password == "trainme" and ENV['RAILS_ENV'] == 'training'
76
-
77
- # Don't go against AD unless we really mean it.
78
- return false unless ENV['RAILS_ENV'] == 'production'
79
-
80
- # If they are not in AD, fail.
81
- return false unless in_active_directory?
82
-
83
- ad_user = ActiveDirectory::User.find_by_sAMAccountName(self.username)
84
- ad_user and ad_user.authenticate(password)
85
- end
86
-
87
- def active_directory_equivalent=(ad_user)
88
- return unless ad_user
89
- update_attributes(
90
- :first_name => ad_user.givenName,
91
- :middle_name => ad_user.initials,
92
- :last_name => ad_user.sn,
93
- :username => ad_user.sAMAccountName,
94
- :email => ad_user.mail,
95
- :guid => ad_user.objectGUID
96
- )
97
- end
98
- end
99
-
100
- module ClassMethods
101
- # Attempt to authenticate someone with a username and password.
102
- # This method properly handles both local store users and AD
103
- # users.
104
- #
105
- # If the username is valid, and the password matches the username,
106
- # the Person object corresponding to the username is return.
107
- #
108
- # Otherwise, nil is returned, to indicate an authentication failure.
109
- #
110
- def authenticate(username, password)
111
- person = find_by_username(username)
112
- return person if (person and person.authenticates?(password))
113
- nil
114
- end
115
-
116
- # Retrieves all of the Person objects that have corresponding
117
- # Active Directory accounts. This method does not contact
118
- # the AD servers to retrieve the AD objects -- that is left up
119
- # to the caller.
120
- #
121
- def in_active_directory
122
- find(:all, :conditions => 'guid IS NOT NULL AND guid != ""')
123
- end
124
-
125
- # Retrieves all Person objects that are currently active,
126
- # meaning they have not been disabled by PeopleSynchronizer.
127
- #
128
- def active
129
- find(:all, :conditions => 'username != ""')
130
- end
131
-
132
- # Retrieves all Person objects that are currently inactive,
133
- # meaning they have been disabled by PeopleSynchronizer.
134
- #
135
- def inactive
136
- find(:all, :conditions => 'username = ""')
137
- end
138
- end
139
- end # module User
140
- end # module Rails
141
- end #module ActiveDirectory