rally_user_management 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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