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,117 @@
|
|
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
|
+
#
|
26
|
+
# The ActiveDirectory::Container class represents a more malleable way
|
27
|
+
# of dealing with LDAP Distinguished Names (dn), like
|
28
|
+
# "cn=UserName,ou=Users,dc=example,dc=org".
|
29
|
+
#
|
30
|
+
# The following two representations of the above dn are identical:
|
31
|
+
#
|
32
|
+
# dn = "cn=UserName,ou=Users,dc=example,dc=org"
|
33
|
+
# dn = ActiveDirectory::Container.dc('org').dc('example').ou('Users').cn('UserName').to_s
|
34
|
+
#
|
35
|
+
class Container
|
36
|
+
attr_reader :type
|
37
|
+
attr_reader :name
|
38
|
+
attr_reader :parent
|
39
|
+
|
40
|
+
def initialize(type, name, node = nil) #:nodoc:
|
41
|
+
@type = type
|
42
|
+
@name = name
|
43
|
+
@node = node
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Creates a starting OU (Organizational Unit) dn part.
|
48
|
+
#
|
49
|
+
# # ou_part = "ou=OrganizationalUnit"
|
50
|
+
# ou_part = ActiveDirectory::Container.ou('OrganizationalUnit').to_s
|
51
|
+
#
|
52
|
+
def self.ou(name)
|
53
|
+
new(:ou, name, nil)
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Creates a starting DC (Domain Component) dn part.
|
58
|
+
#
|
59
|
+
# # dc_part = "dc=net"
|
60
|
+
# dc_part = ActiveDirectory::Container.dc('net').to_s
|
61
|
+
#
|
62
|
+
def self.dc(name)
|
63
|
+
new(:dc, name, nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Creates a starting CN (Canonical Name) dn part.
|
68
|
+
#
|
69
|
+
# # cn_part = "cn=CanonicalName"
|
70
|
+
# cn_part = ActiveDirectory::Container.cn('CanonicalName').to_s
|
71
|
+
#
|
72
|
+
def self.cn(name)
|
73
|
+
new(:cn, name, nil)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Appends an OU (Organizational Unit) dn part to another Container.
|
78
|
+
#
|
79
|
+
# # ou = "ou=InfoTech,dc=net"
|
80
|
+
# ou = ActiveDirectory::Container.dc("net").ou("InfoTech").to_s
|
81
|
+
#
|
82
|
+
def ou(name)
|
83
|
+
self.class.new(:ou, name, self)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Appends a DC (Domain Component) dn part to another Container.
|
88
|
+
#
|
89
|
+
# # base = "dc=example,dc=net"
|
90
|
+
# base = ActiveDirectory::Container.dc("net").dc("example").to_s
|
91
|
+
#
|
92
|
+
def dc(name)
|
93
|
+
self.class.new(:dc, name, self)
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Appends a CN (Canonical Name) dn part to another Container.
|
98
|
+
#
|
99
|
+
# # user = "cn=UID,ou=Users"
|
100
|
+
# user = ActiveDirectory::Container.ou("Users").cn("UID")
|
101
|
+
#
|
102
|
+
def cn(name)
|
103
|
+
self.class.new(:cn, name, self)
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# Converts the Container object to its String representation.
|
108
|
+
#
|
109
|
+
def to_s
|
110
|
+
@node ? "#{@type}=#{name},#{@node.to_s}" : "#{@type}=#{name}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other) #:nodoc:
|
114
|
+
to_s.downcase == other.to_s.downcase
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,162 @@
|
|
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 Group < Base
|
26
|
+
include Member
|
27
|
+
|
28
|
+
def self.filter # :nodoc:
|
29
|
+
Net::LDAP::Filter.eq(:objectClass,'group')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.required_attributes # :nodoc:
|
33
|
+
{ :objectClass => [ 'top', 'group' ] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def reload # :nodoc:
|
37
|
+
@member_users_non_r = nil
|
38
|
+
@member_users_r = nil
|
39
|
+
@member_groups_non_r = nil
|
40
|
+
@member_groups_r = nil
|
41
|
+
@groups = nil
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Returns true if the passed User or Group object belongs to
|
47
|
+
# this group. For performance reasons, the check is handled
|
48
|
+
# by the User or Group object passed.
|
49
|
+
#
|
50
|
+
def has_member?(user)
|
51
|
+
user.member_of?(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Add the passed User or Group object to this Group. Returns true if
|
56
|
+
# the User or Group is already a member of the group, or if the operation
|
57
|
+
# to add them succeeds.
|
58
|
+
#
|
59
|
+
def add(new_member)
|
60
|
+
return false unless new_member.is_a?(User) || new_member.is_a?(Group)
|
61
|
+
if @@ldap.modify(:dn => distinguishedName, :operations => [
|
62
|
+
[ :add, :member, new_member.distinguishedName ]
|
63
|
+
])
|
64
|
+
return true
|
65
|
+
else
|
66
|
+
return has_member?(new_member)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Remove a User or Group from this Group. Returns true if the User or
|
72
|
+
# Group does not belong to this Group, or if the oepration to remove them
|
73
|
+
# succeeds.
|
74
|
+
#
|
75
|
+
def remove(member)
|
76
|
+
return false unless member.is_a?(User) || member.is_a?(Group)
|
77
|
+
if @@ldap.modify(:dn => distinguishedName, :operations => [
|
78
|
+
[ :delete, :member, member.distinguishedName ]
|
79
|
+
])
|
80
|
+
return true
|
81
|
+
else
|
82
|
+
return !has_member?(member)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def has_members?
|
87
|
+
begin
|
88
|
+
return (@entry.member.nil? || @entry.member.empty?) ? false : true
|
89
|
+
rescue NoMethodError
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Returns an array of all User objects that belong to this group.
|
96
|
+
#
|
97
|
+
# If the recursive argument is passed as false, then only Users who
|
98
|
+
# belong explicitly to this Group are returned.
|
99
|
+
#
|
100
|
+
# If the recursive argument is passed as true, then all Users who
|
101
|
+
# belong to this Group, or any of its subgroups, are returned.
|
102
|
+
#
|
103
|
+
def member_users(recursive = false)
|
104
|
+
return [] unless has_members?
|
105
|
+
if recursive
|
106
|
+
if @member_users_r.nil?
|
107
|
+
@member_users_r = []
|
108
|
+
@entry.member.each do |member_dn|
|
109
|
+
subuser = User.find_by_distinguishedName(member_dn)
|
110
|
+
if subuser
|
111
|
+
@member_users_r << subuser
|
112
|
+
else
|
113
|
+
subgroup = Group.find_by_distinguishedName(member_dn)
|
114
|
+
if subgroup
|
115
|
+
@member_users_r = @member_users_r.concat(subgroup.member_users(true))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
return @member_users_r
|
121
|
+
else
|
122
|
+
@member_users_non_r ||= @entry.member.collect { |dn| User.find_by_distinguishedName(dn) }.delete_if { |u| u.nil? }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Returns an array of all Group objects that belong to this group.
|
128
|
+
#
|
129
|
+
# If the recursive argument is passed as false, then only Groups that
|
130
|
+
# belong explicitly to this Group are returned.
|
131
|
+
#
|
132
|
+
# If the recursive argument is passed as true, then all Groups that
|
133
|
+
# belong to this Group, or any of its subgroups, are returned.
|
134
|
+
#
|
135
|
+
def member_groups(recursive = false)
|
136
|
+
return [] unless has_members?
|
137
|
+
if recursive
|
138
|
+
if @member_groups_r.nil?
|
139
|
+
@member_groups_r = []
|
140
|
+
@entry.member.each do |member_dn|
|
141
|
+
subgroup = Group.find_by_distinguishedName(member_dn)
|
142
|
+
if subgroup
|
143
|
+
@member_groups_r << subgroup
|
144
|
+
@member_groups_r = @member_groups_r.concat(subgroup.member_groups(true))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
return @member_groups_r
|
149
|
+
else
|
150
|
+
@member_groups_non_r ||= @entry.member.collect { |dn| Group.find_by_distinguishedName(dn) }.delete_if { |g| g.nil? }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Returns an array of Group objects that this Group belongs to.
|
156
|
+
#
|
157
|
+
def groups
|
158
|
+
return [] if memberOf.nil?
|
159
|
+
@groups ||= memberOf.collect { |group_dn| Group.find_by_distinguishedName(group_dn) }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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 Member
|
26
|
+
#
|
27
|
+
# Returns true if this member (User or Group) is a member of
|
28
|
+
# the passed Group object.
|
29
|
+
#
|
30
|
+
def member_of?(usergroup)
|
31
|
+
group_dns = memberOf
|
32
|
+
return false if group_dns.nil? || group_dns.empty?
|
33
|
+
#group_dns = [group_dns] unless group_dns.is_a?(Array)
|
34
|
+
group_dns.include?(usergroup.dn)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Add the member to the passed Group object. Returns true if this object
|
39
|
+
# is already a member of the Group, or if the operation to add it succeeded.
|
40
|
+
#
|
41
|
+
def join(group)
|
42
|
+
return false unless group.is_a?(Group)
|
43
|
+
group.add(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Remove the member from the passed Group object. Returns true if this
|
48
|
+
# object is not a member of the Group, or if the operation to remove it
|
49
|
+
# succeeded.
|
50
|
+
#
|
51
|
+
def unjoin(group)
|
52
|
+
return false unless group.is_a?(Group)
|
53
|
+
group.remove(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 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
|
33
|
+
|
34
|
+
#
|
35
|
+
# Always returns nil, since you can't decrypt the User's encrypted
|
36
|
+
# password.
|
37
|
+
#
|
38
|
+
def self.decode(hashed)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,234 @@
|
|
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
|