rally_user_management 0.5.5
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.
- checksums.yaml +15 -0
- data/lib/multi_io.rb +19 -0
- data/lib/rally_user_helper.rb +1937 -0
- data/lib/rally_user_management.rb +11 -0
- data/lib/version.rb +34 -0
- metadata +47 -0
@@ -0,0 +1,1937 @@
|
|
1
|
+
# Copyright (c) 2014 Rally Software Development
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module RallyUserManagement
|
22
|
+
|
23
|
+
require 'rally_api'
|
24
|
+
require 'pp'
|
25
|
+
require 'date'
|
26
|
+
|
27
|
+
class UserHelper
|
28
|
+
|
29
|
+
#Setup constants
|
30
|
+
ADMIN = 'Admin'
|
31
|
+
USER = 'User'
|
32
|
+
# Different READ and CREATE attributes are imposed by WSAPI
|
33
|
+
# Hopefully this is a temporary hack
|
34
|
+
PROJECTADMIN_READ = ADMIN
|
35
|
+
PROJECTADMIN_CREATE = 'Project Admin'
|
36
|
+
EDITOR = 'Editor'
|
37
|
+
VIEWER = 'Viewer'
|
38
|
+
NOACCESS = 'No Access'
|
39
|
+
TEAMMEMBER_YES = 'Yes'
|
40
|
+
TEAMMEMBER_NO = 'No'
|
41
|
+
|
42
|
+
# Parameters for cache files
|
43
|
+
SUB_CACHE = "_cached_subscription.txt"
|
44
|
+
WORKSPACE_CACHE = "_cached_workspaces.txt"
|
45
|
+
PROJECT_CACHE = "_cached_projects.txt"
|
46
|
+
CACHE_COL_DELIM = "\t"
|
47
|
+
CACHE_ROW_DELIM = "\n"
|
48
|
+
CACHE_WRITE_MODE = "wb"
|
49
|
+
|
50
|
+
SUB_CACHE_FIELDS = %w{SubscriptionID Name}
|
51
|
+
WORKSPACE_CACHE_FIELDS = %w{ObjectID Name State}
|
52
|
+
PROJECT_CACHE_FIELDS = %w{ObjectID ProjectName State WorkspaceName WorkspaceOID}
|
53
|
+
|
54
|
+
def initialize(config)
|
55
|
+
|
56
|
+
@rally = config[:rally_api]
|
57
|
+
@rally_json_connection = @rally.rally_connection
|
58
|
+
@logger = config[:logger]
|
59
|
+
@create_flag = config[:create_flag]
|
60
|
+
@cached_users = {}
|
61
|
+
@cached_sub_id = 0
|
62
|
+
@cached_subscription = {}
|
63
|
+
@cached_workspaces = {}
|
64
|
+
@cached_projects = {}
|
65
|
+
@cached_workspaces_by_name = {}
|
66
|
+
@cached_projects_by_name = {}
|
67
|
+
|
68
|
+
# Provides lookup of projects per workspace
|
69
|
+
@workspace_hash_of_projects = {}
|
70
|
+
|
71
|
+
# Maximum age in days of the Workspace/Project cache before refresh
|
72
|
+
@max_cache_age = config[:max_cache_age] || 1
|
73
|
+
|
74
|
+
# upgrade_only_mode - when running in upgrade_only_mode, check existing permissions
|
75
|
+
# first, and only apply the change if it represents an upgrade over existing permissions
|
76
|
+
@upgrade_only_mode = config[:upgrade_only_mode] || false
|
77
|
+
|
78
|
+
# File encoding format
|
79
|
+
@file_encoding = config[:file_encoding] || "US-ASCII"
|
80
|
+
|
81
|
+
# User filter for ENABLED users only
|
82
|
+
# For purposes of speed/efficiency, summarize Enabled Users ONLY
|
83
|
+
@summarize_enabled_only = true
|
84
|
+
@enabled_only_filter = "(Disabled = \"False\")"
|
85
|
+
|
86
|
+
# fetch data
|
87
|
+
@initial_user_fetch = "UserName,FirstName,LastName,DisplayName"
|
88
|
+
@detail_user_fetch = "UserName,FirstName,LastName,DisplayName,UserPermissions,Name,Role,Workspace,ObjectID,Project,ObjectID,TeamMemberships,UserProfile"
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_cached_users()
|
93
|
+
return @cached_users
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_cached_workspaces()
|
97
|
+
return @cached_workspaces
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_cached_projects()
|
101
|
+
return @cached_projects
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_workspace_project_hash()
|
105
|
+
return @workspace_hash_of_projects
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_cached_workspaces_by_nane()
|
109
|
+
return @cached_workspaces_by_name
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_cached_projects_by_name()
|
113
|
+
return @cached_projects_by_name
|
114
|
+
end
|
115
|
+
|
116
|
+
# Helper methods
|
117
|
+
# Does the user exist? If so, return the user, if not return nil
|
118
|
+
# Need to downcase the name since user names are downcased when created. Without downcase, we would not be
|
119
|
+
# able to find 'Mark@acme.com'
|
120
|
+
def find_user(name)
|
121
|
+
if ( name.downcase != name )
|
122
|
+
@logger.info "Looking for #{name.downcase} instead of #{name}"
|
123
|
+
end
|
124
|
+
|
125
|
+
if @cached_users.has_key?(name.downcase)
|
126
|
+
return @cached_users[name.downcase]
|
127
|
+
end
|
128
|
+
|
129
|
+
single_user_query = RallyAPI::RallyQuery.new()
|
130
|
+
single_user_query.type = :user
|
131
|
+
single_user_query.fetch = @detail_user_fetch
|
132
|
+
single_user_query.page_size = 200 #optional - default is 200
|
133
|
+
single_user_query.limit = 90000 #optional - default is 99999
|
134
|
+
single_user_query.order = "UserName Asc"
|
135
|
+
single_user_query.query_string = "(UserName = \"" + name + "\")"
|
136
|
+
|
137
|
+
query_results = @rally.find(single_user_query)
|
138
|
+
|
139
|
+
if query_results.total_result_count == 0
|
140
|
+
return nil
|
141
|
+
else
|
142
|
+
# Cache user for use next time
|
143
|
+
this_user = query_results.first
|
144
|
+
@cached_users[this_user["UserName"].downcase] = this_user
|
145
|
+
@logger.info "Caching User: #{this_user.UserName}"
|
146
|
+
|
147
|
+
return this_user
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Needed to refresh user object after updates, since user cache is stale after
|
152
|
+
# user settings are changed by the helper
|
153
|
+
def refresh_user(name)
|
154
|
+
single_user_query = RallyAPI::RallyQuery.new()
|
155
|
+
single_user_query.type = :user
|
156
|
+
single_user_query.fetch = @detail_user_fetch
|
157
|
+
single_user_query.page_size = 200 #optional - default is 200
|
158
|
+
single_user_query.limit = 90000 #optional - default is 99999
|
159
|
+
single_user_query.order = "UserName Asc"
|
160
|
+
single_user_query.query_string = "(UserName = \"" + name + "\")"
|
161
|
+
|
162
|
+
query_results = @rally.find(single_user_query)
|
163
|
+
if query_results.total_result_count == 0
|
164
|
+
return nil
|
165
|
+
else
|
166
|
+
# Cache user for use next time
|
167
|
+
this_user = query_results.first
|
168
|
+
@cached_users[this_user["UserName"].downcase] = this_user
|
169
|
+
@logger.info "Refreshed User: #{this_user.UserName}"
|
170
|
+
|
171
|
+
return this_user
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#==================== Get a list of OPEN projects in Workspace ========================
|
176
|
+
#
|
177
|
+
def get_open_projects (input_workspace)
|
178
|
+
project_query = RallyAPI::RallyQuery.new()
|
179
|
+
project_query.workspace = input_workspace
|
180
|
+
project_query.project = nil
|
181
|
+
project_query.project_scope_up = true
|
182
|
+
project_query.project_scope_down = true
|
183
|
+
project_query.type = :project
|
184
|
+
project_query.fetch = "Name,State,ObjectID,Workspace,ObjectID"
|
185
|
+
project_query.query_string = "(State = \"Open\")"
|
186
|
+
|
187
|
+
begin
|
188
|
+
open_projects = @rally.find(project_query)
|
189
|
+
rescue Exception => ex
|
190
|
+
open_projects = nil
|
191
|
+
end
|
192
|
+
return (open_projects)
|
193
|
+
end
|
194
|
+
|
195
|
+
#added for performance
|
196
|
+
def cache_users()
|
197
|
+
|
198
|
+
user_query = RallyAPI::RallyQuery.new()
|
199
|
+
user_query.type = :user
|
200
|
+
user_query.fetch = @initial_user_fetch
|
201
|
+
user_query.page_size = 200 #optional - default is 200
|
202
|
+
user_query.limit = 90000 #optional - default is 99999
|
203
|
+
user_query.order = "UserName Asc"
|
204
|
+
|
205
|
+
# Filter for enabled only
|
206
|
+
if @summarize_enabled_only then
|
207
|
+
user_query.query_string = @enabled_only_filter
|
208
|
+
number_found_suffix = "Enabled Users."
|
209
|
+
else
|
210
|
+
number_found_suffix = "Users."
|
211
|
+
end
|
212
|
+
|
213
|
+
initial_query_results = @rally.find(user_query)
|
214
|
+
|
215
|
+
number_users = initial_query_results.total_result_count
|
216
|
+
count = 1
|
217
|
+
notify_increment = 25
|
218
|
+
@cached_users = {}
|
219
|
+
initial_query_results.each do | initial_user |
|
220
|
+
notify_remainder=count%notify_increment
|
221
|
+
if notify_remainder==0 then @logger.info "Cached #{count} of #{number_users} #{number_found_suffix}" end
|
222
|
+
|
223
|
+
# Follow-up user-by-user query of Rally for Detailed User Properties
|
224
|
+
user_query.fetch = @detail_user_fetch
|
225
|
+
|
226
|
+
# Setup query parameters for Rally query of detailed user info
|
227
|
+
this_user_name = initial_user["UserName"]
|
228
|
+
query_string = "(UserName = \"#{this_user_name}\")"
|
229
|
+
user_query.query_string = query_string
|
230
|
+
|
231
|
+
# Query Rally for single-user detailed info, including Permissions, Projects, and
|
232
|
+
# Team Memberships
|
233
|
+
detail_user_query_results = @rally.find(user_query)
|
234
|
+
|
235
|
+
# If found, cache the user
|
236
|
+
number_found = detail_user_query_results.total_result_count
|
237
|
+
if number_found > 0 then
|
238
|
+
this_user = detail_user_query_results.first
|
239
|
+
@cached_users[this_user.UserName] = this_user
|
240
|
+
count+=1
|
241
|
+
else
|
242
|
+
@logger.warn "User: #{this_user_name} not found in follow-up query. Skipping..."
|
243
|
+
next
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def find_workspace(object_id)
|
250
|
+
if @cached_workspaces.has_key?(object_id)
|
251
|
+
# Found workspace in cache, return the cached workspace
|
252
|
+
return @cached_workspaces[object_id]
|
253
|
+
else
|
254
|
+
# workspace not found in cache - go to Rally
|
255
|
+
workspace_query = RallyAPI::RallyQuery.new()
|
256
|
+
workspace_query.project = nil
|
257
|
+
workspace_query.type = :workspace
|
258
|
+
workspace_query.fetch = "Name,State,ObjectID"
|
259
|
+
workspace_query.query_string = "((ObjectID = \"#{object_id}\") AND (State = \"Open\"))"
|
260
|
+
|
261
|
+
workspace_results = @rally.find(workspace_query)
|
262
|
+
|
263
|
+
if workspace_results.total_result_count != 0 then
|
264
|
+
# Workspace found via Rally query, return it
|
265
|
+
workspace = workspace_results.first()
|
266
|
+
|
267
|
+
# Cache it for use next time
|
268
|
+
@cached_workspaces[workspace["ObjectID"]] = workspace
|
269
|
+
@logger.info "Caching Workspace: #{workspace['Name']}"
|
270
|
+
|
271
|
+
# Return workspace object
|
272
|
+
return workspace
|
273
|
+
else
|
274
|
+
# Workspace not found in Rally _or_ cache - return Nil
|
275
|
+
@logger.warn "Rally Workspace: #{object_id} not found"
|
276
|
+
return nil
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def find_workspace_by_name(workspace_name)
|
282
|
+
if @cached_workspaces_by_name.has_key?(workspace_name)
|
283
|
+
# Found workspace in cache, return the cached workspace
|
284
|
+
return @cached_workspaces_by_name[workspace_name]
|
285
|
+
else
|
286
|
+
# workspace not found in cache - go to Rally
|
287
|
+
workspace_query = RallyAPI::RallyQuery.new()
|
288
|
+
workspace_query.project = nil
|
289
|
+
workspace_query.type = :workspace
|
290
|
+
workspace_query.fetch = "Name,State,ObjectID"
|
291
|
+
workspace_query.query_string = "((Name = \"#{workspace_name}\") AND (State = \"Open\"))"
|
292
|
+
|
293
|
+
workspace_results = @rally.find(workspace_query)
|
294
|
+
|
295
|
+
if workspace_results.total_result_count != 0 then
|
296
|
+
# Workspace found via Rally query, return it
|
297
|
+
workspace = workspace_results.first()
|
298
|
+
|
299
|
+
# Cache it for use next time
|
300
|
+
@cached_workspaces_by_name[workspace["Name"]] = workspace
|
301
|
+
@logger.info "Caching Workspace: #{workspace['Name']}"
|
302
|
+
|
303
|
+
if workspace_results.total_result_count > 1 then
|
304
|
+
# More than one Workspace matching this name found
|
305
|
+
@logger.warn "More than one Workspace of name: #{workspace_name} found."
|
306
|
+
@logger.warn "Returning only the first instance found!"
|
307
|
+
end
|
308
|
+
|
309
|
+
# Return workspace object
|
310
|
+
return workspace
|
311
|
+
else
|
312
|
+
# Workspace not found in Rally _or_ cache - return Nil
|
313
|
+
@logger.warn "Rally Workspace: #{workspace_name} not found"
|
314
|
+
return nil
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def find_project_by_name(project_name)
|
320
|
+
if @cached_projects_by_name.has_key?(project_name)
|
321
|
+
# Found workspace in cache, return the cached workspace
|
322
|
+
return @cached_projects_by_name[project_name]
|
323
|
+
else
|
324
|
+
# workspace not found in cache - go to Rally
|
325
|
+
project_query = RallyAPI::RallyQuery.new()
|
326
|
+
project_query.project = nil
|
327
|
+
project_query.type = :workspace
|
328
|
+
project_query.fetch = "Name,State,ObjectID"
|
329
|
+
project_query.query_string = "((Name = \"#{project_name}\") AND (State = \"Open\"))"
|
330
|
+
|
331
|
+
project_results = @rally.find(project_query)
|
332
|
+
|
333
|
+
if project_results.total_result_count != 0 then
|
334
|
+
# Workspace found via Rally query, return it
|
335
|
+
project = project_results.first()
|
336
|
+
|
337
|
+
# Cache it for use next time
|
338
|
+
@cached_projects_by_name[project["Name"]] = project
|
339
|
+
@logger.info "Caching Project: #{project['Name']}"
|
340
|
+
|
341
|
+
if project_results.total_result_count > 1 then
|
342
|
+
# More than one Project matching this name found
|
343
|
+
@logger.warn "More than one Project of name: #{project_name} found."
|
344
|
+
@logger.warn "Returning only the first instance found!"
|
345
|
+
end
|
346
|
+
|
347
|
+
# Return project object
|
348
|
+
return project
|
349
|
+
else
|
350
|
+
# Project not found in Rally _or_ cache - return Nil
|
351
|
+
@logger.warn "Rally Project: #{project_name} not found"
|
352
|
+
return nil
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def find_project(object_id)
|
358
|
+
if @cached_projects.has_key?(object_id)
|
359
|
+
# Found project in cache, return the cached project
|
360
|
+
return @cached_projects[object_id]
|
361
|
+
else
|
362
|
+
# project not found in cache - go to Rally
|
363
|
+
project_query = RallyAPI::RallyQuery.new()
|
364
|
+
project_query.type = :project
|
365
|
+
project_query.fetch = "Name,State,ObjectID,Workspace,ObjectID"
|
366
|
+
project_query.query_string = "((ObjectID = \"#{object_id}\") AND (State = \"Open\"))"
|
367
|
+
|
368
|
+
project_results = @rally.find(project_query)
|
369
|
+
|
370
|
+
if project_results.total_result_count != 0 then
|
371
|
+
# Project found via Rally query, return it
|
372
|
+
project = project_results.first()
|
373
|
+
|
374
|
+
# Cache it for use next time
|
375
|
+
@cached_projects[project["ObjectID"]] = project
|
376
|
+
@logger.info "Caching Project: #{project['Name']}"
|
377
|
+
|
378
|
+
# Return it
|
379
|
+
return project
|
380
|
+
else
|
381
|
+
# Project not found in Rally _or_ cache - return Nil
|
382
|
+
@logger.warn "Rally Project: #{object_id} not found"
|
383
|
+
return nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Check to see if a project is within a particular workspace.
|
389
|
+
def is_project_in_workspace(project, workspace)
|
390
|
+
test_project_oid = project["ObjectID"]
|
391
|
+
this_workspace_oid = workspace["ObjectID"]
|
392
|
+
|
393
|
+
object_id_matches = false
|
394
|
+
these_projects = @workspace_hash_of_projects[this_workspace_oid]
|
395
|
+
these_projects.each do | this_project |
|
396
|
+
this_project_oid = this_project["ObjectID"]
|
397
|
+
if test_project_oid == this_project_oid then
|
398
|
+
object_id_matches = true
|
399
|
+
end
|
400
|
+
end
|
401
|
+
return object_id_matches
|
402
|
+
end
|
403
|
+
|
404
|
+
# Get current SubID
|
405
|
+
def get_current_sub_id()
|
406
|
+
|
407
|
+
subscription_query = RallyAPI::RallyQuery.new()
|
408
|
+
subscription_query.type = :subscription
|
409
|
+
subscription_query.fetch = "Name,SubscriptionID"
|
410
|
+
subscription_query.page_size = 200 #optional - default is 200
|
411
|
+
subscription_query.limit = 50000 #optional - default is 99999
|
412
|
+
subscription_query.order = "Name Asc"
|
413
|
+
|
414
|
+
results = @rally.find(subscription_query)
|
415
|
+
|
416
|
+
this_subscription = results.first
|
417
|
+
this_sub_id = this_subscription["SubscriptionID"]
|
418
|
+
|
419
|
+
return this_sub_id
|
420
|
+
end
|
421
|
+
|
422
|
+
# Given the name of a file, calculates age of that file
|
423
|
+
def calc_file_age(filename)
|
424
|
+
today = Time.now
|
425
|
+
|
426
|
+
# Return really big number to prompt refresh if files not found
|
427
|
+
file_age = 10000
|
428
|
+
|
429
|
+
if !FileTest.exist?(filename) then
|
430
|
+
# Maintain "really big" age and return
|
431
|
+
return file_age
|
432
|
+
end
|
433
|
+
|
434
|
+
file_reference = File.new(filename, "r")
|
435
|
+
# Round fractional days up
|
436
|
+
file_age = ((today - file_reference.mtime)/86400).ceil
|
437
|
+
return file_age
|
438
|
+
end
|
439
|
+
|
440
|
+
# Determine cache age
|
441
|
+
def get_cache_age()
|
442
|
+
|
443
|
+
subscription_cache_filename = File.dirname(__FILE__) + "/" + SUB_CACHE
|
444
|
+
workspace_cache_filename = File.dirname(__FILE__) + "/" + WORKSPACE_CACHE
|
445
|
+
project_cache_filename = File.dirname(__FILE__) + "/" + PROJECT_CACHE
|
446
|
+
|
447
|
+
# Default to really big number to prompt refresh if files not found
|
448
|
+
cache_age = 10000
|
449
|
+
|
450
|
+
if !FileTest.exist?(subscription_cache_filename) ||
|
451
|
+
!FileTest.exist?(workspace_cache_filename) ||
|
452
|
+
!FileTest.exist?(project_cache_filename) then
|
453
|
+
# Maintain "really big" age and return
|
454
|
+
return cache_age
|
455
|
+
end
|
456
|
+
|
457
|
+
age_array = [
|
458
|
+
calc_file_age(subscription_cache_filename),
|
459
|
+
calc_file_age(workspace_cache_filename),
|
460
|
+
calc_file_age(project_cache_filename)
|
461
|
+
]
|
462
|
+
min_age = age_array.min
|
463
|
+
|
464
|
+
# return the age of the youngest cache file (should all be the same though)
|
465
|
+
cache_age = min_age
|
466
|
+
return cache_age
|
467
|
+
end
|
468
|
+
|
469
|
+
# Determine whether or not to refresh local sub/workspace/project cache
|
470
|
+
def cache_refresh_needed()
|
471
|
+
|
472
|
+
refresh_needed = false
|
473
|
+
reason = ""
|
474
|
+
subscription_cache_filename = File.dirname(__FILE__) + "/" + SUB_CACHE
|
475
|
+
workspace_cache_filename = File.dirname(__FILE__) + "/" + WORKSPACE_CACHE
|
476
|
+
project_cache_filename = File.dirname(__FILE__) + "/" + PROJECT_CACHE
|
477
|
+
|
478
|
+
if !FileTest.exist?(subscription_cache_filename) ||
|
479
|
+
!FileTest.exist?(workspace_cache_filename) ||
|
480
|
+
!FileTest.exist?(project_cache_filename) then
|
481
|
+
refresh_needed = true
|
482
|
+
reason = "One or more cache files is not found."
|
483
|
+
return refresh_needed, reason
|
484
|
+
end
|
485
|
+
|
486
|
+
cache_age = get_cache_age()
|
487
|
+
if cache_age > @max_cache_age then
|
488
|
+
refresh_needed = true
|
489
|
+
reason = "Age of workspace/project cache is greater than specified max of #{@max_cache_age}"
|
490
|
+
return refresh_needed, reason
|
491
|
+
end
|
492
|
+
|
493
|
+
read_subscription_cache()
|
494
|
+
cached_subscription_id = @cached_sub_id
|
495
|
+
current_subscription_id = get_current_sub_id()
|
496
|
+
|
497
|
+
if current_subscription_id != cached_subscription_id then
|
498
|
+
refresh_needed = true
|
499
|
+
reason = "Specified SubID: #{current_subscription_id} is different from cached SubID: #{cached_subscription_id}"
|
500
|
+
return refresh_needed, reason
|
501
|
+
end
|
502
|
+
|
503
|
+
# If we've fallen through to here, no refresh is needed
|
504
|
+
reason = "No workspace/project cache refresh currently required"
|
505
|
+
return refresh_needed, reason
|
506
|
+
end
|
507
|
+
|
508
|
+
# Create ref from oid
|
509
|
+
def make_ref_from_oid(object_type, object_id)
|
510
|
+
return "/#{object_type}/#{object_id}"
|
511
|
+
end
|
512
|
+
|
513
|
+
# Add row to subscription cache
|
514
|
+
def cache_subscription_entry(header, row)
|
515
|
+
subscription_id = row[header[0]].strip
|
516
|
+
subscription_name = row[header[1]].strip
|
517
|
+
|
518
|
+
this_subscription = {}
|
519
|
+
this_subscription["SubscriptionID"] = subscription_id
|
520
|
+
this_subscription["Name"] = subscription_name
|
521
|
+
|
522
|
+
@cached_sub_id = subscription_id.to_i
|
523
|
+
@cached_subscription[subscription_id] = this_subscription
|
524
|
+
end
|
525
|
+
|
526
|
+
# Read subscription cache
|
527
|
+
def read_subscription_cache()
|
528
|
+
|
529
|
+
subscription_cache_filename = File.dirname(__FILE__) + "/" + SUB_CACHE
|
530
|
+
@logger.info "Started reading subscription cache from #{subscription_cache_filename}"
|
531
|
+
|
532
|
+
# Read in Subscription cache items (should be only 1)
|
533
|
+
subscription_cache_input = CSV.read(subscription_cache_filename, {:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding})
|
534
|
+
|
535
|
+
header = subscription_cache_input.first #ignores first line
|
536
|
+
|
537
|
+
rows = []
|
538
|
+
(1...subscription_cache_input.size).each { |i| rows << CSV::Row.new(header, subscription_cache_input[i]) }
|
539
|
+
|
540
|
+
number_processed = 0
|
541
|
+
|
542
|
+
rows.each do |row|
|
543
|
+
if !row.nil? then
|
544
|
+
cache_subscription_entry(header, row)
|
545
|
+
number_processed += 1
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
@logger.info "Completed reading subscription cache from #{subscription_cache_filename}"
|
550
|
+
end
|
551
|
+
|
552
|
+
# Add row to workspace cache
|
553
|
+
def cache_workspace_entry(header, row)
|
554
|
+
workspace_id = row[header[0]].strip
|
555
|
+
workspace_name = row[header[1]].strip
|
556
|
+
workspace_state = row[header[2]].strip
|
557
|
+
|
558
|
+
this_workspace = {}
|
559
|
+
this_workspace["ObjectID"] = workspace_id
|
560
|
+
this_workspace["Name"] = workspace_name
|
561
|
+
this_workspace["State"] = workspace_state
|
562
|
+
this_workspace["_ref"] = make_ref_from_oid("workspace", workspace_id)
|
563
|
+
|
564
|
+
@cached_workspaces[workspace_id] = this_workspace
|
565
|
+
if !@cached_workspaces_by_name.has_key?(workspace_name) then
|
566
|
+
@cached_workspaces_by_name[workspace_name] = this_workspace
|
567
|
+
else
|
568
|
+
@logger.warn " Warning caching Workspace by Name: #{workspace_name}. Duplicate name found!"
|
569
|
+
@logger.warn " Only first instance of this Workspace name will be cached."
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# Read workspace cache
|
574
|
+
def read_workspace_cache()
|
575
|
+
|
576
|
+
workspace_cache_filename = File.dirname(__FILE__) + "/" + WORKSPACE_CACHE
|
577
|
+
@logger.info "Started reading workspace cache from #{workspace_cache_filename}"
|
578
|
+
|
579
|
+
# Read in workspace cache items (should be only 1)
|
580
|
+
workspace_cache_input = CSV.read(workspace_cache_filename, {:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding})
|
581
|
+
|
582
|
+
header = workspace_cache_input.first #ignores first line
|
583
|
+
|
584
|
+
rows = []
|
585
|
+
(1...workspace_cache_input.size).each { |i| rows << CSV::Row.new(header, workspace_cache_input[i]) }
|
586
|
+
|
587
|
+
number_processed = 0
|
588
|
+
|
589
|
+
rows.each do |row|
|
590
|
+
if !row.nil? then
|
591
|
+
cache_workspace_entry(header, row)
|
592
|
+
number_processed += 1
|
593
|
+
end
|
594
|
+
end
|
595
|
+
@logger.info "Completed reading workspace cache from #{workspace_cache_filename}"
|
596
|
+
@logger.info "Read and cached a total of #{number_processed} workspaces from local cache file."
|
597
|
+
end
|
598
|
+
|
599
|
+
def cache_project_entry(header, row)
|
600
|
+
project_id = row[header[0]].strip
|
601
|
+
project_name = row[header[1]].strip
|
602
|
+
project_state = row[header[2]].strip
|
603
|
+
project_workspace_name = row[header[3]].strip
|
604
|
+
project_workspace_oid = row[header[4]].strip
|
605
|
+
|
606
|
+
this_project = {}
|
607
|
+
this_project["ObjectID"] = project_id
|
608
|
+
this_project["Name"] = project_name
|
609
|
+
this_project["State"] = project_state
|
610
|
+
this_project["_ref"] = make_ref_from_oid("project", project_id)
|
611
|
+
this_workspace = {}
|
612
|
+
this_workspace["Name"] = project_workspace_name
|
613
|
+
this_workspace["ObjectID"] = project_workspace_oid
|
614
|
+
this_workspace["_ref"] = make_ref_from_oid("workspace", project_workspace_oid)
|
615
|
+
this_project["Workspace"] = this_workspace
|
616
|
+
@cached_projects[project_id] = this_project
|
617
|
+
|
618
|
+
if !@cached_projects_by_name.has_key?(project_name) then
|
619
|
+
@cached_projects_by_name[project_name] = this_project
|
620
|
+
else
|
621
|
+
@logger.warn " Warning caching Project by Name: #{project_name}. Duplicate name found!"
|
622
|
+
@logger.warn " Only first instance of this Project name will be cached."
|
623
|
+
end
|
624
|
+
|
625
|
+
return this_project, project_workspace_oid
|
626
|
+
|
627
|
+
end
|
628
|
+
|
629
|
+
# Read project cache
|
630
|
+
def read_project_cache()
|
631
|
+
|
632
|
+
project_cache_filename = File.dirname(__FILE__) + "/" + PROJECT_CACHE
|
633
|
+
@logger.info "Started reading project cache from #{project_cache_filename}"
|
634
|
+
|
635
|
+
# Read in project cache items (should be only 1)
|
636
|
+
project_cache_input = CSV.read(project_cache_filename, {:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding})
|
637
|
+
|
638
|
+
header = project_cache_input.first #ignores first line
|
639
|
+
|
640
|
+
rows = []
|
641
|
+
(1...project_cache_input.size).each { |i| rows << CSV::Row.new(header, project_cache_input[i]) }
|
642
|
+
|
643
|
+
number_processed = 0
|
644
|
+
|
645
|
+
current_workspace_oid_string = "-9999"
|
646
|
+
these_projects = []
|
647
|
+
|
648
|
+
rows.each do |row|
|
649
|
+
if !row.nil? then
|
650
|
+
this_project, this_workspace_oid = cache_project_entry(header, row)
|
651
|
+
|
652
|
+
# make sure workspace OID is a string
|
653
|
+
this_workspace_oid_string = this_workspace_oid.to_s
|
654
|
+
|
655
|
+
if @workspace_hash_of_projects.has_key?(this_workspace_oid_string) then
|
656
|
+
these_projects = @workspace_hash_of_projects[this_workspace_oid_string]
|
657
|
+
else
|
658
|
+
these_projects = []
|
659
|
+
end
|
660
|
+
|
661
|
+
these_projects.push(this_project)
|
662
|
+
@workspace_hash_of_projects[this_workspace_oid_string] = these_projects
|
663
|
+
|
664
|
+
number_processed += 1
|
665
|
+
end
|
666
|
+
|
667
|
+
end
|
668
|
+
@logger.info "Completed reading project cache from #{project_cache_filename}"
|
669
|
+
@logger.info "Read and cached a total of #{number_processed} projects from local cache file."
|
670
|
+
end
|
671
|
+
|
672
|
+
# Write subscription/workspace/project cache
|
673
|
+
def write_subscription_cache()
|
674
|
+
|
675
|
+
subscription_cache_filename = File.dirname(__FILE__) + "/" + SUB_CACHE
|
676
|
+
@logger.info "Started writing subscription cache to #{subscription_cache_filename}"
|
677
|
+
|
678
|
+
# Output CSV header
|
679
|
+
subscription_csv = CSV.open(
|
680
|
+
subscription_cache_filename,
|
681
|
+
CACHE_WRITE_MODE,
|
682
|
+
{:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding}
|
683
|
+
)
|
684
|
+
subscription_csv << SUB_CACHE_FIELDS
|
685
|
+
|
686
|
+
# Output cache to file
|
687
|
+
# Record for CSV output
|
688
|
+
@cached_subscription.each_pair do | sub_id, this_subscription |
|
689
|
+
|
690
|
+
data = []
|
691
|
+
@logger.info "sub_id: #{sub_id.inspect}"
|
692
|
+
@logger.info "this_subscription: #{this_subscription.inspect}"
|
693
|
+
|
694
|
+
data << sub_id
|
695
|
+
data << this_subscription
|
696
|
+
|
697
|
+
subscription_csv << CSV::Row.new(SUB_CACHE_FIELDS, data)
|
698
|
+
end
|
699
|
+
|
700
|
+
@logger.info "Finished writing subscription cache to #{subscription_cache_filename}"
|
701
|
+
end
|
702
|
+
|
703
|
+
def write_workspace_cache()
|
704
|
+
workspace_cache_filename = File.dirname(__FILE__) + "/" + WORKSPACE_CACHE
|
705
|
+
@logger.info "Started writing workspace cache to #{workspace_cache_filename}"
|
706
|
+
|
707
|
+
# Output CSV header
|
708
|
+
workspace_csv = CSV.open(
|
709
|
+
workspace_cache_filename,
|
710
|
+
CACHE_WRITE_MODE,
|
711
|
+
{:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding}
|
712
|
+
)
|
713
|
+
workspace_csv << WORKSPACE_CACHE_FIELDS
|
714
|
+
|
715
|
+
# Output cache to file
|
716
|
+
# Record for CSV output
|
717
|
+
@cached_workspaces.each_pair do | workspace_id, this_workspace |
|
718
|
+
|
719
|
+
data = []
|
720
|
+
|
721
|
+
workspace_id = this_workspace["ObjectID"]
|
722
|
+
workspace_name = this_workspace["Name"]
|
723
|
+
workspace_state = this_workspace["State"]
|
724
|
+
|
725
|
+
data << workspace_id
|
726
|
+
data << workspace_name
|
727
|
+
data << workspace_state
|
728
|
+
|
729
|
+
workspace_csv << CSV::Row.new(WORKSPACE_CACHE_FIELDS, data)
|
730
|
+
end
|
731
|
+
|
732
|
+
@logger.info "Finished writing workspace cache to #{workspace_cache_filename}"
|
733
|
+
end
|
734
|
+
|
735
|
+
def write_project_cache()
|
736
|
+
project_cache_filename = File.dirname(__FILE__) + "/" + PROJECT_CACHE
|
737
|
+
@logger.info "Started writing project cache to #{project_cache_filename}"
|
738
|
+
|
739
|
+
# The following results in an array of two-element arrays. The first element of each 2-element array
|
740
|
+
# is the ProjectOID, or the key for the project hash. The second element is the value part of the hash
|
741
|
+
projects_sorted_by_workspace = @cached_projects.sort_by {|key, value| value["WorkspaceOIDNumeric"]}
|
742
|
+
|
743
|
+
# Output CSV header
|
744
|
+
project_csv = CSV.open(
|
745
|
+
project_cache_filename,
|
746
|
+
CACHE_WRITE_MODE,
|
747
|
+
{:col_sep => CACHE_COL_DELIM, :encoding => @file_encoding}
|
748
|
+
)
|
749
|
+
project_csv << PROJECT_CACHE_FIELDS
|
750
|
+
|
751
|
+
# Output cache to file
|
752
|
+
# Record for CSV output
|
753
|
+
projects_sorted_by_workspace.each do | project_element |
|
754
|
+
|
755
|
+
this_project = project_element[1]
|
756
|
+
|
757
|
+
data = []
|
758
|
+
|
759
|
+
project_id = this_project["ObjectID"]
|
760
|
+
project_name = this_project["Name"]
|
761
|
+
project_state = this_project["State"]
|
762
|
+
project_workspace = this_project["Workspace"]
|
763
|
+
workspace_name = project_workspace["Name"]
|
764
|
+
workspace_oid = project_workspace["ObjectID"]
|
765
|
+
|
766
|
+
data << project_id
|
767
|
+
data << project_name
|
768
|
+
data << project_state
|
769
|
+
data << workspace_name
|
770
|
+
data << workspace_oid
|
771
|
+
|
772
|
+
project_csv << CSV::Row.new(PROJECT_CACHE_FIELDS, data)
|
773
|
+
end
|
774
|
+
|
775
|
+
@logger.info "Finished writing project cache to #{project_cache_filename}"
|
776
|
+
end
|
777
|
+
|
778
|
+
def read_workspace_project_cache()
|
779
|
+
# Caches by OID
|
780
|
+
@cached_subscription = {}
|
781
|
+
@cached_workspaces = {}
|
782
|
+
@cached_projects = {}
|
783
|
+
|
784
|
+
@cached_workspaces_by_name = {}
|
785
|
+
@cached_projects_by_name = {}
|
786
|
+
|
787
|
+
read_subscription_cache()
|
788
|
+
read_workspace_cache()
|
789
|
+
read_project_cache()
|
790
|
+
end
|
791
|
+
|
792
|
+
# Added for performance
|
793
|
+
def cache_workspaces_projects()
|
794
|
+
@cached_subscription = {}
|
795
|
+
@cached_workspaces = {}
|
796
|
+
@cached_projects = {}
|
797
|
+
@workspace_hash_of_projects = {}
|
798
|
+
|
799
|
+
subscription_query = RallyAPI::RallyQuery.new()
|
800
|
+
subscription_query.type = :subscription
|
801
|
+
subscription_query.fetch = "Name,SubscriptionID,Workspaces,Name,State,ObjectID"
|
802
|
+
subscription_query.page_size = 200 #optional - default is 200
|
803
|
+
subscription_query.limit = 50000 #optional - default is 99999
|
804
|
+
subscription_query.order = "Name Asc"
|
805
|
+
|
806
|
+
results = @rally.find(subscription_query)
|
807
|
+
|
808
|
+
# pre-populate workspace hash
|
809
|
+
results.each do | this_subscription |
|
810
|
+
|
811
|
+
this_subscription_id = this_subscription["SubscriptionID"]
|
812
|
+
@cached_subscription[this_subscription_id] = this_subscription
|
813
|
+
@cached_sub_id = this_subscription_id
|
814
|
+
|
815
|
+
@logger.info "This subscription has: #{this_subscription.Workspaces.length} workspaces."
|
816
|
+
|
817
|
+
workspaces = this_subscription.Workspaces
|
818
|
+
workspaces.each do |this_workspace|
|
819
|
+
|
820
|
+
this_workspace_oid_string = this_workspace["ObjectID"].to_s
|
821
|
+
|
822
|
+
# Look for open projects within Workspace
|
823
|
+
open_projects = get_open_projects(this_workspace)
|
824
|
+
@workspace_hash_of_projects[this_workspace_oid_string] = open_projects
|
825
|
+
|
826
|
+
if this_workspace.State != "Closed" && open_projects != nil then
|
827
|
+
@logger.info "Caching Workspace: #{this_workspace['Name']}."
|
828
|
+
@cached_workspaces[this_workspace_oid_string] = this_workspace
|
829
|
+
|
830
|
+
if !@cached_workspaces_by_name.has_key?(this_workspace["Name"]) then
|
831
|
+
@cached_workspaces_by_name[this_workspace["Name"]]
|
832
|
+
else
|
833
|
+
@logger.warn " Warning caching Workspace by Name: #{this_workspace["Name"]}. Duplicate name found!"
|
834
|
+
@logger.warn " Only first instance of this Workspace name will be cached."
|
835
|
+
end
|
836
|
+
|
837
|
+
@logger.info "Workspace: #{this_workspace['Name']} has: #{open_projects.length} open projects."
|
838
|
+
|
839
|
+
# Loop through open projects and Cache
|
840
|
+
open_projects.each do | this_project |
|
841
|
+
this_project["WorkspaceOIDNumeric"] = this_workspace["ObjectID"]
|
842
|
+
@cached_projects[this_project.ObjectID.to_s] = this_project
|
843
|
+
|
844
|
+
if !@cached_projects_by_name.has_key?(this_project["Name"]) then
|
845
|
+
@cached_projects_by_name[this_project["Name"]]
|
846
|
+
else
|
847
|
+
@logger.warn " Warning caching Project by Name: #{this_project["Name"]}. Duplicate name found!"
|
848
|
+
@logger.warn " Only first instance of this Project name will be cached."
|
849
|
+
end
|
850
|
+
end
|
851
|
+
else
|
852
|
+
@logger.warn "Workspace: #{this_workspace['Name']} is closed or has no open projects. Not added to cache."
|
853
|
+
end
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
write_subscription_cache()
|
858
|
+
write_workspace_cache()
|
859
|
+
write_project_cache()
|
860
|
+
end
|
861
|
+
|
862
|
+
# Mirrors ProjectPermission set from a source user to a target user
|
863
|
+
# Note that creation of a ProjectPermission will automatically create
|
864
|
+
# the minimum needed WorkspacePermission for the "owning" Workspace,
|
865
|
+
# if a WorkspacePermission does not already exist. Thus there's
|
866
|
+
# no need for a sync_workspace_permissions method
|
867
|
+
def sync_project_permissions(source_user_id, target_user_id)
|
868
|
+
source_user = find_user(source_user_id)
|
869
|
+
target_user = find_user(target_user_id)
|
870
|
+
if source_user.nil? then
|
871
|
+
@logger.warn " Source user: #{source_user_id} Not found. Skipping sync of permissions to #{target_user_id}."
|
872
|
+
return
|
873
|
+
elsif target_user.nil then
|
874
|
+
@logger.warn " Target user: #{target_user_id} Not found. Skipping sync of permissions for #{target_user_id}."
|
875
|
+
end
|
876
|
+
|
877
|
+
permissions_existing = target_user.UserPermissions
|
878
|
+
source_permissions = source_user.UserPermissions
|
879
|
+
|
880
|
+
# build permission hashes by Project ObjectID
|
881
|
+
source_permissions_by_project = {}
|
882
|
+
source_permissions.each do | this_source_permission |
|
883
|
+
if this_source_permission._type == "ProjectPermission" then
|
884
|
+
source_permissions_by_project[this_source_permission.Project.ObjectID.to_s] = this_source_permission
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
permissions_existing_by_project = {}
|
889
|
+
permissions_existing.each do | this_permission |
|
890
|
+
if this_permission._type == "ProjectPermission" then
|
891
|
+
permissions_existing_by_project[this_permission.Project.ObjectID.to_s] = this_permission
|
892
|
+
end
|
893
|
+
end
|
894
|
+
|
895
|
+
# Prepare arrays of permissions to update, create, or delete
|
896
|
+
permissions_to_update = []
|
897
|
+
permissions_to_create = []
|
898
|
+
permissions_to_delete = []
|
899
|
+
|
900
|
+
# Check target permissions list for permissions to create and/or update
|
901
|
+
source_permissions_by_project.each_pair do | this_source_project_oid, this_source_permission |
|
902
|
+
|
903
|
+
# If target hash doesn't contain the OID referenced in the source permission set, it's a new
|
904
|
+
# permission we need to create
|
905
|
+
if !permissions_existing_by_project.has_key?(this_source_project_oid) then
|
906
|
+
permissions_to_create.push(this_source_permission)
|
907
|
+
|
908
|
+
# We found the OID key, so there is an existing permission for this Project. Is it different
|
909
|
+
# from the target permission?
|
910
|
+
else
|
911
|
+
this_source_role = this_source_permission.Role
|
912
|
+
this_source_project = find_project(this_source_project_oid)
|
913
|
+
this_source_project_name = this_source_project["Name"]
|
914
|
+
|
915
|
+
if project_permissions_different?(this_source_project, target_user, this_source_role) then
|
916
|
+
existing_permission = permissions_existing_by_project[this_source_project_oid]
|
917
|
+
this_existing_project = existing_permission.Project
|
918
|
+
this_existing_project_name = this_existing_project["Name"]
|
919
|
+
this_existing_role = existing_permission.Role
|
920
|
+
@logger.info "Existing Permission: #{this_existing_project_name}: #{this_existing_role}"
|
921
|
+
@logger.info "Updated Permission: #{this_source_project_name}: #{this_source_role}"
|
922
|
+
permissions_to_update.push(this_source_permission)
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
# Loop through target permissions list and check for Project Permissions that don't exist
|
928
|
+
# in source permissions template, indicating they need to be removed
|
929
|
+
permissions_existing_by_project.each_pair do | this_existing_project_oid, this_existing_permission |
|
930
|
+
if !source_permissions_by_project.has_key?(this_existing_project_oid) then
|
931
|
+
permissions_to_delete.push(this_existing_permission)
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
# Process creates
|
936
|
+
number_new_permissions = 0
|
937
|
+
permissions_to_create.each do | this_new_permission |
|
938
|
+
this_project = find_project(this_new_permission.Project.ObjectID.to_s)
|
939
|
+
if !this_project.nil? then
|
940
|
+
|
941
|
+
this_project = this_new_permission.Project
|
942
|
+
this_workspace = this_project.Workspace
|
943
|
+
this_workspace.read
|
944
|
+
|
945
|
+
this_project_name = this_new_permission.Project.Name
|
946
|
+
this_workspace_name = this_workspace.Name
|
947
|
+
|
948
|
+
this_role = this_new_permission.Role
|
949
|
+
|
950
|
+
@logger.info "Workspace: #{this_workspace_name}"
|
951
|
+
@logger.info "Creating #{this_role} permission on #{this_project_name} from #{source_user_id} to: #{target_user_id}."
|
952
|
+
create_project_permission(target_user, this_project, this_role)
|
953
|
+
number_new_permissions += 1
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
# Process updates
|
958
|
+
number_updated_permissions = 0
|
959
|
+
permissions_to_update.each do | this_new_permission |
|
960
|
+
this_project = find_project(this_new_permission.Project.ObjectID.to_s)
|
961
|
+
if !this_project.nil? then
|
962
|
+
this_project = this_new_permission.Project
|
963
|
+
this_workspace = this_project.Workspace
|
964
|
+
this_workspace.read
|
965
|
+
|
966
|
+
this_project_name = this_new_permission.Project.Name
|
967
|
+
this_workspace_name = this_workspace.Name
|
968
|
+
|
969
|
+
this_role = this_new_permission.Role
|
970
|
+
|
971
|
+
@logger.info "Workspace: #{this_workspace_name}"
|
972
|
+
@logger.info "Updating #{this_role} permission on #{this_project_name} from #{source_user_id} to: #{target_user_id}."
|
973
|
+
create_project_permission(target_user, this_project, this_role)
|
974
|
+
number_updated_permissions += 1
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
# Process deletes
|
979
|
+
number_removed_permissions = 0
|
980
|
+
permissions_to_delete.each do | this_deleted_permission |
|
981
|
+
this_project = find_project(this_deleted_permission.Project.ObjectID.to_s)
|
982
|
+
if !this_project.nil? then
|
983
|
+
this_project = this_deleted_permission.Project
|
984
|
+
this_workspace = this_project.Workspace
|
985
|
+
this_workspace.read
|
986
|
+
|
987
|
+
this_project_name = this_deleted_permission.Project.Name
|
988
|
+
this_workspace_name = this_workspace.Name
|
989
|
+
|
990
|
+
this_role = this_deleted_permission.Role
|
991
|
+
|
992
|
+
@logger.info "Workspace: #{this_workspace_name}"
|
993
|
+
@logger.info "Removing #{this_role} permission to #{this_project_name} from #{target_user_id} since it is not present on source: #{source_user_id}."
|
994
|
+
if !@upgrade_only_mode then
|
995
|
+
delete_project_permission(target_user, this_project)
|
996
|
+
number_removed_permissions += 1
|
997
|
+
else
|
998
|
+
@logger.info " #{target_user_id} - upgrade_only_mode == true."
|
999
|
+
@logger.info " Proposed Permission removal would downgrade permissions. No permission removal applied."
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
@logger.info "#{number_new_permissions} Permissions Created; #{number_updated_permissions} Permissions Updated; #{number_removed_permissions} Permissions Removed."
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
# Mirrors team membership set from a source user to a target user
|
1007
|
+
def sync_team_memberships(source_user_id, target_user_id)
|
1008
|
+
source_user = find_user(source_user_id)
|
1009
|
+
target_user = find_user(target_user_id)
|
1010
|
+
if source_user.nil? then
|
1011
|
+
@logger.warn " Source user: #{source_user_id} Not found. Skipping sync of permissions to #{target_user_id}."
|
1012
|
+
return
|
1013
|
+
elsif target_user.nil then
|
1014
|
+
@logger.warn " Target user: #{target_user_id} Not found. Skipping sync of permissions for #{target_user_id}."
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
memberships_existing = target_user["TeamMemberships"]
|
1018
|
+
source_memberships = source_user["TeamMemberships"]
|
1019
|
+
|
1020
|
+
# build membership lists by Project ObjectID
|
1021
|
+
source_membership_oids = []
|
1022
|
+
source_memberships.each do | this_source_membership |
|
1023
|
+
source_membership_oids.push(get_membership_oid_from_membership(this_source_membership))
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
memberships_existing_oids = []
|
1027
|
+
memberships_existing.each do | this_membership |
|
1028
|
+
memberships_existing_oids.push(get_membership_oid_from_membership(this_membership))
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
# build Target User Permissions list by Project ObjectID
|
1032
|
+
# Needed to make sure that user to whom we're trying to set team membership for
|
1033
|
+
# is an editor
|
1034
|
+
permissions_existing = target_user.UserPermissions
|
1035
|
+
permissions_existing_by_project = {}
|
1036
|
+
permissions_existing.each do | this_permission |
|
1037
|
+
if this_permission._type == "ProjectPermission" then
|
1038
|
+
permissions_existing_by_project[this_permission.Project.ObjectID.to_s] = this_permission
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
memberships_to_add = []
|
1043
|
+
source_membership_oids.each do | this_membership_oid |
|
1044
|
+
if !memberships_existing_oids.include?(this_membership_oid) then
|
1045
|
+
memberships_to_add.push(this_membership_oid)
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
memberships_to_remove = []
|
1050
|
+
memberships_existing_oids.each do | this_membership_oid |
|
1051
|
+
if !source_membership_oids.include?(this_membership_oid) then
|
1052
|
+
memberships_to_remove.push(this_membership_oid)
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
number_memberships_added = 0
|
1057
|
+
memberships_to_add.each do | this_membership_oid |
|
1058
|
+
|
1059
|
+
this_permission = permissions_existing_by_project[this_membership_oid]
|
1060
|
+
if !this_permission.nil? then
|
1061
|
+
this_role = this_permission.Role
|
1062
|
+
if !this_role.eql?(EDITOR) && !this_role.eql(PROJECTADMIN) then
|
1063
|
+
@logger.warn " Target User: #{target_user_id} must be an Editor or Project Admin to make them a Team Member. Skipping."
|
1064
|
+
return
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
this_project = find_project(this_membership_oid)
|
1068
|
+
if !this_project.nil? then
|
1069
|
+
number_memberships_added += 1
|
1070
|
+
this_project_name = this_project["Name"]
|
1071
|
+
@logger.info "Updating TeamMembership on #{this_project_name} from #{source_user_id} to: #{target_user_id}."
|
1072
|
+
update_team_membership(target_user, this_membership_oid, this_project_name, TEAMMEMBER_YES)
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
number_memberships_removed = 0
|
1079
|
+
memberships_to_remove.each do | this_membership_oid |
|
1080
|
+
this_project = find_project(this_membership_oid)
|
1081
|
+
if !this_project.nil? then
|
1082
|
+
number_memberships_removed += 1
|
1083
|
+
this_project_name = this_project["Name"]
|
1084
|
+
@logger.info "Removing TeamMembership on #{this_project_name} from #{target_user_id} since source: #{source_user_id} is not a TeamMember."
|
1085
|
+
update_team_membership(target_user, this_membership_oid, this_project_name, TEAMMEMBER_NO)
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
@logger.info "Team Memberships Added: #{number_memberships_added}; Team Memberships Removed: #{number_memberships_removed}"
|
1090
|
+
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def update_workspace_permissions(workspace, user, permission, new_user)
|
1094
|
+
if new_user or workspace_permissions_different?(workspace, user, permission)
|
1095
|
+
if @upgrade_only_mode then
|
1096
|
+
is_upgrade, existing_permission = is_workspace_permission_upgrade?(workspace, user, permission)
|
1097
|
+
if is_upgrade then
|
1098
|
+
update_permission_workspacelevel(workspace, user, permission)
|
1099
|
+
else
|
1100
|
+
@logger.info " #{user["UserName"]} #{workspace["Name"]} - upgrade_only_mode == true."
|
1101
|
+
@logger.warn " Existing Permission: #{existing_permission}"
|
1102
|
+
@logger.warn " Proposed Permission: #{permission}"
|
1103
|
+
@logger.info " Proposed Permission change would downgrade permissions. No permission updates applied."
|
1104
|
+
end
|
1105
|
+
else
|
1106
|
+
update_permission_workspacelevel(workspace, user, permission)
|
1107
|
+
end
|
1108
|
+
else
|
1109
|
+
@logger.info " #{user["UserName"]} #{workspace["Name"]} - Existing/new permission same. No permission updates applied."
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
def update_project_permissions(project, user, permission, new_user)
|
1114
|
+
if new_user or project_permissions_different?(project, user, permission)
|
1115
|
+
if @upgrade_only_mode then
|
1116
|
+
is_upgrade, existing_permission = is_project_permission_upgrade?(project, user, permission)
|
1117
|
+
if is_upgrade then
|
1118
|
+
update_permission_projectlevel(project, user, permission)
|
1119
|
+
else
|
1120
|
+
@logger.warn " #{user["UserName"]} #{project["Name"]} - upgrade_only_mode == true."
|
1121
|
+
@logger.warn " Existing Permission: #{existing_permission}"
|
1122
|
+
@logger.warn " Proposed Permission: #{permission}"
|
1123
|
+
@logger.warn " Proposed Permission change would downgrade permissions. No permission updates applied."
|
1124
|
+
end
|
1125
|
+
else
|
1126
|
+
update_permission_projectlevel(project, user, permission)
|
1127
|
+
end
|
1128
|
+
else
|
1129
|
+
@logger.info " #{user["UserName"]} #{project["Name"]} - Existing/new permission same. No permission updates applied."
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
def create_user(user_name, fields = user_fields)
|
1134
|
+
|
1135
|
+
new_user_obj = {}
|
1136
|
+
|
1137
|
+
new_user_obj["UserName"] = user_name.downcase
|
1138
|
+
new_user_obj["EmailAddress"] = user_name.downcase
|
1139
|
+
|
1140
|
+
fields.each_pair do | key, value |
|
1141
|
+
new_user_obj[key] = value
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
new_user = nil
|
1145
|
+
|
1146
|
+
begin
|
1147
|
+
if @create_flag
|
1148
|
+
new_user = @rally.create(:user, new_user_obj)
|
1149
|
+
end
|
1150
|
+
@logger.info "Created Rally user #{user_name.downcase}"
|
1151
|
+
rescue
|
1152
|
+
@logger.error "Error creating user: #{$!}"
|
1153
|
+
raise $!
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# Grab full object of the created user and return so that we can use it later
|
1157
|
+
new_user_query = RallyAPI::RallyQuery.new()
|
1158
|
+
new_user_query.type = :user
|
1159
|
+
new_user_query.fetch = "UserName,FirstName,LastName,DisplayName,UserPermissions,Name,Role,Workspace,ObjectID,Project,ObjectID,TeamMemberships"
|
1160
|
+
new_user_query.query_string = "(UserName = \"#{user_name.downcase}\")"
|
1161
|
+
new_user_query.order = "UserName Asc"
|
1162
|
+
|
1163
|
+
query_results = @rally.find(new_user_query)
|
1164
|
+
new_user_created = query_results.first
|
1165
|
+
|
1166
|
+
# Cache the new user
|
1167
|
+
@cached_users[user_name.downcase] = new_user_created
|
1168
|
+
return new_user_created
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
def update_user(user, fields = user_fields)
|
1172
|
+
|
1173
|
+
user_name = user["UserName"]
|
1174
|
+
begin
|
1175
|
+
if @create_flag
|
1176
|
+
updated_user = user.update(fields)
|
1177
|
+
end
|
1178
|
+
@logger.info "Updated Rally user #{user_name.downcase}"
|
1179
|
+
rescue
|
1180
|
+
@logger.error "Error creating user: #{$!}"
|
1181
|
+
raise $!
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
# Grab full object of the created user and return so that we can use it later
|
1185
|
+
updated_user_query = RallyAPI::RallyQuery.new()
|
1186
|
+
updated_user_query.type = :user
|
1187
|
+
updated_user_query.fetch = "UserName,FirstName,LastName,DisplayName,UserPermissions,Name,Role,Workspace,ObjectID,Project,ObjectID,TeamMemberships"
|
1188
|
+
updated_user_query.query_string = "(UserName = \"#{user_name.downcase}\")"
|
1189
|
+
updated_user_query.order = "UserName Asc"
|
1190
|
+
|
1191
|
+
query_results = @rally.find(updated_user_query)
|
1192
|
+
updated_user_object = query_results.first
|
1193
|
+
|
1194
|
+
# Cache the new user
|
1195
|
+
@cached_users[user_name.downcase] = updated_user_object
|
1196
|
+
return
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
def disable_user(user)
|
1200
|
+
if user.Disabled == 'False'
|
1201
|
+
if @create_flag
|
1202
|
+
fields = {}
|
1203
|
+
fields["Disabled"] = 'False'
|
1204
|
+
updated_user = @rally.update(:user, user._ref, fields) #by ref
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
@logger.info "#{user["UserName"]} disabled in Rally"
|
1208
|
+
else
|
1209
|
+
@logger.info "#{user["UserName"]} already disabled from Rally"
|
1210
|
+
return false
|
1211
|
+
end
|
1212
|
+
return true
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def enable_user(user)
|
1216
|
+
if user.Disabled == 'True'
|
1217
|
+
fields = {}
|
1218
|
+
fields["Disabled"] = 'True'
|
1219
|
+
updated_user = @rally.update(:user, user._ref, fields) if @create_flag
|
1220
|
+
@logger.info "#{user["UserName"]} enabled in Rally"
|
1221
|
+
return true
|
1222
|
+
else
|
1223
|
+
@logger.info "#{user["UserName"]} already enabled in Rally"
|
1224
|
+
return false
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
# Checks to see if input record is using:
|
1229
|
+
# Text string (i.e. Editor, Viewer)
|
1230
|
+
# Or
|
1231
|
+
# User (i.e. john.doe@company.com)
|
1232
|
+
# as source of default permissions
|
1233
|
+
def check_default_permission_type(value)
|
1234
|
+
|
1235
|
+
type = :stringsource
|
1236
|
+
if !value.match(/@/).nil? then
|
1237
|
+
type = :usersource
|
1238
|
+
end
|
1239
|
+
return type
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# Pulls Project OID off of team membership _ref
|
1243
|
+
def get_membership_oid_from_membership(team_membership)
|
1244
|
+
this_membership_ref = team_membership._ref
|
1245
|
+
this_membership_oid = this_membership_ref.split("\/")[-1].split("\.")[0]
|
1246
|
+
return this_membership_oid
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
# Checks to see if a user is a Team Member for a particular project
|
1250
|
+
def is_team_member(project_oid, user)
|
1251
|
+
|
1252
|
+
# Default values
|
1253
|
+
is_member = false
|
1254
|
+
return_value = "No"
|
1255
|
+
|
1256
|
+
team_memberships = user["TeamMemberships"]
|
1257
|
+
|
1258
|
+
# First check if team_memberships are nil then loop through and look for a match on
|
1259
|
+
# Project OID
|
1260
|
+
if team_memberships != nil then
|
1261
|
+
|
1262
|
+
team_memberships.each do |this_membership|
|
1263
|
+
|
1264
|
+
# Grab the Project OID off of the ref URL
|
1265
|
+
this_membership_oid = get_membership_oid_from_membership(this_membership)
|
1266
|
+
if this_membership_oid == project_oid then
|
1267
|
+
is_member = true
|
1268
|
+
end
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
if is_member then return_value = "Yes" end
|
1273
|
+
return return_value
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
# Updates team membership. Note - this utilizes un-documented and un-supported Rally endpoint
|
1277
|
+
# that is not part of WSAPI REST
|
1278
|
+
# it also digs down into rally_api to directly PUT against this endpoint
|
1279
|
+
# not guaranteed to work forever
|
1280
|
+
|
1281
|
+
def update_team_membership(user, project_oid, project_name, team_member_setting)
|
1282
|
+
|
1283
|
+
# look up user
|
1284
|
+
these_team_memberships = user["TeamMemberships"]
|
1285
|
+
this_user_oid = user["ObjectID"]
|
1286
|
+
|
1287
|
+
# Default for whether user is member or not
|
1288
|
+
is_member = is_team_member(project_oid, user)
|
1289
|
+
|
1290
|
+
url_base = make_team_member_url(this_user_oid, project_oid)
|
1291
|
+
|
1292
|
+
# if User isn't a team member and update value is Yes then make them one
|
1293
|
+
if is_member == "No" && team_member_setting.downcase == TEAMMEMBER_YES.downcase then
|
1294
|
+
|
1295
|
+
# Construct payload object
|
1296
|
+
my_payload = {}
|
1297
|
+
my_team_member_setting = {}
|
1298
|
+
my_team_member_setting ["TeamMember"] = "true"
|
1299
|
+
my_payload["projectuser"] = my_team_member_setting
|
1300
|
+
|
1301
|
+
args = {:method => :put}
|
1302
|
+
args[:payload] = my_payload
|
1303
|
+
|
1304
|
+
# @rally_json_connection does a to_json on object to convert
|
1305
|
+
# payload object to JSON: {"projectuser":{"TeamMember":"true"}}
|
1306
|
+
response = @rally_json_connection.send_request(url_base, args)
|
1307
|
+
@logger.info " #{user["UserName"]} #{project_name} - Team Membership set to #{team_member_setting}"
|
1308
|
+
|
1309
|
+
# if User is a team member and update value is No then remove them from team
|
1310
|
+
elsif is_member == "Yes" && team_member_setting.downcase == TEAMMEMBER_NO.downcase then
|
1311
|
+
|
1312
|
+
# Construct payload object
|
1313
|
+
my_payload = {}
|
1314
|
+
my_team_member_setting = {}
|
1315
|
+
my_team_member_setting ["TeamMember"] = "false"
|
1316
|
+
my_payload["projectuser"] = my_team_member_setting
|
1317
|
+
|
1318
|
+
args = {:method => :put}
|
1319
|
+
args[:payload] = my_payload
|
1320
|
+
|
1321
|
+
# @rally_json_connection will convert payload object to JSON: {"projectuser":{"TeamMember":"false"}}
|
1322
|
+
response = @rally_json_connection.send_request(url_base, args)
|
1323
|
+
@logger.info " #{user["UserName"]} #{project_name} - Team Membership set to #{team_member_setting}"
|
1324
|
+
else
|
1325
|
+
@logger.info " #{user["UserName"]} #{project_name} - No creation of or changes to Team Membership"
|
1326
|
+
end
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
# Create Admin, User, or Viewer permissions for a Workspace
|
1330
|
+
def create_workspace_permission(user, workspace, permission)
|
1331
|
+
# Keep backward compatibility of our old permission names
|
1332
|
+
if permission == VIEWER || permission == EDITOR
|
1333
|
+
permission = USER
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
if permission != NOACCESS
|
1337
|
+
new_permission_obj = {}
|
1338
|
+
new_permission_obj["Workspace"] = workspace["_ref"]
|
1339
|
+
new_permission_obj["User"] = user._ref
|
1340
|
+
new_permission_obj["Role"] = permission
|
1341
|
+
|
1342
|
+
if @create_flag then new_permission = @rally.create(:workspacepermission, new_permission_obj) end
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
# Check to see if User has _any_ permission within a specific workspace
|
1347
|
+
def does_user_have_workspace_permission?(workspace, user)
|
1348
|
+
# set default return value
|
1349
|
+
workspace_permission_exists = false
|
1350
|
+
|
1351
|
+
use_cache = false
|
1352
|
+
|
1353
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1354
|
+
if @cached_users != nil then
|
1355
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1359
|
+
if use_cache then
|
1360
|
+
|
1361
|
+
this_user = @cached_users[user.UserName]
|
1362
|
+
|
1363
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1364
|
+
# workspace, and if so, has it changed
|
1365
|
+
user_permissions = this_user.UserPermissions
|
1366
|
+
user_permissions.each do | this_permission |
|
1367
|
+
if this_permission._type == "WorkspacePermission" then
|
1368
|
+
if this_permission.Workspace.ObjectID.to_s == workspace["ObjectID"].to_s then
|
1369
|
+
workspace_permission_exists = true
|
1370
|
+
break
|
1371
|
+
end
|
1372
|
+
end
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1376
|
+
workspace_permission_query = RallyAPI::RallyQuery.new()
|
1377
|
+
workspace_permission_query.type = :workspacepermission
|
1378
|
+
workspace_permission_query.fetch = "Workspace,Name,ObjectID,Role,User"
|
1379
|
+
workspace_permission_query.page_size = 200 #optional - default is 200
|
1380
|
+
workspace_permission_query.order = "Name Asc"
|
1381
|
+
workspace_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1382
|
+
|
1383
|
+
query_results = @rally.find(workspace_permission_query)
|
1384
|
+
|
1385
|
+
workspace_permission_exists = false
|
1386
|
+
|
1387
|
+
# Look to see if any existing WorkspacePermissions for this user match the one we're examining
|
1388
|
+
# If so, check to see if the workspace permissions are any different
|
1389
|
+
query_results.each { |wp|
|
1390
|
+
if ( wp.Workspace.ObjectID == workspace["ObjectID"])
|
1391
|
+
workspace_permission_exists = true
|
1392
|
+
break
|
1393
|
+
end
|
1394
|
+
}
|
1395
|
+
end
|
1396
|
+
return workspace_permission_exists
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
# Check to see if User has _any_ permission within a specific project
|
1400
|
+
def does_user_have_project_permission?(project, user)
|
1401
|
+
|
1402
|
+
# set default return value
|
1403
|
+
project_permission_exists = false
|
1404
|
+
|
1405
|
+
use_cache = false
|
1406
|
+
|
1407
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1408
|
+
if @cached_users != nil then
|
1409
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1413
|
+
if use_cache then
|
1414
|
+
|
1415
|
+
this_user = @cached_users[user.UserName]
|
1416
|
+
|
1417
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1418
|
+
# workspace, and if so, has it changed
|
1419
|
+
|
1420
|
+
user_permissions = this_user.UserPermissions
|
1421
|
+
|
1422
|
+
user_permissions.each do |this_permission|
|
1423
|
+
|
1424
|
+
if this_permission._type == "ProjectPermission" then
|
1425
|
+
# user has existing permissions in this project - let's compare new role against existing
|
1426
|
+
if this_permission.Project.ObjectID.to_s == project["ObjectID"].to_s then
|
1427
|
+
project_permission_exists = true
|
1428
|
+
break
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
end
|
1432
|
+
|
1433
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1434
|
+
|
1435
|
+
project_permission_query = RallyAPI::RallyQuery.new()
|
1436
|
+
project_permission_query.type = :projectpermission
|
1437
|
+
project_permission_query.fetch = "Project,Name,ObjectID,Role,User"
|
1438
|
+
project_permission_query.page_size = 200 #optional - default is 200
|
1439
|
+
project_permission_query.order = "Name Asc"
|
1440
|
+
project_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1441
|
+
|
1442
|
+
query_results = @rally.find(project_permission_query)
|
1443
|
+
|
1444
|
+
project_permission_exists = false
|
1445
|
+
|
1446
|
+
# Look to see if any existing ProjectPermissions for this user match the one we're examining
|
1447
|
+
# If so, check to see if the project permissions are any different
|
1448
|
+
query_results.each { |pp|
|
1449
|
+
|
1450
|
+
if ( pp.Project.ObjectID == project["ObjectID"])
|
1451
|
+
project_permission_exists = true
|
1452
|
+
break
|
1453
|
+
end
|
1454
|
+
}
|
1455
|
+
end
|
1456
|
+
return project_permission_exists
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
#--------- Private methods --------------
|
1460
|
+
private
|
1461
|
+
|
1462
|
+
# Takes the name of the permission and returns the last token which is the permission
|
1463
|
+
def parse_permission(name)
|
1464
|
+
if name.reverse.index(VIEWER.reverse)
|
1465
|
+
return VIEWER
|
1466
|
+
elsif name.reverse.index(EDITOR.reverse)
|
1467
|
+
return EDITOR
|
1468
|
+
elsif name.reverse.index(USER.reverse)
|
1469
|
+
return USER
|
1470
|
+
elsif name.reverse.index(ADMIN.reverse)
|
1471
|
+
return ADMIN
|
1472
|
+
else
|
1473
|
+
@logger.info "Error in parsing permission"
|
1474
|
+
end
|
1475
|
+
nil
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
# Creates a team membership URL for request against (undocumented, non-WSAPI and non-supported)
|
1479
|
+
# team membership endpoint.
|
1480
|
+
# Method: PUT
|
1481
|
+
# URL Format:
|
1482
|
+
# https://rally1.rallydev.com/slm/webservice/x/project/12345678910/projectuser/12345678911.js
|
1483
|
+
# Payload: {"projectuser":{"TeamMember":"true"}}
|
1484
|
+
# Where 12345678910 => Project OID
|
1485
|
+
# And 12345678911 => User OID
|
1486
|
+
|
1487
|
+
def make_team_member_url(input_user_oid, input_project_oid)
|
1488
|
+
|
1489
|
+
rally_url = @rally.rally_url + "/webservice/"
|
1490
|
+
wsapi_version = @rally.wsapi_version
|
1491
|
+
|
1492
|
+
make_team_member_url = rally_url + wsapi_version +
|
1493
|
+
"/project/" + input_project_oid.to_s +
|
1494
|
+
"/projectuser/" + input_user_oid.to_s + ".js"
|
1495
|
+
|
1496
|
+
return make_team_member_url
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
# check if the new permissions are an upgrade vs. what the user currently has
|
1500
|
+
def is_project_permission_upgrade?(project, user, new_permission)
|
1501
|
+
|
1502
|
+
# set default return value
|
1503
|
+
project_permission_upgrade = false
|
1504
|
+
|
1505
|
+
# set a default existing_permission conservatively at Project Admin
|
1506
|
+
existing_permission = PROJECTADMIN_READ
|
1507
|
+
use_cache = false
|
1508
|
+
|
1509
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1510
|
+
if @cached_users != nil then
|
1511
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1512
|
+
end
|
1513
|
+
|
1514
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1515
|
+
if use_cache then
|
1516
|
+
|
1517
|
+
number_matching_projects = 0
|
1518
|
+
this_user = @cached_users[user.UserName]
|
1519
|
+
|
1520
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1521
|
+
# workspace, and if so, is our proposed change an upgrade
|
1522
|
+
user_permissions = this_user.UserPermissions
|
1523
|
+
user_permissions.each do | this_permission |
|
1524
|
+
|
1525
|
+
if this_permission._type == "ProjectPermission" then
|
1526
|
+
# user has existing permissions in this project - let's compare new role against existing
|
1527
|
+
if this_permission.Project.ObjectID.to_s == project["ObjectID"].to_s then
|
1528
|
+
existing_permission = this_permission.Role
|
1529
|
+
case existing_permission
|
1530
|
+
|
1531
|
+
# you can't upgrade a PROJECTADMIN, so return false
|
1532
|
+
when PROJECTADMIN_READ
|
1533
|
+
project_permission_upgrade = false
|
1534
|
+
when EDITOR
|
1535
|
+
if new_permission.eql?(PROJECTADMIN_READ) then
|
1536
|
+
project_permission_upgrade = true
|
1537
|
+
end
|
1538
|
+
when VIEWER
|
1539
|
+
if new_permission.eql?(EDITOR) || new_permission.eql?(PROJECTADMIN_READ) then
|
1540
|
+
project_permission_upgrade = true
|
1541
|
+
end
|
1542
|
+
# NOACCESS is denoted by non-existence of a permission so we don't
|
1543
|
+
# check that here
|
1544
|
+
end
|
1545
|
+
number_matching_projects += 1
|
1546
|
+
end
|
1547
|
+
end
|
1548
|
+
end
|
1549
|
+
|
1550
|
+
# This is a new project permission - set the changed bit to true
|
1551
|
+
if number_matching_projects == 0 then
|
1552
|
+
project_permission_upgrade = true
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1556
|
+
|
1557
|
+
project_permission_query = RallyAPI::RallyQuery.new()
|
1558
|
+
project_permission_query.type = :projectpermission
|
1559
|
+
project_permission_query.fetch = "Project,Name,ObjectID,Role,User"
|
1560
|
+
project_permission_query.page_size = 200 #optional - default is 200
|
1561
|
+
project_permission_query.order = "Name Asc"
|
1562
|
+
project_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1563
|
+
|
1564
|
+
query_results = @rally.find(project_permission_query)
|
1565
|
+
|
1566
|
+
project_permission_upgrade = false
|
1567
|
+
number_matching_projects = 0
|
1568
|
+
|
1569
|
+
# Look to see if any existing ProjectPermissions for this user match the one we're examining
|
1570
|
+
# If so, check to see if the project permissions are any different
|
1571
|
+
query_results.each { |pp|
|
1572
|
+
|
1573
|
+
if ( pp.Project.ObjectID == project["ObjectID"])
|
1574
|
+
number_matching_projects+=1
|
1575
|
+
existing_permission = pp.Role
|
1576
|
+
case existing_permission
|
1577
|
+
# you can't upgrade a PROJECTADMIN, so return false
|
1578
|
+
when PROJECTADMIN_READ
|
1579
|
+
project_permission_upgrade = false
|
1580
|
+
when EDITOR
|
1581
|
+
if new_permission.eql?(PROJECTADMIN_READ) then
|
1582
|
+
project_permission_upgrade = true
|
1583
|
+
end
|
1584
|
+
when VIEWER
|
1585
|
+
if new_permission.eql?(EDITOR) || new_permission.eql?(PROJECTADMIN_READ) then
|
1586
|
+
project_permission_upgrade = true
|
1587
|
+
end
|
1588
|
+
# NOACCESS is denoted by non-existence of a permission so we don't
|
1589
|
+
# check that here
|
1590
|
+
end
|
1591
|
+
end
|
1592
|
+
}
|
1593
|
+
# This is a new project permission - set the upgrade bit to true
|
1594
|
+
if number_matching_projects == 0 then project_permission_upgrade = true end
|
1595
|
+
end
|
1596
|
+
return project_permission_upgrade, existing_permission
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
def is_workspace_permission_upgrade?(workspace, user, new_permission)
|
1600
|
+
|
1601
|
+
# set default return values
|
1602
|
+
workspace_permission_upgrade = false
|
1603
|
+
|
1604
|
+
# set a default existing_permission conservatively at Admin
|
1605
|
+
existing_permission = ADMIN
|
1606
|
+
use_cache = false
|
1607
|
+
|
1608
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1609
|
+
if @cached_users != nil then
|
1610
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1614
|
+
if use_cache then
|
1615
|
+
|
1616
|
+
number_matching_workspaces = 0
|
1617
|
+
this_user = @cached_users[user.UserName]
|
1618
|
+
|
1619
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1620
|
+
# workspace, and if so, has it changed
|
1621
|
+
user_permissions = this_user.UserPermissions
|
1622
|
+
user_permissions.each do | this_permission |
|
1623
|
+
if this_permission._type == "WorkspacePermission" then
|
1624
|
+
if this_permission.Workspace.ObjectID.to_s == workspace["ObjectID"].to_s then
|
1625
|
+
existing_permission = this_permission.Role
|
1626
|
+
number_matching_workspaces += 1
|
1627
|
+
case existing_permission
|
1628
|
+
# Can't upgrade an admin, so return false
|
1629
|
+
when ADMIN
|
1630
|
+
workspace_permission_upgrade = false
|
1631
|
+
when USER
|
1632
|
+
if new_permission.eql?(ADMIN) then
|
1633
|
+
workspace_permission_upgrade = true
|
1634
|
+
end
|
1635
|
+
# No-Access is denoted by an absence of a Permission - we don't need to check that here
|
1636
|
+
end
|
1637
|
+
end
|
1638
|
+
end
|
1639
|
+
end
|
1640
|
+
# This is a new workspace permission - set the changed bit to true
|
1641
|
+
if number_matching_workspaces == 0 then workspace_permission_upgrade = true end
|
1642
|
+
|
1643
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1644
|
+
workspace_permission_query = RallyAPI::RallyQuery.new()
|
1645
|
+
workspace_permission_query.type = :workspacepermission
|
1646
|
+
workspace_permission_query.fetch = "Workspace,Name,ObjectID,Role,User"
|
1647
|
+
workspace_permission_query.page_size = 200 #optional - default is 200
|
1648
|
+
workspace_permission_query.order = "Name Asc"
|
1649
|
+
workspace_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1650
|
+
|
1651
|
+
query_results = @rally.find(workspace_permission_query)
|
1652
|
+
|
1653
|
+
workspace_permission_upgrade = false
|
1654
|
+
number_matching_workspaces = 0
|
1655
|
+
|
1656
|
+
# Look to see if any existing WorkspacePermissions for this user match the one we're examining
|
1657
|
+
# If so, check to see if the workspace permissions are any different
|
1658
|
+
query_results.each { |wp|
|
1659
|
+
if ( wp.Workspace.ObjectID == workspace["ObjectID"])
|
1660
|
+
existing_permission = wp.Role
|
1661
|
+
number_matching_workspaces+=1
|
1662
|
+
case existing_permission
|
1663
|
+
# Can't upgrade an admin, so return false
|
1664
|
+
when ADMIN
|
1665
|
+
workspace_permission_upgrade = false
|
1666
|
+
when USER
|
1667
|
+
if new_permission.eql?(ADMIN) then
|
1668
|
+
workspace_permission_upgrade = true
|
1669
|
+
end
|
1670
|
+
# No-Access is denoted by an absence of a Permission - we don't need to check that here
|
1671
|
+
end
|
1672
|
+
end
|
1673
|
+
}
|
1674
|
+
# This is a new workspace permission - set the changed bit to true
|
1675
|
+
if number_matching_workspaces == 0 then workspace_permission_upgrade = true end
|
1676
|
+
end
|
1677
|
+
return workspace_permission_upgrade, existing_permission
|
1678
|
+
end
|
1679
|
+
|
1680
|
+
# check if the new permissions are different than what the user currently has
|
1681
|
+
# if we don't do this, we will delete and recreate permissions each time and that
|
1682
|
+
# will make the revision history on user really, really, really, really ugly
|
1683
|
+
def project_permissions_different?(project, user, new_permission)
|
1684
|
+
|
1685
|
+
# set default return value
|
1686
|
+
project_permission_changed = false
|
1687
|
+
use_cache = false
|
1688
|
+
|
1689
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1690
|
+
if @cached_users != nil then
|
1691
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
if use_cache then
|
1695
|
+
number_matching_projects = 0
|
1696
|
+
this_user = @cached_users[user.UserName]
|
1697
|
+
|
1698
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1699
|
+
# workspace, and if so, has it changed
|
1700
|
+
user_permissions = this_user.UserPermissions
|
1701
|
+
user_permissions.each do | this_permission |
|
1702
|
+
|
1703
|
+
if this_permission._type == "ProjectPermission" then
|
1704
|
+
# user has existing permissions in this project - let's compare new role against existing
|
1705
|
+
if this_permission.Project.ObjectID.to_s == project["ObjectID"].to_s then
|
1706
|
+
number_matching_projects += 1
|
1707
|
+
if this_permission.Role != new_permission then
|
1708
|
+
project_permission_changed = true
|
1709
|
+
end
|
1710
|
+
end
|
1711
|
+
end
|
1712
|
+
end
|
1713
|
+
|
1714
|
+
# This is a new project permission - set the changed bit to true
|
1715
|
+
if number_matching_projects == 0 then
|
1716
|
+
project_permission_changed = true
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1720
|
+
|
1721
|
+
project_permission_query = RallyAPI::RallyQuery.new()
|
1722
|
+
project_permission_query.type = :projectpermission
|
1723
|
+
project_permission_query.fetch = "Project,Name,ObjectID,Role,User"
|
1724
|
+
project_permission_query.page_size = 200 #optional - default is 200
|
1725
|
+
project_permission_query.order = "Name Asc"
|
1726
|
+
project_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1727
|
+
|
1728
|
+
query_results = @rally.find(project_permission_query)
|
1729
|
+
|
1730
|
+
project_permission_changed = false
|
1731
|
+
number_matching_projects = 0
|
1732
|
+
|
1733
|
+
# Look to see if any existing ProjectPermissions for this user match the one we're examining
|
1734
|
+
# If so, check to see if the project permissions are any different
|
1735
|
+
query_results.each { |pp|
|
1736
|
+
|
1737
|
+
if ( pp.Project.ObjectID == project["ObjectID"])
|
1738
|
+
number_matching_projects+=1
|
1739
|
+
if pp.Role != new_permission then project_permission_changed = true end
|
1740
|
+
end
|
1741
|
+
}
|
1742
|
+
# This is a new project permission - set the changed bit to true
|
1743
|
+
if number_matching_projects == 0 then project_permission_changed = true end
|
1744
|
+
end
|
1745
|
+
return project_permission_changed
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
# check if the new permissions are different than what the user currently has
|
1749
|
+
# if we don't do this, we will delete and recreate permissions each time and that
|
1750
|
+
# will make the revision history on user really, really, really, really ugly
|
1751
|
+
|
1752
|
+
def workspace_permissions_different?(workspace, user, new_permission)
|
1753
|
+
|
1754
|
+
# set default return value
|
1755
|
+
workspace_permission_changed = false
|
1756
|
+
|
1757
|
+
use_cache = false
|
1758
|
+
|
1759
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1760
|
+
if @cached_users != nil then
|
1761
|
+
if @cached_users.has_key?(user.UserName) then use_cache = true end
|
1762
|
+
end
|
1763
|
+
|
1764
|
+
# first try to lookup against cached user list -- much faster than re-querying Rally
|
1765
|
+
if use_cache then
|
1766
|
+
|
1767
|
+
number_matching_workspaces = 0
|
1768
|
+
this_user = @cached_users[user.UserName]
|
1769
|
+
|
1770
|
+
# loop through permissions and look to see if there's an existing permission for this
|
1771
|
+
# workspace, and if so, has it changed
|
1772
|
+
user_permissions = this_user.UserPermissions
|
1773
|
+
user_permissions.each do | this_permission |
|
1774
|
+
if this_permission._type == "WorkspacePermission" then
|
1775
|
+
if this_permission.Workspace.ObjectID.to_s == workspace["ObjectID"].to_s then
|
1776
|
+
number_matching_workspaces += 1
|
1777
|
+
if this_permission.Role != new_permission then workspace_permission_changed = true end
|
1778
|
+
end
|
1779
|
+
end
|
1780
|
+
end
|
1781
|
+
# This is a new workspace permission - set the changed bit to true
|
1782
|
+
if number_matching_workspaces == 0 then workspace_permission_changed = true end
|
1783
|
+
|
1784
|
+
else # Cache does not exist or user isn't in it - query info from Rally
|
1785
|
+
workspace_permission_query = RallyAPI::RallyQuery.new()
|
1786
|
+
workspace_permission_query.type = :workspacepermission
|
1787
|
+
workspace_permission_query.fetch = "Workspace,Name,ObjectID,Role,User"
|
1788
|
+
workspace_permission_query.page_size = 200 #optional - default is 200
|
1789
|
+
workspace_permission_query.order = "Name Asc"
|
1790
|
+
workspace_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1791
|
+
|
1792
|
+
query_results = @rally.find(workspace_permission_query)
|
1793
|
+
|
1794
|
+
workspace_permission_changed = false
|
1795
|
+
number_matching_workspaces = 0
|
1796
|
+
|
1797
|
+
# Look to see if any existing WorkspacePermissions for this user match the one we're examining
|
1798
|
+
# If so, check to see if the workspace permissions are any different
|
1799
|
+
query_results.each { |wp|
|
1800
|
+
if ( wp.Workspace.ObjectID == workspace["ObjectID"])
|
1801
|
+
number_matching_workspaces+=1
|
1802
|
+
if wp.Role != new_permission then workspace_permission_changed = true end
|
1803
|
+
end
|
1804
|
+
}
|
1805
|
+
# This is a new workspace permission - set the changed bit to true
|
1806
|
+
if number_matching_workspaces == 0 then workspace_permission_changed = true end
|
1807
|
+
end
|
1808
|
+
return workspace_permission_changed
|
1809
|
+
end
|
1810
|
+
|
1811
|
+
# Create User or Viewer permissions for a Project
|
1812
|
+
def create_project_permission(user, project, permission)
|
1813
|
+
# Keep backward compatibility of our old permission names
|
1814
|
+
if permission == USER
|
1815
|
+
permission = EDITOR
|
1816
|
+
end
|
1817
|
+
|
1818
|
+
if permission != NOACCESS
|
1819
|
+
this_workspace = project["Workspace"]
|
1820
|
+
new_permission_obj = {}
|
1821
|
+
new_permission_obj["Workspace"] = this_workspace["_ref"]
|
1822
|
+
new_permission_obj["Project"] = project["_ref"]
|
1823
|
+
new_permission_obj["User"] = user._ref
|
1824
|
+
new_permission_obj["Role"] = permission
|
1825
|
+
|
1826
|
+
if @create_flag then new_permission = @rally.create(:projectpermission, new_permission_obj) end
|
1827
|
+
end
|
1828
|
+
end
|
1829
|
+
|
1830
|
+
# Project permissions are automatically deleted in this case
|
1831
|
+
def delete_workspace_permission(user, workspace)
|
1832
|
+
|
1833
|
+
if @upgrade_only_mode then
|
1834
|
+
@logger.info " #{user["UserName"]} #{workspace["Name"]} - upgrade_only_mode == true."
|
1835
|
+
@logger.warn " Proposed Permission: #{NOACCESS}"
|
1836
|
+
@logger.info " Proposed Permission change would downgrade permissions. No permission updates applied."
|
1837
|
+
return
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
# queries on permissions are a bit limited - to only one filter parameter
|
1841
|
+
workspace_permission_query = RallyAPI::RallyQuery.new()
|
1842
|
+
workspace_permission_query.type = :workspacepermission
|
1843
|
+
workspace_permission_query.fetch = "Workspace,Name,ObjectID,Role,User,UserName"
|
1844
|
+
workspace_permission_query.page_size = 200 #optional - default is 200
|
1845
|
+
workspace_permission_query.order = "Name Asc"
|
1846
|
+
workspace_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1847
|
+
|
1848
|
+
query_results = @rally.find(workspace_permission_query)
|
1849
|
+
|
1850
|
+
query_results.each do | this_workspace_permission |
|
1851
|
+
|
1852
|
+
this_workspace = this_workspace_permission.Workspace
|
1853
|
+
this_workspace_oid = this_workspace["ObjectID"].to_s
|
1854
|
+
|
1855
|
+
if this_workspace_permission != nil && this_workspace_oid == workspace["ObjectID"].to_s
|
1856
|
+
begin
|
1857
|
+
@rally.delete(this_workspace_permission["_ref"])
|
1858
|
+
rescue Exception => ex
|
1859
|
+
this_user = this_workspace_permission.User
|
1860
|
+
this_user_name = this_user.Name
|
1861
|
+
|
1862
|
+
@logger.warn "Cannot remove WorkspacePermission: #{this_workspace_permission.Name}."
|
1863
|
+
@logger.warn "WorkspacePermission either already NoAccess, or would remove the only WorkspacePermission in Subscription."
|
1864
|
+
@logger.warn "User #{this_user_name} must have access to at least one Workspace within the Subscription."
|
1865
|
+
end
|
1866
|
+
end
|
1867
|
+
end
|
1868
|
+
end
|
1869
|
+
|
1870
|
+
def delete_project_permission(user, project)
|
1871
|
+
|
1872
|
+
if @upgrade_only_mode then
|
1873
|
+
@logger.info " #{user["UserName"]} #{project["Name"]} - upgrade_only_mode == true."
|
1874
|
+
@logger.warn " Proposed Permission: #{NOACCESS}"
|
1875
|
+
@logger.info " Proposed Permission change would downgrade permissions. No permission updates applied."
|
1876
|
+
return
|
1877
|
+
end
|
1878
|
+
|
1879
|
+
# queries on permissions are a bit limited - to only one filter parameter
|
1880
|
+
project_permission_query = RallyAPI::RallyQuery.new()
|
1881
|
+
project_permission_query.type = :projectpermission
|
1882
|
+
project_permission_query.fetch = "Project,Name,ObjectID,Role,User,UserName"
|
1883
|
+
project_permission_query.page_size = 200 #optional - default is 200
|
1884
|
+
project_permission_query.order = "Name Asc"
|
1885
|
+
project_permission_query.query_string = "(User.UserName = \"" + user.UserName + "\")"
|
1886
|
+
|
1887
|
+
query_results = @rally.find(project_permission_query)
|
1888
|
+
query_results.each do | this_project_permission |
|
1889
|
+
|
1890
|
+
this_project = this_project_permission.Project
|
1891
|
+
this_project_oid = this_project.ObjectID.to_s
|
1892
|
+
|
1893
|
+
if this_project_permission != nil && this_project_oid == project["ObjectID"].to_s
|
1894
|
+
begin
|
1895
|
+
@rally.delete(this_project_permission["_ref"])
|
1896
|
+
rescue Exception => ex
|
1897
|
+
this_user = this_project_permission.User
|
1898
|
+
this_user_name = this_user.Name
|
1899
|
+
|
1900
|
+
@logger.warn "Cannot remove ProjectPermission: #{this_project_permission.Name}."
|
1901
|
+
@logger.warn "ProjectPermission either already NoAccess, or would remove the only ProjectPermission in Workspace."
|
1902
|
+
@logger.warn "User #{this_user_name} must have access to at least one Project within the Workspace."
|
1903
|
+
end
|
1904
|
+
end
|
1905
|
+
end
|
1906
|
+
end
|
1907
|
+
|
1908
|
+
def update_permission_workspacelevel(workspace, user, permission)
|
1909
|
+
@logger.info " #{user.UserName} #{workspace["Name"]} - Permission set to #{permission}"
|
1910
|
+
if permission == ADMIN
|
1911
|
+
create_workspace_permission(user, workspace, permission)
|
1912
|
+
elsif permission == NOACCESS
|
1913
|
+
delete_workspace_permission(user, workspace)
|
1914
|
+
elsif permission == USER || permission == VIEWER || permission == EDITOR
|
1915
|
+
create_workspace_permission(user, workspace, permission)
|
1916
|
+
else
|
1917
|
+
@logger.error "Invalid Permission - #{permission}"
|
1918
|
+
end
|
1919
|
+
end
|
1920
|
+
|
1921
|
+
def update_permission_projectlevel(project, user, permission)
|
1922
|
+
@logger.info " #{user.UserName} #{project["Name"]} - Permission set to #{permission}"
|
1923
|
+
if permission == PROJECTADMIN_READ
|
1924
|
+
# Hopefully temporary re-cast of "Admin" to "Project Admin"
|
1925
|
+
permission = PROJECTADMIN_CREATE
|
1926
|
+
create_project_permission(user, project, permission)
|
1927
|
+
elsif permission == NOACCESS
|
1928
|
+
delete_project_permission(user, project)
|
1929
|
+
elsif permission == USER || permission == VIEWER || permission == EDITOR
|
1930
|
+
create_project_permission(user, project, permission)
|
1931
|
+
else
|
1932
|
+
@logger.error "Invalid Permission - #{permission}"
|
1933
|
+
end
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
end
|
1937
|
+
end
|