ucb_confluence 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,154 @@
1
+
2
+ ##
3
+ # Sync our confluence instance with LDAP so people in LDAP that are part
4
+ # of IST will be part of the ucb-ist group in confluence
5
+ #
6
+ module Confluence
7
+ module Jobs
8
+ class IstLdapSync
9
+
10
+ IST_GROUP = 'ucb-ist'
11
+
12
+ def initialize()
13
+ @new_users = []
14
+ @modified_users = []
15
+ end
16
+
17
+ ##
18
+ # Run the job
19
+ #
20
+ def execute()
21
+ @new_users.clear()
22
+ @modified_users.clear()
23
+ sync_ist_from_ldap()
24
+ sync_ist_from_confluence()
25
+ log_job()
26
+ end
27
+
28
+ ##
29
+ # If the IST LDAP person is not in confluence, add them. If they are in
30
+ # confluence but not part of the IST_GROUP, give them membership.
31
+ #
32
+ def sync_ist_from_ldap()
33
+ ist_people.each do |ldap_person|
34
+ next unless eligible_for_confluence?(ldap_person)
35
+
36
+ user = find_or_new_user(ldap_person.uid())
37
+
38
+ if user.new_record?
39
+ user.save()
40
+ user.join_group(Confluence::User::DEFAULT_GROUP)
41
+ @new_users << user
42
+ end
43
+
44
+ unless user.groups.include?(IST_GROUP)
45
+ user.join_group(IST_GROUP)
46
+ @modified_users << user
47
+ end
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Remove a confluene user from the IST_GROUP if LDAP indicates they are
53
+ # no longer part of IST
54
+ #
55
+ def sync_ist_from_confluence()
56
+ confluence_user_names.each do |name|
57
+ next if name == "conflusa"
58
+
59
+ ldap_person = find_in_ldap(name)
60
+ next if ldap_person.nil?
61
+
62
+ if !in_ist?(ldap_person)
63
+ user = find_in_confluence(name)
64
+ next if user.nil?
65
+ user.leave_group(IST_GROUP)
66
+ @modified_users << user
67
+ end
68
+ end
69
+ end
70
+
71
+ def log_job()
72
+ msg = "#{self.class.name}\n\n"
73
+
74
+ msg.concat("Modified Users\n\n")
75
+ @modified_users.each { |u| msg.concat(u) }
76
+ msg.concat("\n")
77
+
78
+ msg.concat("New Users\n\n")
79
+ @new_users.each { |u| msg.concat(u) }
80
+ msg.concat("\n")
81
+
82
+ logger.info(msg)
83
+ end
84
+
85
+ def logger()
86
+ Confluence.logger
87
+ end
88
+
89
+ ##
90
+ # @return [Array<String>] confluence user names.
91
+ #
92
+ def confluence_user_names()
93
+ Confluence::User.active.map(&:name)
94
+ end
95
+
96
+ ##
97
+ # All of the people in IST.
98
+ #
99
+ # @return [Array<UCB::LDAP::Person>]
100
+ #
101
+ def ist_people(str = "UCBKL-AVCIS-VRIST-*")
102
+ UCB::LDAP::Person.search(:filter => {"berkeleyedudeptunithierarchystring" => str})
103
+ end
104
+
105
+ ##
106
+ # Retrieves the user if they already exist in Confluence. Otherwise,
107
+ # returns a new record that has not yet been persisted to Confluence.
108
+ #
109
+ # @param [String] the user's ldap uid
110
+ # @return [Confluence::User]
111
+ #
112
+ def find_or_new_user(ldap_uid)
113
+ Confluence::User.find_or_new_from_ldap(ldap_uid)
114
+ end
115
+
116
+ ##
117
+ # @param [String] user's confluence account name.
118
+ # @return [Confluence::User, nil]
119
+ #
120
+ def find_in_confluence(name)
121
+ Confluence::User.find_by_name(name)
122
+ end
123
+
124
+ ##
125
+ # @param [String] user's ldap uid
126
+ # @return [UCB::LDAP::Person, nil]
127
+ #
128
+ def find_in_ldap(ldap_uid)
129
+ UCB::LDAP::Person.find_by_uid(ldap_uid)
130
+ end
131
+
132
+ def in_ist?(person)
133
+ person.berkeleyEduDeptUnitHierarchyString.each do |str|
134
+ return true if str =~ /UCBKL-AVCIS-VRIST-.*/
135
+ end
136
+ false
137
+ end
138
+
139
+ def eligible_for_confluence?(person)
140
+ valid_affiliations = person.affiliations.inject([]) do |accum, aff|
141
+ if aff =~ /AFFILIATE-TYPE.*(ALUMNUS|RETIREE|EXPIRED)/
142
+ accum
143
+ elsif aff =~ /AFFILIATE-TYPE.*/
144
+ accum << aff
145
+ end
146
+ accum
147
+ end
148
+
149
+ person.employee? || !valid_affiliations.empty?
150
+ end
151
+ end
152
+ end
153
+ end
154
+
@@ -0,0 +1,312 @@
1
+ module Confluence
2
+ class User
3
+ DISABLED_SUFFIX = "(ACCOUNT DISABLED)"
4
+ DEFAULT_GROUP = 'confluence-users'
5
+ VALID_ATTRS = [:name, :fullname, :email]
6
+
7
+ class LdapPersonNotFound < StandardError; end;
8
+
9
+ attr_accessor :name, :fullname, :email
10
+
11
+ ##
12
+ # Unrecognized attributes are ignored
13
+ #
14
+ def initialize(attrs = {})
15
+ @new_record = true
16
+ @errors = []
17
+ VALID_ATTRS.each do |attr|
18
+ self.send("#{attr}=", attrs[attr] || attrs[attr.to_s])
19
+ end
20
+ end
21
+
22
+ def self.new_from_ldap(ldap_person)
23
+ @new_record = true
24
+ @errors = []
25
+ self.new({
26
+ :name => ldap_person.uid,
27
+ :fullname => "#{ldap_person.first_name} + #{ldap_person.last_name}",
28
+ :email => ldap_person.email || "test@berkeley.edu"
29
+ })
30
+ end
31
+
32
+ ##
33
+ # Lets confluence XML-RPC access this object as if it was a Hash.
34
+ # returns nil if key is not in VALID_ATTRS
35
+ #
36
+ def [](key)
37
+ self.send(key) if VALID_ATTRS.include?(key.to_sym)
38
+ end
39
+
40
+ ##
41
+ # Name can only be set if the user has not yet been saved to confluence
42
+ # users table. Once they have been saved, the name is immutable. This is
43
+ # a restriction enforced by Confluence's API.
44
+ #
45
+ def name=(n)
46
+ @name = n if new_record?
47
+ end
48
+
49
+ ##
50
+ # Predicate that determines if this [User] record has been persisted.
51
+ #
52
+ # @return [true, false] evaluates to true if the record has not been
53
+ # persisted, evaluates to false if it has not been persisted.
54
+ #
55
+ def new_record?
56
+ @new_record
57
+ end
58
+
59
+ def to_s()
60
+ "name=#{name}, fullname=#{fullname}, email=#{email}"
61
+ end
62
+
63
+ ##
64
+ # Creates a [Hash] representation of this user object.
65
+ #
66
+ # @example
67
+ # user.to_hash
68
+ # #=> {"name" => "runner", "fullname" => "Steven Hansen", "runner@b.e"}
69
+ #
70
+ # @return [Hash<String,String>]
71
+ #
72
+ def to_hash()
73
+ {"name" => name, "fullname" => fullname, "email" => email}
74
+ end
75
+
76
+ ##
77
+ # List of all groups this user has membership in.
78
+ #
79
+ # @return [Array<String>] names of all groups.
80
+ #
81
+ def groups()
82
+ return [] if new_record?
83
+ conn.getUserGroups(self.name)
84
+ end
85
+
86
+ ##
87
+ # Gives user membership in a group.
88
+ #
89
+ # @param [String] the name of the group
90
+ # @return [true, false] result of whether group membership was successful.
91
+ #
92
+ def join_group(grp)
93
+ @errors.clear
94
+ unless groups.include?(grp)
95
+ conn.addUserToGroup(self.name, grp)
96
+ logger.debug("User [#{self}] added to group: #{grp}")
97
+ return true
98
+ else
99
+ @errors << "User is already in group: #{grp}"
100
+ return false
101
+ end
102
+ rescue(RuntimeError) => e
103
+ logger.debug(e.message)
104
+ @errors << e.message
105
+ return false
106
+ end
107
+
108
+ ##
109
+ # Removes user from a group.
110
+ #
111
+ # @param [String] the name of the group
112
+ # @return [true, false] result of whether removal from group was successful.
113
+ #
114
+ def leave_group(grp)
115
+ @errors.clear
116
+ if groups.include?(grp)
117
+ conn.removeUserFromGroup(self.name, grp)
118
+ logger.debug("User [#{self}] removed from group: #{grp}")
119
+ return true
120
+ else
121
+ @errors << "User not in group: #{grp}"
122
+ return false
123
+ end
124
+ rescue(RuntimeError) => e
125
+ logger.debug(e.message)
126
+ @errors << e.message
127
+ return false
128
+ end
129
+
130
+ ##
131
+ # Persists any changes to this user. If the user record is new, a new record
132
+ # is created.
133
+ #
134
+ # @return [true, false] result of whether operation was successful.
135
+ #
136
+ def save()
137
+ @errors.clear
138
+ if new_record?
139
+ conn.addUser(self.to_hash, Confluence.config[:user_default_password])
140
+ @new_record = false
141
+ else
142
+ conn.editUser(self.to_hash)
143
+ end
144
+ return true
145
+ rescue(RuntimeError) => e
146
+ logger.debug(e.message)
147
+ @errors << e.message
148
+ return false
149
+ end
150
+
151
+ ##
152
+ # Deletes the user from Confluence.
153
+ #
154
+ # @return [true, false] result of whether operation was successful.
155
+ #
156
+ def delete()
157
+ @errors.clear
158
+ conn.removeUser(name.to_s)
159
+ self.freeze
160
+ return true
161
+ rescue(RuntimeError) => e
162
+ logger.debug(e.message)
163
+ @errors << e.message
164
+ return false
165
+ end
166
+
167
+ ##
168
+ # Flags this user as disabled (inactive) and removes them from all
169
+ # groups. Update happens immediately.
170
+ #
171
+ # @return [true, false] true if the operation was successfull, otherwise
172
+ # false
173
+ #
174
+ def disable()
175
+ @errors.clear
176
+ if disabled?
177
+ logger.debug("#{self} has already been disabled")
178
+ return true
179
+ end
180
+
181
+ groups.each { |grp| leave_group(grp) }
182
+ self.fullname = "#{self.fullname} #{DISABLED_SUFFIX}"
183
+ result = self.save()
184
+ logger.debug("Disabled user: #{self}")
185
+ result
186
+ end
187
+
188
+ ##
189
+ # Predicate indicating if the current user is disabled (inactive)
190
+ #
191
+ # @return [true, false]
192
+ #
193
+ def disabled?
194
+ fullname.include?(DISABLED_SUFFIX) && groups.empty?
195
+ end
196
+
197
+ def logger()
198
+ self.class.logger
199
+ end
200
+
201
+ def conn()
202
+ self.class.conn
203
+ end
204
+
205
+ ##
206
+ # List of errors associated with this record.
207
+ #
208
+ # @return [Array<String>]
209
+ #
210
+ def errors()
211
+ @errors ||= []
212
+ end
213
+
214
+ class << self
215
+ def conn()
216
+ Confluence.conn
217
+ end
218
+
219
+ def logger()
220
+ Confluence.logger
221
+ end
222
+
223
+ ##
224
+ # Finds an existing Confluence user by their name (which also happens
225
+ # to be their ldap_uid). If they do not exist in Confluence, we look
226
+ # them up in LDAP and then add them to Confluence finally returning
227
+ # the newly created user object.
228
+ #
229
+ def find_or_create_from_ldap(name)
230
+ user = find_or_new_from_ldap(name)
231
+ user.save if user.new_record?
232
+ user
233
+ end
234
+
235
+ def find_or_new_from_ldap(name)
236
+ if (u = find_by_name(name))
237
+ return u
238
+ elsif (p = UCB::LDAP::Person.find_by_uid(name)).nil?
239
+ msg = "User not found in LDAP: #{name}"
240
+ logger.debug(msg)
241
+ raise(LdapPersonNotFound, msg)
242
+ else
243
+ self.new({
244
+ :name => p.uid.to_s,
245
+ :fullname => "#{p.first_name} + #{p.last_name}",
246
+ :email => p.email || "test@berkeley.edu"
247
+ })
248
+ end
249
+ end
250
+
251
+ ##
252
+ # Retrieves all users where their accoutns have been disabled.
253
+ #
254
+ # @return [Array<Confluence::User>]
255
+ #
256
+ def expired()
257
+ self.all.select { |u| u[:fullname].include?("ACCOUNT DISABLED") }
258
+ end
259
+
260
+ ##
261
+ # Retrieves all users where their accounts are currently enabled.
262
+ #
263
+ # @return [Array<Confluence::User>]
264
+ #
265
+ def active()
266
+ self.all.reject { |u| u[:fullname].include?("ACCOUNT DISABLED") }
267
+ end
268
+
269
+ ##
270
+ # Returns a list of all Confluence user names.
271
+ #
272
+ # @return [Array<String>] where each entry is the user's name
273
+ # in Confluence.
274
+ #
275
+ def all_names()
276
+ conn.getActiveUsers(true)
277
+ end
278
+
279
+ ##
280
+ # Retrieves all users, both expired and active.
281
+ #
282
+ # @return [Array<Confluence::User>]
283
+ #
284
+ def all()
285
+ all_names.map { |name| find_by_name(name) }
286
+ end
287
+
288
+ ##
289
+ # Finds a given Confluence user by their username.
290
+ #
291
+ # @param [String] the username.
292
+ # @return [Confluence::User, nil] the found record, otherwise returns
293
+ # nil.
294
+ #
295
+ def find_by_name(name)
296
+ begin
297
+ u = self.new(conn.getUser(name.to_s))
298
+ u.instance_variable_set(:@new_record, false)
299
+ u
300
+ rescue(RuntimeError) => e
301
+ logger.debug(e.message)
302
+ return nil
303
+ end
304
+ end
305
+
306
+ def exists?(name)
307
+ conn.hasUser(name)
308
+ end
309
+ end
310
+ end
311
+ end
312
+