activedirectory 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,296 +1,155 @@
1
- #--
2
- # Active Directory Module for Ruby
1
+ #-- license
3
2
  #
4
- # Copyright (c) 2005-2006 Justin Mecham
3
+ # This file is part of the Ruby Active Directory Project
4
+ # on the web at http://rubyforge.org/projects/activedirectory
5
5
  #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to
8
- # deal in the Software without restriction, including without limitation the
9
- # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
- # sell copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
6
+ # Copyright (c) 2008, James Hunt <filefrog@gmail.com>
7
+ # based on original code by Justin Mecham
12
8
  #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
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.
15
13
  #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
- # IN THE SOFTWARE.
23
- #++
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
24
23
 
25
24
  module ActiveDirectory
26
-
27
- #
28
- # Represents a User object within an Active Directory instance.
29
- #
30
- class User < Base
31
-
32
- # Distinguished Name (DN)
33
- attr_reader :dn
34
-
35
- # Display Name (e.g. "John Q. Public")
36
- attr_reader :name
37
-
38
- # Given Name (e.g. "John")
39
- attr_reader :given_name
40
-
41
- # Surname (e.g. "Public")
42
- attr_reader :surname
43
-
44
- # Primary E-Mail Address
45
- attr_reader :email
46
-
47
- # Account/Username (e.g. "jpublic")
48
- attr_reader :username
49
-
50
- # Job Title
51
- attr_reader :title
52
-
53
- # Company Name
54
- attr_reader :company
55
-
56
- # Department Name
57
- attr_reader :department
58
-
59
- # Primary Phone Number
60
- attr_reader :main_number
61
-
62
- # Mobile Number
63
- attr_reader :mobile_number
64
-
65
- # Street Address
66
- attr_reader :street_address
67
-
68
- # City / Town
69
- attr_reader :city
70
-
71
- # State/Province
72
- attr_reader :state
73
-
74
- # Zip/Postal Code
75
- attr_reader :zip
76
-
77
- # Country
78
- attr_reader :country
79
-
80
- # Direct Reports
81
- attr :direct_reports
82
-
83
- # Manager
84
- attr :manager
85
-
86
- # Groups this User is a member of
87
- attr :groups
88
-
89
- #
90
- # Attributes that we wish to pull from Active Directory for any User that
91
- # can be located within the directory.
92
- #
93
- ATTRIBUTES = ["displayName", # Name (e.g. "John Doe")
94
- "givenName", # Given (First) Name
95
- "sn", # Surname (Last)
96
- "distinguishedName", # DN of User
97
- "sAMAccountName", # Account Name
98
- "mail", # Primary E-Mail Address
99
- "manager", # DN Reference to Manager
100
- "directReports", # DN References to Minions
101
- "memberOf", # Group Membership
102
- "company", # Company Name
103
- "department", # Department Name
104
- "title", # Title
105
- "mobile", # Mobile Phone Number
106
- "telephoneNumber", # Primary Phone Number
107
- "streetAddress", # Street Address
108
- "l", # City
109
- "st", # State
110
- "postalCode", # Zip Code
111
- "co"] #:nodoc: # Country
112
-
113
- #
114
- # Attempts to load a User by a Distinguished Name (DN) or sAMAccountName.
115
- #
116
- def initialize(identifier)
117
-
118
- if (identifier =~ /(CN|cn)=/) != nil
119
- load_by_dn(identifier)
120
- else
121
- load_by_username(identifier)
122
- end
123
-
124
- end
125
-
126
- #
127
- # Attempts to authenticate the loaded user with the supplied password.
128
- # Returns true if the authentication attempt was successful.
129
- #
130
- def authenticate(password)
131
-
132
- # Clean up the password before we run it through our series of tests.
133
- password.strip!
134
-
135
- # If no password was specified, raise an exception. This check must
136
- # occur to avoid a huge security hole if anonymous bind is on - if this
137
- # check is not performed, someone can authenticate without providing a
138
- # password when anonymous bind is turned on.
139
- raise PasswordInvalid unless (!password.nil? and password.length > 0)
140
-
141
- # Clone our shared connection for isolated use in determining the
142
- # validity of our user's credentials.
143
- auth_connection = Base.connection.clone
144
-
145
- # Unbind the connection if it is already bound.
146
- auth_connection.unbind if auth_connection.bound?
147
-
148
- begin
149
-
150
- # Attempt to bind to the connection as the currently loaded user with
151
- # the supplied password.
152
- auth_connection.bind("#{@username}@#{@@server_settings[:domain]}",
153
- password)
154
-
155
- return true
156
-
157
- rescue LDAP::ResultError
158
- if ($!.to_s == "Invalid credentials")
159
- raise PasswordInvalid
160
- else
161
- raise
162
- end
163
- ensure
164
- auth_connection.unbind
165
- auth_connection = nil
166
- end
167
-
168
- return false
169
-
170
- end
171
-
172
- #
173
- # Conveniently return the name of the User if the object is called
174
- # directly.
175
- #
176
- def to_s #:nodoc:
177
- @name
178
- end
179
-
180
- #
181
- # Proxy for loading and returning this users' manager.
182
- #
183
- def manager #:nodoc:
184
- @manager ||= User.new(@manager_dn) unless @manager_dn.nil?
185
- end
186
-
187
- #
188
- # Proxy for loading and returning the group membership of this user.
189
- #
190
- def groups #:nodoc:
191
- groups = Array.new
192
- unless @groups.nil?
193
- for group_dn in @groups
194
- groups << Group.new(group_dn)
195
- end
196
- end
197
- groups
198
- end
199
-
200
- #
201
- # Proxy for loading and returning the users who report directly to this
202
- # user.
203
- #
204
- def direct_reports #:nodoc:
205
- direct_reports = Array.new
206
- unless @direct_reports.nil?
207
- for user_dn in @direct_reports
208
- direct_reports << User.new(user_dn)
209
- end
210
- end
211
- direct_reports
212
- end
213
-
214
- #
215
- # Determines if the user is a member of the given group. Returns true if
216
- # the user is in the passed group.
217
- #
218
- def member_of?(group)
219
- @groups.include?(group.dn)
220
- end
221
-
222
- private
223
-
224
- #
225
- # Attempt to load a user by their username (sAMAccountName).
226
- #
227
- def load_by_username(username)
228
-
229
- if username.nil? or username.length == 0
230
- raise ArgumentError, "No username provided."
231
- end
232
-
233
- @username = username
234
-
235
- # Set our filter to include only information about the requested user of
236
- # class user.
237
- filter = "(&(objectClass=user)(sAMAccountName=#{@username}))"
238
-
239
- entries = Base.search(@@server_settings[:base_dn],
240
- LDAP::LDAP_SCOPE_SUBTREE, filter, ATTRIBUTES)
241
-
242
- raise UnknownUserError if (entries.nil? or entries.length != 1)
243
-
244
- parse_ldap_entry(entries[0])
245
-
246
- end
247
-
248
- #
249
- # Attempt to load a User by its Distinguished Name (DN).
250
- #
251
- def load_by_dn(dn)
252
-
253
- if dn.nil? or dn.length == 0
254
- raise ArgumentError, "No distinguished name provided."
255
- end
256
-
257
- # De-escape the DN
258
- dn = dn.gsub(/"/, "\\\"")
259
-
260
- # Set our filter to include only information about the requested user of
261
- # class user.
262
- filter = "(&(objectClass=user))"
263
-
264
- entries = Base.search(dn, LDAP::LDAP_SCOPE_BASE, filter)
265
-
266
- raise UnknownUserError if (entries.nil? or entries.length != 1)
267
-
268
- parse_ldap_entry(entries[0])
269
-
270
- end
271
-
272
- def parse_ldap_entry(entry)
273
- @dn = entry['distinguishedName'][0]
274
- @username = entry['sAMAccountName'][0]
275
- @name = entry['displayName'][0] unless entry['displayName'].nil?
276
- @given_name = entry['givenName'][0] unless entry['givenName'].nil?
277
- @surname = entry['sn'][0] unless entry['sn'].nil?
278
- @email = entry['mail'][0] unless entry['mail'].nil?
279
- @manager_dn = entry['manager'][0] unless entry['manager'].nil?
280
- @company = entry['company'][0] unless entry['company'].nil?
281
- @department = entry['department'][0] unless entry['department'].nil?
282
- @title = entry['title'][0] unless entry['title'].nil?
283
- @main_number = entry['telephoneNumber'][0] unless entry['telephoneNumber'].nil?
284
- @mobile_number = entry['mobile'][0] unless entry['mobile'].nil?
285
- @street_address = entry['streetAddress'][0] unless entry['streetAddress'].nil?
286
- @city = entry['l'][0] unless entry['l'].nil?
287
- @state = entry['st'][0] unless entry['st'].nil?
288
- @zip = entry['postalCode'][0] unless entry['postalCode'].nil?
289
- @country = entry['co'][0] unless entry['co'].nil?
290
- @direct_reports = entry['directReports']
291
- @groups = entry['memberOf']
292
- end
293
-
294
- end
295
-
296
- end
25
+ class User < Base
26
+ include Member
27
+
28
+ UAC_ACCOUNT_DISABLED = 0x0002
29
+ UAC_NORMAL_ACCOUNT = 0x0200 # 512
30
+
31
+ def self.filter # :nodoc:
32
+ Net::LDAP::Filter.eq(:objectClass,'user') & ~Net::LDAP::Filter.eq(:objectClass,'computer')
33
+ end
34
+
35
+ def self.required_attributes #:nodoc:
36
+ { :objectClass => ['top', 'organizationalPerson', 'person', 'user'] }
37
+ end
38
+
39
+ #
40
+ # Try to authenticate the current User against Active Directory
41
+ # using the supplied password. Returns false upon failure.
42
+ #
43
+ # Authenticate can fail for a variety of reasons, primarily:
44
+ #
45
+ # * The password is wrong
46
+ # * The account is locked
47
+ # * The account is disabled
48
+ #
49
+ # User#locked? and User#disabled? can be used to identify the
50
+ # latter two cases, and if the account is enabled and unlocked,
51
+ # Athe password is probably invalid.
52
+ #
53
+ def authenticate(password)
54
+ return false if password.to_s.empty?
55
+
56
+ auth_ldap = @@ldap.dup.bind_as(
57
+ :filter => "(sAMAccountName=#{sAMAccountName})",
58
+ :password => password
59
+ )
60
+ end
61
+
62
+ #
63
+ # Return the User's manager (another User object), depending on
64
+ # what is stored in the manager attribute.
65
+ #
66
+ # Returns nil if the schema does not include the manager attribute
67
+ # or if no manager has been configured.
68
+ #
69
+ def manager
70
+ return nil if @entry.manager.nil?
71
+ User.find_by_distinguishedName(@entry.manager.to_s)
72
+ end
73
+
74
+ #
75
+ # Returns an array of Group objects that this User belongs to.
76
+ # Only the immediate parent groups are returned, so if the user
77
+ # Sally is in a group called Sales, and Sales is in a group
78
+ # called Marketting, this method would only return the Sales group.
79
+ #
80
+ def groups
81
+ @groups ||= memberOf.collect { |dn| Group.find_by_distinguishedName(dn) }
82
+ end
83
+
84
+ #
85
+ # Returns an array of User objects that have this
86
+ # User as their manager.
87
+ #
88
+ def direct_reports
89
+ return [] if @entry.directReports.nil?
90
+ @direct_reports ||= @entry.directReports.collect { |dn| User.find_by_distinguishedName(dn) }
91
+ end
92
+
93
+ #
94
+ # Returns true if this account has been locked out
95
+ # (usually because of too many invalid authentication attempts).
96
+ #
97
+ # Locked accounts can be unlocked with the User#unlock! method.
98
+ #
99
+ def locked?
100
+ !lockoutTime.nil? && lockoutTime.to_i != 0
101
+ end
102
+
103
+ #
104
+ # Returns true if this account has been disabled.
105
+ #
106
+ def disabled?
107
+ userAccountControl.to_i & UAC_ACCOUNT_DISABLED != 0
108
+ end
109
+
110
+ #
111
+ # Returns true if the user should be able to log in with a correct
112
+ # password (essentially, their account is not disabled or locked
113
+ # out).
114
+ #
115
+ def can_login?
116
+ !disabled? && !locked?
117
+ end
118
+
119
+ #
120
+ # Change the password for this account.
121
+ #
122
+ # This operation requires that the bind user specified in
123
+ # Base.setup have heightened privileges. It also requires an
124
+ # SSL connection.
125
+ #
126
+ # If the force_change argument is passed as true, the password will
127
+ # be marked as 'expired', forcing the user to change it the next
128
+ # time they successfully log into the domain.
129
+ #
130
+ def change_password(new_password, force_change = false)
131
+ settings = @@settings.dup.merge {
132
+ :port => 636,
133
+ :encryption => { :method => :simple_tls }
134
+ }
135
+
136
+ ldap = Net::LDAP.new(settings)
137
+ ldap.modify(
138
+ :dn => distinguishedName,
139
+ :operations => [
140
+ [ :replace, :lockoutTime, [ '0' ] ],
141
+ [ :replace, :unicodePwd, [ Password.encode(new_password) ] ],
142
+ [ :replace, :userAccountControl, [ UAC_NORMAL_ACCOUNT.to_s ] ],
143
+ [ :replace, :pwdLastSet, [ (force_change ? '0' : '-1') ] ]
144
+ ]
145
+ )
146
+ end
147
+
148
+ #
149
+ # Unlocks this account.
150
+ #
151
+ def unlock!
152
+ @@ldap.replace_attribute(distinguishedName, :lockoutTime, ['0'])
153
+ end
154
+ end
155
+ end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: activedirectory
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.9.3
7
- date: 2006-05-11 00:00:00 -06:00
8
- summary: Module for easy interaction with Active Directory servers.
6
+ version: 1.0.0
7
+ date: 2008-08-02 00:00:00 -05:00
8
+ summary: An interface library for accessing Microsoft's Active Directory.
9
9
  require_paths:
10
10
  - lib
11
- email: justin@aspect.net
12
- homepage:
13
- rubyforge_project:
14
- description: Makes it trivial to integrate with Active Directory servers for information and authentication concerning users and groups.
15
- autorequire: active_directory
11
+ email: filefrog@gmail.com
12
+ homepage: http://rubyforge.net/projects/activedirectory
13
+ rubyforge_project: activedirectory
14
+ description: ActiveDirectory uses Net::LDAP to provide a means of accessing and modifying an Active Directory data store.
15
+ autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
@@ -25,22 +25,21 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
- - Justin Mecham
30
+ - James R. Hunt
30
31
  files:
31
- - Rakefile
32
- - install.rb
33
- - README
34
- - LICENSE
35
- - lib/active_directory
36
- - lib/active_directory.rb
32
+ - lib/active_directory/container.rb
33
+ - lib/active_directory/timestamp.rb
34
+ - lib/active_directory/user.rb
35
+ - lib/active_directory/computer.rb
36
+ - lib/active_directory/password.rb
37
+ - lib/active_directory/member.rb
37
38
  - lib/active_directory/base.rb
38
- - lib/active_directory/ext
39
+ - lib/active_directory/rails/user.rb
40
+ - lib/active_directory/rails/synchronizer.rb
39
41
  - lib/active_directory/group.rb
40
- - lib/active_directory/user.rb
41
- - lib/active_directory/version.rb
42
- - lib/active_directory/ext/class.rb
43
- - lib/active_directory/ext/hash.rb
42
+ - lib/active_directory.rb
44
43
  test_files: []
45
44
 
46
45
  rdoc_options: []
@@ -51,7 +50,15 @@ executables: []
51
50
 
52
51
  extensions: []
53
52
 
54
- requirements:
55
- - none
56
- dependencies: []
53
+ requirements: []
57
54
 
55
+ dependencies:
56
+ - !ruby/object:Gem::Dependency
57
+ name: ruby-net-ldap
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Version::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.0.4
64
+ version: