adtools 0.0.1pre

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.
@@ -0,0 +1,45 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module Adtools
22
+ module FieldType
23
+ class Timestamp
24
+ AD_DIVISOR = 10_000_000 #:nodoc:
25
+ AD_OFFSET = 11_644_473_600 #:nodoc:
26
+
27
+ #
28
+ # Encodes a local Time object (or the number of seconds since January
29
+ # 1, 1970) into a timestamp that the Active Directory server can
30
+ # understand (number of 100 nanosecond time units since January 1, 1600)
31
+ #
32
+ def self.encode(local_time)
33
+ (local_time.to_i + AD_OFFSET) * AD_DIVISOR
34
+ end
35
+
36
+ #
37
+ # Decodes an Active Directory timestamp (the number of 100 nanosecond time
38
+ # units since January 1, 1600) into a Ruby Time object.
39
+ #
40
+ def self.decode(remote_time)
41
+ Time.at( (remote_time.to_i / AD_DIVISOR) - AD_OFFSET )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module Adtools
22
+ module FieldType
23
+ class UserDnArray
24
+ #
25
+ # Encodes an array of objects into a list of dns
26
+ #
27
+ def self.encode(obj_array)
28
+ obj_array.collect { |obj| obj.dn }
29
+ end
30
+
31
+ #
32
+ # Decodes a list of DNs into the objects that they are
33
+ #
34
+ def self.decode(dn_array)
35
+ # How to do user or group?
36
+ User.find(:all, :distinguishedname => dn_array)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,137 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module Adtools
22
+ class Group < Base
23
+ include Member
24
+
25
+ def self.filter # :nodoc:
26
+ Net::LDAP::Filter.eq(:objectClass,'group')
27
+ end
28
+
29
+ def self.required_attributes # :nodoc:
30
+ { :objectClass => [ 'top', 'group' ] }
31
+ end
32
+
33
+ def reload # :nodoc:
34
+ @member_users_non_r = nil
35
+ @member_users_r = nil
36
+ @member_groups_non_r = nil
37
+ @member_groups_r = nil
38
+ @groups = nil
39
+ super
40
+ end
41
+
42
+ #
43
+ # Returns true if the passed User or Group object belongs to
44
+ # this group. For performance reasons, the check is handled
45
+ # by the User or Group object passed.
46
+ #
47
+ def has_member?(user)
48
+ user.member_of?(self)
49
+ end
50
+
51
+ #
52
+ # Add the passed User or Group object to this Group. Returns true if
53
+ # the User or Group is already a member of the group, or if the operation
54
+ # to add them succeeds.
55
+ #
56
+ def add(new_member)
57
+ return false unless new_member.is_a?(User) || new_member.is_a?(Group)
58
+ if @@ldap.modify(:dn => distinguishedName, :operations => [
59
+ [ :add, :member, new_member.distinguishedName ]
60
+ ])
61
+ return true
62
+ else
63
+ return has_member?(new_member)
64
+ end
65
+ end
66
+
67
+ #
68
+ # Remove a User or Group from this Group. Returns true if the User or
69
+ # Group does not belong to this Group, or if the oepration to remove them
70
+ # succeeds.
71
+ #
72
+ def remove(member)
73
+ return false unless member.is_a?(User) || member.is_a?(Group)
74
+ if @@ldap.modify(:dn => distinguishedName, :operations => [
75
+ [ :delete, :member, member.distinguishedName ]
76
+ ])
77
+ return true
78
+ else
79
+ return !has_member?(member)
80
+ end
81
+ end
82
+
83
+ def has_members?
84
+ begin
85
+ return (@entry.member.nil? || @entry.member.empty?) ? false : true
86
+ rescue NoMethodError
87
+ return false
88
+ end
89
+ end
90
+
91
+ #
92
+ # Returns an array of all User objects that belong to this group.
93
+ #
94
+ # If the recursive argument is passed as false, then only Users who
95
+ # belong explicitly to this Group are returned.
96
+ #
97
+ # If the recursive argument is passed as true, then all Users who
98
+ # belong to this Group, or any of its subgroups, are returned.
99
+ #
100
+ def member_users(recursive = false)
101
+ @member_users = User.find(:all, :distinguishedname => @entry.member).delete_if { |u| u.nil? }
102
+ if recursive then
103
+ self.member_groups.each do |group|
104
+ @member_users.concat(group.member_users(true))
105
+ end
106
+ end
107
+ return @member_users
108
+ end
109
+
110
+ #
111
+ # Returns an array of all Group objects that belong to this group.
112
+ #
113
+ # If the recursive argument is passed as false, then only Groups that
114
+ # belong explicitly to this Group are returned.
115
+ #
116
+ # If the recursive argument is passed as true, then all Groups that
117
+ # belong to this Group, or any of its subgroups, are returned.
118
+ #
119
+ def member_groups(recursive = false)
120
+ @member_groups ||= Group.find(:all, :distinguishedname => @entry.member).delete_if { |g| g.nil? }
121
+ if recursive then
122
+ self.member_groups.each do |group|
123
+ @member_groups.concat(group.member_groups(true))
124
+ end
125
+ end
126
+ return @member_groups
127
+ end
128
+
129
+ #
130
+ # Returns an array of Group objects that this Group belongs to.
131
+ #
132
+ def groups
133
+ return [] if memberOf.nil?
134
+ @groups ||= Group.find(:all, :distinguishedname => @entry.memberOf).delete_if { |g| g.nil? }
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,53 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module Adtools
22
+ module Member
23
+ #
24
+ # Returns true if this member (User or Group) is a member of
25
+ # the passed Group object.
26
+ #
27
+ def member_of?(usergroup)
28
+ group_dns = memberOf
29
+ return false if group_dns.nil? || group_dns.empty?
30
+ #group_dns = [group_dns] unless group_dns.is_a?(Array)
31
+ group_dns.include?(usergroup.dn)
32
+ end
33
+
34
+ #
35
+ # Add the member to the passed Group object. Returns true if this object
36
+ # is already a member of the Group, or if the operation to add it succeeded.
37
+ #
38
+ def join(group)
39
+ return false unless group.is_a?(Group)
40
+ group.add(self)
41
+ end
42
+
43
+ #
44
+ # Remove the member from the passed Group object. Returns true if this
45
+ # object is not a member of the Group, or if the operation to remove it
46
+ # succeeded.
47
+ #
48
+ def unjoin(group)
49
+ return false unless group.is_a?(Group)
50
+ group.remove(self)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ module Adtools
2
+ class Ou < Base
3
+ def self.filter
4
+ Net::LDAP::Filter.eq(:objectClass,'organizationalUnit')
5
+ end
6
+
7
+ def self.required_attributes
8
+ { :objectClass => [ 'top', 'organizationalUnit' ] }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,152 @@
1
+ #-- license
2
+ #
3
+ # Based on original code by Justin Mecham and James Hunt
4
+ # at http://rubyforge.org/projects/activedirectory
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ #++ license
20
+
21
+ module Adtools
22
+ class User < Base
23
+ include Member
24
+
25
+ UAC_ACCOUNT_DISABLED = 0x0002
26
+ UAC_NORMAL_ACCOUNT = 0x0200 # 512
27
+
28
+ def self.filter # :nodoc:
29
+ Net::LDAP::Filter.eq(:objectClass,'user') & ~Net::LDAP::Filter.eq(:objectClass,'computer')
30
+ end
31
+
32
+ def self.required_attributes #:nodoc:
33
+ { :objectClass => ['top', 'organizationalPerson', 'person', 'user'] }
34
+ end
35
+
36
+ #
37
+ # Try to authenticate the current User against Active Directory
38
+ # using the supplied password. Returns false upon failure.
39
+ #
40
+ # Authenticate can fail for a variety of reasons, primarily:
41
+ #
42
+ # * The password is wrong
43
+ # * The account is locked
44
+ # * The account is disabled
45
+ #
46
+ # User#locked? and User#disabled? can be used to identify the
47
+ # latter two cases, and if the account is enabled and unlocked,
48
+ # Athe password is probably invalid.
49
+ #
50
+ def authenticate(password)
51
+ return false if password.to_s.empty?
52
+
53
+ auth_ldap = @@ldap.dup.bind_as(
54
+ :filter => "(sAMAccountName=#{sAMAccountName})",
55
+ :password => password
56
+ )
57
+ end
58
+
59
+ #
60
+ # Return the User's manager (another User object), depending on
61
+ # what is stored in the manager attribute.
62
+ #
63
+ # Returns nil if the schema does not include the manager attribute
64
+ # or if no manager has been configured.
65
+ #
66
+ def manager
67
+ return nil if @entry.manager.nil?
68
+ User.find_by_distinguishedName(@entry.manager.to_s)
69
+ end
70
+
71
+ #
72
+ # Returns an array of Group objects that this User belongs to.
73
+ # Only the immediate parent groups are returned, so if the user
74
+ # Sally is in a group called Sales, and Sales is in a group
75
+ # called Marketting, this method would only return the Sales group.
76
+ #
77
+ def groups
78
+ @groups ||= Group.find(:all, :distinguishedname => @entry.memberOf)
79
+ end
80
+
81
+ #
82
+ # Returns an array of User objects that have this
83
+ # User as their manager.
84
+ #
85
+ def direct_reports
86
+ return [] if @entry.directReports.nil?
87
+ @direct_reports ||= User.find(:all, @entry.directReports)
88
+ end
89
+
90
+ #
91
+ # Returns true if this account has been locked out
92
+ # (usually because of too many invalid authentication attempts).
93
+ #
94
+ # Locked accounts can be unlocked with the User#unlock! method.
95
+ #
96
+ def locked?
97
+ !lockoutTime.nil? && lockoutTime.to_i != 0
98
+ end
99
+
100
+ #
101
+ # Returns true if this account has been disabled.
102
+ #
103
+ def disabled?
104
+ userAccountControl.to_i & UAC_ACCOUNT_DISABLED != 0
105
+ end
106
+
107
+ #
108
+ # Returns true if the user should be able to log in with a correct
109
+ # password (essentially, their account is not disabled or locked
110
+ # out).
111
+ #
112
+ def can_login?
113
+ !disabled? && !locked?
114
+ end
115
+
116
+ #
117
+ # Change the password for this account.
118
+ #
119
+ # This operation requires that the bind user specified in
120
+ # Base.setup have heightened privileges. It also requires an
121
+ # SSL connection.
122
+ #
123
+ # If the force_change argument is passed as true, the password will
124
+ # be marked as 'expired', forcing the user to change it the next
125
+ # time they successfully log into the domain.
126
+ #
127
+ def change_password(new_password, force_change = false)
128
+ settings = @@settings.dup.merge({
129
+ :port => 636,
130
+ :encryption => { :method => :simple_tls }
131
+ })
132
+
133
+ ldap = Net::LDAP.new(settings)
134
+ ldap.modify(
135
+ :dn => distinguishedName,
136
+ :operations => [
137
+ [ :replace, :lockoutTime, [ '0' ] ],
138
+ [ :replace, :unicodePwd, [ FieldType::Password.encode(new_password) ] ],
139
+ [ :replace, :userAccountControl, [ UAC_NORMAL_ACCOUNT.to_s ] ],
140
+ [ :replace, :pwdLastSet, [ (force_change ? '0' : '-1') ] ]
141
+ ]
142
+ )
143
+ end
144
+
145
+ #
146
+ # Unlocks this account.
147
+ #
148
+ def unlock!
149
+ @@ldap.replace_attribute(distinguishedName, :lockoutTime, ['0'])
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,3 @@
1
+ module Adtools
2
+ Version = '0.0.1pre'
3
+ end