activedirectory 0.9.3 → 1.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.
@@ -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: