active_directory 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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