chuckdbacon-activedirectory 1.0.4
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/README +4 -0
- data/lib/active_directory.rb +37 -0
- data/lib/active_directory/base.rb +405 -0
- data/lib/active_directory/computer.rb +38 -0
- data/lib/active_directory/container.rb +117 -0
- data/lib/active_directory/group.rb +162 -0
- data/lib/active_directory/member.rb +56 -0
- data/lib/active_directory/password.rb +42 -0
- data/lib/active_directory/rails/synchronizer.rb +234 -0
- data/lib/active_directory/rails/user.rb +141 -0
- data/lib/active_directory/timestamp.rb +46 -0
- data/lib/active_directory/user.rb +155 -0
- metadata +69 -0
@@ -0,0 +1,141 @@
|
|
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
|
@@ -0,0 +1,46 @@
|
|
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
|
+
class Timestamp
|
26
|
+
AD_DIVISOR = 10_000_000 #:nodoc:
|
27
|
+
AD_OFFSET = 11_644_473_600 #:nodoc:
|
28
|
+
|
29
|
+
#
|
30
|
+
# Encodes a local Time object (or the number of seconds since January
|
31
|
+
# 1, 1970) into a timestamp that the Active Directory server can
|
32
|
+
# understand (number of 100 nanosecond time units since January 1, 1600)
|
33
|
+
#
|
34
|
+
def self.encode(local_time)
|
35
|
+
(local_time.to_i + AD_OFFSET) * AD_DIVISOR
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Decodes an Active Directory timestamp (the number of 100 nanosecond time
|
40
|
+
# units since January 1, 1600) into a Ruby Time object.
|
41
|
+
#
|
42
|
+
def self.decode(remote_time)
|
43
|
+
Time.at( (remote_time.to_i / AD_DIVISOR) - AD_OFFSET )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,155 @@
|
|
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
|
+
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
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chuckdbacon-activedirectory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James R Hunt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: net-ldap
|
16
|
+
requirement: &70256844765740 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70256844765740
|
25
|
+
description: ActiveDirectory uses Net::LDAP to provide a means of accessing and modifying
|
26
|
+
an Active Directory data store.
|
27
|
+
email: filefrog@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README
|
32
|
+
files:
|
33
|
+
- lib/active_directory.rb
|
34
|
+
- lib/active_directory/base.rb
|
35
|
+
- lib/active_directory/computer.rb
|
36
|
+
- lib/active_directory/container.rb
|
37
|
+
- lib/active_directory/group.rb
|
38
|
+
- lib/active_directory/member.rb
|
39
|
+
- lib/active_directory/password.rb
|
40
|
+
- lib/active_directory/rails/synchronizer.rb
|
41
|
+
- lib/active_directory/rails/user.rb
|
42
|
+
- lib/active_directory/timestamp.rb
|
43
|
+
- lib/active_directory/user.rb
|
44
|
+
- README
|
45
|
+
homepage: http://github.com/filefrog/activedirectory
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.11
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: An interface library for accessing Microsoft's Active Directory.
|
69
|
+
test_files: []
|