ucb_confluence 0.0.2

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,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
+