rs_user_policy 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,12 +1,32 @@
1
1
  = rs_user_policy
2
2
 
3
- A useful tool for managing many users across many child accounts in a RightScale Enterprise Edition
3
+ A useful tool for managing many users across many RightScale accounts.
4
4
 
5
5
  While the tests are not exhaustive, the current build status is..
6
6
  {<img src="https://travis-ci.org/rgeyer/rs_user_policy.png" />}[https://travis-ci.org/rgeyer/rs_user_policy]
7
7
 
8
8
  == Usage
9
9
 
10
+ You must pass in your RightScale authentication information, and a policy file. You can also specify one or many RightScale accounts using the --rs-acct-num or -a parameters. If an account that you specify is an Enterprise Master account, all Enterprise Children accounts will automatically be included.
11
+
12
+ Options:
13
+ --rs-email, -r <s>: You RightScale User Email Address
14
+ --rs-pass, -s <s>: Your RightScale User Password
15
+ --rs-acct-num, -a <s>: A RightScale Enterprise Master Account ID
16
+ --policy, -p <s>: The path to a JSON file containing the role to permissions policy to enforce
17
+ --user-assignments, -u <s>: The path to a JSON file containing email address => role pairs for user assignments
18
+ --dry-run, -d: A flag indicating that no changes should be made, only the user_assignments.json should be evaluated (or created) and
19
+ the audit_log.json produced
20
+ --help, -h: Show this message
21
+
22
+ Example (One account)
23
+ rs_user_policy -r "foo@bar.baz" -s "password" -p policy.json -u user_assignments.json -a 12345
24
+
25
+ Example (Multiple accounts)
26
+ rs_user_policy -r "foo@bar.baz" -s "password" -p policy.json -u user_assignments.json -a 12345 -a 67891 -a 11121
27
+
28
+ === Managing existing user permissions
29
+
10
30
  The binary contained in this gem accepts two files as inputs to determine it's behavior. The first, is a policy JSON file which specifies the permissions to be applied to users.
11
31
 
12
32
  Policy JSON should be in the following format
@@ -73,6 +93,24 @@ user2 will be assigned observer and lite_user rights on account 12345, and obser
73
93
 
74
94
  Got that? Cool!
75
95
 
96
+ === Creating new users
97
+
98
+ This tool can also be used to create net-new users who have either never used RightScale, or who have never been associated with one of the accounts targetted by the tool. For those type of users some additional parameters are necessary in the user_assignments source file. The minimum set of properties is ["roles", "company", "first_name", "last_name", "phone"]
99
+
100
+ {
101
+ "net@new.user": {
102
+ "roles": ["team1"],
103
+ "company": "RightScale",
104
+ "first_name": "Net",
105
+ "last_name": "New",
106
+ "phone": "9999999999"
107
+ }
108
+ }
109
+
110
+ The order of the additional parameters does not matter. The properties list can also include "identity_provider_href" and "principal_uid" or "password" to specify the users authentication details. If no authentication details are supplied a random secure password will be generated, and written to the output user_assignments.json file.
111
+
112
+ If a user with the specified email already exists, but that user does not have any permissions in the account(s) targetted by the tool, these additional properties are still required, but will be ignored.
113
+
76
114
  == Output
77
115
 
78
116
  When the script is run, it will produce two JSON files as output.
@@ -83,11 +121,11 @@ Second is the user_assignments-<timestamp>.json file. This will be a combinatio
83
121
 
84
122
  == TODO
85
123
 
86
- * Allow users to be added who are not already invited to any RS parent or child account
87
124
  * In absence of a policy.json, create a new policy.json with base roles for each account discovered (I.E. Admin, Observer, Designer, etc)
88
125
  * Perhaps allow a role to inherit from another, or be a concatenation of several?
89
126
  * Handle the race condition where permissions have been deleted in the correct order, but API still throws 422 removing "observer" because the changes have not become consistent server side
90
127
  * HTTP Code: 422, Response body: A user must have the observer role. (RightApi::Exceptions::ApiException)
128
+ * 2012/12/18 - This does not appear to be an issue any longer. Or at least I am not encountering it anymore. Perhaps this was a transient issue?
91
129
 
92
130
  == Copyright
93
131
 
data/bin/rs_user_policy CHANGED
@@ -24,6 +24,7 @@
24
24
  require 'trollop'
25
25
  require 'right_api_client'
26
26
  require 'logger'
27
+ require 'digest/md5'
27
28
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rs_user_policy'))
28
29
 
29
30
  opts = Trollop::options do
@@ -31,7 +32,7 @@ opts = Trollop::options do
31
32
 
32
33
  opt :rs_email, "You RightScale User Email Address", :type => :string, :required => true
33
34
  opt :rs_pass, "Your RightScale User Password", :type => :string, :required => true
34
- opt :rs_acct_num, "A RightScale Enterprise Master Account ID", :type => :string, :required => true
35
+ opt :rs_acct_num, "A RightScale Enterprise Master Account ID", :type => :string, :multi => true, :required => true
35
36
  opt :policy, "The path to a JSON file containing the role to permissions policy to enforce", :type => :string, :required => true
36
37
  opt :user_assignments, "The path to a JSON file containing email address => role pairs for user assignments", :type => :string
37
38
  opt :dry_run, "A flag indicating that no changes should be made, only the user_assignments.json should be evaluated (or created) and the audit_log.json produced"
@@ -39,8 +40,6 @@ end
39
40
 
40
41
  log = Logger.new(STDOUT)
41
42
  timestamp = Time::now.to_i
42
- deleted_users = []
43
- accounts = []
44
43
 
45
44
  if opts[:dry_run]
46
45
  log.info("The dry_run option was selected, no action will be taken, but the user_assignments output and audit_log files will be written reflecting the actions which would have been taken")
@@ -66,40 +65,56 @@ end
66
65
 
67
66
  user_collection = RsUserPolicy::UserCollection.new
68
67
 
69
- client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => opts[:rs_acct_num])
70
- master_account = client.accounts(:id => opts[:rs_acct_num]).show()
71
- user_collection.add_users(client.users.index)
72
- user_collection.add_permissions(master_account.href, client.permissions.index)
68
+ multi_client = RsUserPolicy::RightApi::MultiClient.new(opts[:rs_email], opts[:rs_pass], opts[:rs_acct_num])
73
69
 
74
- accounts << {:client => client, :href => master_account.href, :name => master_account.name}
70
+ # Iterate over all accounts once to discover users and their permissions
71
+ multi_client.each do |account_id, account|
72
+ child_client = account[:client]
73
+ child_account = child_client.accounts(:id => account_id).show()
74
+ account_href = child_account.href
75
+ users_hash = Hash[child_client.users.index.map{|user| [user.href, user.email] }]
76
+ user_collection.add_users(users_hash)
77
+ user_collection.add_permissions(account_href, child_client.permissions.index)
78
+ end
75
79
 
76
- log.info("Operating on the Enterprise Master Account #{master_account.name}")
80
+ # Populate the user_assignments with hrefs
81
+ user_collection.users.each do |user|
82
+ user_assignments[user.email]["href"] = user.href
83
+ end
77
84
 
78
- begin
79
- client.child_accounts.index.each do |child|
80
- child_client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => RsUserPolicy::Utilities.id_from_href(child.href))
81
- accounts << {:client => child_client, :href => child.href, :name => child.name}
85
+ net_new_users = user_assignments.list - user_collection.users.map{|u| u.email }
82
86
 
83
- user_collection.add_users(child_client.users.index)
84
- user_collection.add_permissions(child.href, child_client.permissions.index)
87
+ net_new_users.each do |net_new_user_email|
88
+ client = multi_client[opts[:rs_acct_num].first()][:client]
89
+ user_create_params = user_assignments[net_new_user_email]
90
+ user_create_params = {:email => net_new_user_email}.merge(user_create_params)
91
+ unless user_create_params.key?("password") || (user_create_params.key?("identity_provider_href") && user_create_params.key?("principal_uid"))
92
+ pass = RsUserPolicy::Utilities.generate_compliant_password
93
+ user_create_params["password"] = pass
94
+ user_assignments[net_new_user_email]["password"] = pass
85
95
  end
86
- rescue RightApi::Exceptions::ApiException => e
87
- if e.message =~ /Permission denied/
88
- log.warn("#{master_account.name} is not an Enterprise Master, or you do not have the enterprise_manager permission. No child accounts will be operated on.")
89
- else
90
- raise e
96
+ net_new_user_href = Digest::MD5.hexdigest((Time.now.to_i + rand(256)).to_s)
97
+ unless opts[:dry_run]
98
+ net_new_user = client.users.create(:user => user_create_params)
99
+ net_new_user_href = net_new_user.href
91
100
  end
101
+ user_collection.add_users({net_new_user_href => net_new_user_email})
102
+ user_assignments[net_new_user_email]["href"] = net_new_user_href
92
103
  end
93
104
 
105
+ # Now iterate over all accounts to do actual permission assignments.
94
106
  begin
95
- accounts.each do |account|
107
+ multi_client.each do |account_id,account|
96
108
  child_client = account[:client]
109
+ child_account = child_client.accounts(:id => account_id).show()
110
+ account_name = child_account.name
111
+ account_href = child_account.href
97
112
 
98
- log.info("#{account[:name]} - #{account[:href]}")
113
+ log.info("#{account_name} - #{account_href}")
99
114
  user_collection.users.each do |user|
100
115
  email = user.email
101
116
  user_role = user_assignments.get_roles(email)
102
- existing_permissions = user.get_api_permissions(account[:href])
117
+ existing_permissions = user.get_api_permissions(account_href)
103
118
 
104
119
  # Allow some different options about possibly prompting the user etc
105
120
  if user_role.include?('delete')
@@ -107,16 +122,15 @@ begin
107
122
  # a user can also be effectively deleted if they have an empty list of
108
123
  # permissions for a particular account
109
124
  unless opts[:dry_run]
110
- log.debug("Deleting #{user.email} from #{account[:name]} by removing these permissions #{JSON.pretty_generate(existing_permissions)}")
111
- user.clear_permissions(account[:href], child_client)
112
- user_assignments.delete(user.email)
125
+ log.debug("Deleting #{user.email} from #{account_name} by removing these permissions #{JSON.pretty_generate(existing_permissions)}")
126
+ user.clear_permissions(account_href, child_client)
113
127
  end
114
- audit_log.add_entry(email, account[:name], 'deleted', 'deleted')
128
+ audit_log.add_entry(email, account_name, 'deleted', 'deleted')
115
129
  elsif !user_role.include?("immutable")
116
- user_policy = policy.get_permissions(user_role, account[:href])
117
- removed,added = user.set_api_permissions(user_policy, account[:href], child_client, opts)
130
+ user_policy = policy.get_permissions(user_role, account_href)
131
+ removed,added = user.set_api_permissions(user_policy, account_href, child_client, :dry_run => opts[:dry_run])
118
132
  changes = "-#{removed.values} +#{added.values}"
119
- audit_log.add_entry(email, account[:name], 'update_permissions', changes) unless removed.length + added.length == 0
133
+ audit_log.add_entry(email, account_name, 'update_permissions', changes) unless removed.length + added.length == 0
120
134
  end
121
135
  end
122
136
  end
@@ -124,5 +138,11 @@ rescue RightApi::Exceptions::ApiException => e
124
138
  log.fatal("A RightScale API exception occurred - #{e}")
125
139
  end
126
140
 
141
+ user_collection.users.each do |user|
142
+ if user_assignments.get_roles(user.email).include?("delete") && user.permissions == {}
143
+ user_assignments.delete(user.email)
144
+ end
145
+ end unless opts[:dry_run]
146
+
127
147
  user_assignments.serialize(:filename => user_assignments_output)
128
148
  audit_log.write_file
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2012 Ryan J. Geyer
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ module RsUserPolicy
23
+ module RightApi
24
+ class MultiClient
25
+
26
+ attr_accessor :accounts
27
+
28
+ # Creates RightApi::Client instances for each account ID supplied.
29
+ # If any supplied account ID is an enterprise master account, the
30
+ # child accounts are found and added to this MultiClient as well.
31
+ #
32
+ # @param [String] email Email address of a RightScale user with permissions to access the API
33
+ # @param [String] password Password of a RightScale user with permissions to access the API
34
+ # @param [Array<Integer>] accounts List of accounts for which to create RightApi::Client objects
35
+ def initialize(email, password, accounts)
36
+ @accounts ||= {}
37
+ accounts.each do |account_id|
38
+ client = ::RightApi::Client.new(
39
+ :email => email,
40
+ :password => password,
41
+ :account_id => account_id
42
+ )
43
+ this_account = {
44
+ :client => client,
45
+ :has_children => false
46
+ }
47
+ begin
48
+ child_accounts = client.child_accounts.index
49
+ this_account[:has_children] = true
50
+ # NOTE: Assuming that children can not have grand children
51
+ child_accounts.each do |child_account_res|
52
+ # TODO: Looser coupling to the Utilities class here?
53
+ child_account_id = RsUserPolicy::Utilities.id_from_href(child_account_res.href).to_i
54
+ child_account = ::RightApi::Client.new(
55
+ :email => email,
56
+ :password => password,
57
+ :account_id => child_account_id
58
+ )
59
+ @accounts[child_account_id] = {
60
+ :client => child_account,
61
+ :has_children => false,
62
+ :parent => account_id
63
+ }
64
+ end
65
+ rescue ::RightApi::Exceptions::ApiException => e
66
+ raise e unless e.message =~ /Permission denied/
67
+ end
68
+ @accounts[account_id] = this_account
69
+ end
70
+ end
71
+
72
+ def length
73
+ @accounts.length
74
+ end
75
+
76
+ def size
77
+ length
78
+ end
79
+
80
+ def [](account_id)
81
+ @accounts[account_id]
82
+ end
83
+
84
+ def each(&block)
85
+ @accounts.each do |account_id,account|
86
+ yield account_id, account
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -21,7 +21,7 @@
21
21
 
22
22
  module RsUserPolicy
23
23
  class User
24
- attr_reader :email, :href
24
+ attr_reader :email, :href, :permissions
25
25
 
26
26
  # Initializes read only attributes for an RsUserPolicy::User
27
27
  #
@@ -67,10 +67,12 @@ module RsUserPolicy
67
67
  if options[:dry_run]
68
68
  Hash[current_permissions.map{|p| [p.href, p.role_title]}]
69
69
  else
70
- RsUserPolicy::RightApi::PermissionUtilities.destroy_permissions(
70
+ retval = RsUserPolicy::RightApi::PermissionUtilities.destroy_permissions(
71
71
  current_permissions,
72
72
  client
73
73
  )
74
+ @permissions.delete(account_href)
75
+ retval
74
76
  end
75
77
  end
76
78
 
@@ -91,7 +93,9 @@ module RsUserPolicy
91
93
  existing_api_permissions_response = get_api_permissions(account_href)
92
94
  existing_api_permissions = Hash[existing_api_permissions_response.map{|p| [p.role_title, p] }]
93
95
  if permissions.length == 0
94
- return clear_permissions(account_href, client, options), {}
96
+ removed = clear_permissions(account_href, client, options)
97
+ @permissions.delete(account_href)
98
+ return removed, {}
95
99
  else
96
100
  permissions_to_remove = (existing_api_permissions.keys - permissions).map{|p| existing_api_permissions[p]}
97
101
  remove_response = Hash[permissions_to_remove.map{|p| [p.href, p.role_title]}]
@@ -112,6 +116,8 @@ module RsUserPolicy
112
116
  add_response = RsUserPolicy::RightApi::PermissionUtilities.create_permissions(permissions_to_add, client)
113
117
  end
114
118
 
119
+ @permissions[account_href] = client.permissions.index(:filter => ["user_href==#{@href}"]) unless options[:dry_run]
120
+
115
121
  return remove_response, Hash[add_response[@href].keys.map{|p| [add_response[@href][p],p]}]
116
122
  end
117
123
  end
@@ -75,9 +75,7 @@ module RsUserPolicy
75
75
  # @return [Array<String>] The roles assigned to the user
76
76
  def get_roles(email)
77
77
  # TODO: This seems expensive to do in an accessor?
78
- unless @user_assignments.key?(email)
79
- @user_assignments[email] = {'roles' => ['immutable']}
80
- end
78
+ add_user(email)
81
79
  @user_assignments[email]['roles']
82
80
  end
83
81
 
@@ -101,6 +99,37 @@ module RsUserPolicy
101
99
  File.open(options[:filename], 'w') {|f| f.write(JSON.pretty_generate(@user_assignments))}
102
100
  end
103
101
 
102
+ # Returns a list of all user emails which have a user assignment in the source
103
+ #
104
+ # @return [Array<String>] An array of email addresses for users with a user assignment
105
+ def list
106
+ @user_assignments.keys
107
+ end
108
+
109
+ # Returns a hash which represents the user specified by the email address specified
110
+ # If the user does not exist the (see #add_user) method will be called and the
111
+ # user will be created.
112
+ #
113
+ # @param [String] email The email address of the user to fetch
114
+ #
115
+ # @return [Hash] A hash of key/value pairs to be passed to the RightScale API for Users#create. This will also include a "roles" key, and may also include any other keys returned by the source
116
+ def [](email)
117
+ add_user(email)
118
+ end
119
+
120
+ # Adds a user to user_assignments. If the user already exists the existing record
121
+ # will be returned. Otherwise the user will be created with a single role of "immutable"
122
+ #
123
+ # @param [String] email The email address of the user to create or return
124
+ # @param [Hash] options Hash of property key/value pairs for the user. The following options are known, but there can be any key in thi hash
125
+ # @option options [Array<String>] "roles" An array of role names for the user
126
+ #
127
+ # @return [Hash] The added or existing user where they key is the users email, and the value is a hash of key/value pairs of user properties.
128
+ def add_user(email, options={})
129
+ options = {"roles" => ["immutable"]}.merge(options)
130
+ @user_assignments[email] || @user_assignments[email] = options
131
+ end
132
+
104
133
  private
105
134
 
106
135
  def validate()
@@ -22,6 +22,13 @@
22
22
  module RsUserPolicy
23
23
  module UserAssignments
24
24
  module UserAssignments
25
+ # Returns a list of all user emails which have a user assignment in the source
26
+ #
27
+ # @return [Array<String>] An array of email addresses for users with a user assignment
28
+ def list
29
+ raise NotImplementedError, "Please implement this in your concrete class"
30
+ end
31
+
25
32
  # @return [Int] The number of users in the user assignments object
26
33
  def length
27
34
  raise NotImplementedError, "Please implement this in your concrete class"
@@ -57,6 +64,29 @@ module RsUserPolicy
57
64
  def serialize(options={})
58
65
  raise NotImplementedError, "Please implement this in your concrete class"
59
66
  end
67
+
68
+ # Returns a hash which represents the user specified by the email address specified
69
+ # If the user does not exist the (see #add_user) method will be called and the
70
+ # user will be created.
71
+ #
72
+ # @param [String] email The email address of the user to fetch
73
+ #
74
+ # @return [Hash] A hash of key/value pairs to be passed to the RightScale API for Users#create. This will also include a "roles" key, and may also include any other keys returned by the source
75
+ def [](email)
76
+ raise NotImplementedError, "Please implement this in your concrete class"
77
+ end
78
+
79
+ # Adds a user to user_assignments. If the user already exists the existing record
80
+ # will be returned. Otherwise the user will be created with a single role of "immutable"
81
+ #
82
+ # @param [String] email The email address of the user to create or return
83
+ # @param [Hash] options Hash of property key/value pairs for the user. The following options are known, but there can be any key in thi hash
84
+ # @option options [Array<String>] "roles" An array of role names for the user
85
+ #
86
+ # @return [Hash] The added or existing user where they key is the users email, and the value is a hash of key/value pairs of user properties.
87
+ def add_user(email, options={})
88
+ raise NotImplementedError, "Please implement this in your concrete class"
89
+ end
60
90
  end
61
91
  end
62
92
  end
@@ -37,23 +37,26 @@ module RsUserPolicy
37
37
  # include the specified users. The users RightScale API href is used
38
38
  # as the unique identifier for deduplication
39
39
  #
40
- # @param [Array<RightApi::ResourceDetail>] An array of ResourceDetail from the Right API Client for users. Returned by client.users.index
40
+ # @param [Hash] users A hash where the key is a users href, and the value is the users email
41
41
  def add_users(users)
42
- users.each do |user|
43
- unless @users_by_href.has_key?(user.href)
44
- @users_by_href[user.href] = RsUserPolicy::User.new(user.email, user.href)
42
+ users.each do |href, email|
43
+ unless @users_by_href.has_key?(href)
44
+ @users_by_href[href] = RsUserPolicy::User.new(email, href)
45
45
  end
46
46
  end
47
47
  end
48
48
 
49
- def [](idx)
50
- @users_by_href[idx]
49
+ def [](href)
50
+ @users_by_href[href]
51
51
  end
52
52
 
53
53
  def add_permissions(account_href, permissions)
54
54
  permissions.each do |permission|
55
55
  user_href = permission.user.href
56
- add_users([permission.user])
56
+ unless @users_by_href.has_key?(user_href)
57
+ user = permission.user.show()
58
+ @users_by_href[user.href] = RsUserPolicy::User.new(user.email, user.href)
59
+ end
57
60
  @users_by_href[user_href].add_permission(account_href, permission)
58
61
  end
59
62
  end
@@ -56,5 +56,15 @@ module RsUserPolicy
56
56
  hash.select{|k,v| !order.include?(v) }.each{|k,v| yield k,v }
57
57
  end
58
58
 
59
+ def self.generate_compliant_password(size=12)
60
+ chars = (
61
+ ('a'..'z').to_a +
62
+ ('A'..'Z').to_a +
63
+ ('0'..'9').to_a +
64
+ ["!","@","#","$","%","^","&","*","(",")","-","_","=","+"]
65
+ ) - %w(i o 0 1 l 0)
66
+ (1..size).collect{|a| chars[rand(chars.size)] }.join
67
+ end
68
+
59
69
  end
60
70
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rs_user_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-15 00:00:00.000000000 Z
12
+ date: 2012-12-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: right_api_client
@@ -54,6 +54,7 @@ files:
54
54
  - lib/rs_user_policy/audit_log.rb
55
55
  - lib/rs_user_policy/policy/json_policy.rb
56
56
  - lib/rs_user_policy/policy/policy.rb
57
+ - lib/rs_user_policy/right_api/multi_client.rb
57
58
  - lib/rs_user_policy/right_api/permission_utilities.rb
58
59
  - lib/rs_user_policy/user.rb
59
60
  - lib/rs_user_policy/user_assignments/json_user_assignments.rb
@@ -79,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
80
  version: '0'
80
81
  segments:
81
82
  - 0
82
- hash: -4206610464842566721
83
+ hash: 3250796022551410117
83
84
  required_rubygems_version: !ruby/object:Gem::Requirement
84
85
  none: false
85
86
  requirements:
@@ -88,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
89
  version: '0'
89
90
  segments:
90
91
  - 0
91
- hash: -4206610464842566721
92
+ hash: 3250796022551410117
92
93
  requirements: []
93
94
  rubyforge_project:
94
95
  rubygems_version: 1.8.24