rs_user_policy 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -27,10 +27,15 @@ The second input file is the user assignments JSON file which assigns users to r
27
27
  User Assignment JSON should be in the following format
28
28
 
29
29
  {
30
- "ryan.geyer@rightscale.com": "role_name"
30
+ "ryan.geyer@rightscale.com": {
31
+ "roles": [
32
+ "role_name"
33
+ ]
34
+ }
31
35
  }
32
36
 
33
37
  There are two default roles which do not need to be defined in the policy file. "immutable" which indicates that no changes should be performed on the user, and "delete" which indicates that all permissions should be removed for the user in all accounts.
38
+ Both "immutable" and "delete", if present will take precedence over any other roles assigned to the user.
34
39
 
35
40
  Many email:role pairs can be specified. in the user assignments JSON
36
41
 
@@ -50,8 +55,16 @@ So, given a policy file like;
50
55
  And a user assignments file like;
51
56
 
52
57
  {
53
- "user1@email.com": "team1",
54
- "user2@email.com": "team2"
58
+ "user1@email.com": {
59
+ "roles": [
60
+ "team1"
61
+ ]
62
+ },
63
+ "user2@email.com": {
64
+ "roles": [
65
+ "team2"
66
+ ]
67
+ }
55
68
  }
56
69
 
57
70
  And operating on the accounts 12345 and 23456;
@@ -70,7 +83,11 @@ Second is the user_assignments-<timestamp>.json file. This will be a combinatio
70
83
 
71
84
  == TODO
72
85
 
73
- * Allow a user to belong to more than one "role"
86
+ * Allow users to be added who are not already invited to any RS parent or child account
87
+ * 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
+ * Perhaps allow a role to inherit from another, or be a concatenation of several?
89
+ * 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
+ * HTTP Code: 422, Response body: A user must have the observer role. (RightApi::Exceptions::ApiException)
74
91
 
75
92
  == Copyright
76
93
 
data/bin/rs_user_policy CHANGED
@@ -24,8 +24,7 @@
24
24
  require 'trollop'
25
25
  require 'right_api_client'
26
26
  require 'logger'
27
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'audit_log'))
28
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'utilities'))
27
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rs_user_policy'))
29
28
 
30
29
  opts = Trollop::options do
31
30
  banner = "Manages users across many different child accounts of a RightScale Enterprise Master Account"
@@ -42,86 +41,47 @@ log = Logger.new(STDOUT)
42
41
  timestamp = Time::now.to_i
43
42
  deleted_users = []
44
43
  accounts = []
45
- user_href_resource_map = {}
46
- user_email_resource_map = {}
47
- permission_delete_order = [
48
- 'enterprise_manager',
49
- 'admin',
50
- 'security_manager',
51
- 'actor',
52
- 'billing',
53
- 'server_login',
54
- 'publisher',
55
- 'designer',
56
- 'library',
57
- 'lite_user',
58
- 'observer'
59
- ]
60
-
61
- 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") if opts[:dry_run]
44
+
45
+ if opts[:dry_run]
46
+ 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")
47
+ end
62
48
 
63
49
  user_assignments_output = "user_assignments-#{timestamp}.json"
64
50
 
65
- audit_log = AuditLog.new opts.merge(:timestamp => timestamp)
66
- policy = {}
67
-
68
- def valid_policy_json_file?(json_file, &block)
69
- # TODO: Also validate that the policy file is in the correct form.
70
- # I.E. {
71
- # "policy-name": {
72
- # "account-href-or-default": ["list", "of", "permissions"]
73
- # }
74
- #}
75
- yield JSON.parse(File.read(json_file))
76
- return true
77
- rescue JSON::ParserError
78
- return false
79
- end
51
+ audit_log = RsUserPolicy::AuditLog.new opts.merge(:timestamp => timestamp)
80
52
 
81
- if File.exists? opts[:policy]
82
- if valid_policy_json_file?(opts[:policy]) do |p|
83
- policy = p
84
- end
85
- else
86
- log.fatal("The policy file named #{opts[:policy]} is not a properly formatted json file!")
87
- exit 1
88
- end
89
- else
90
- log.fatal("The policy file named #{opts[:policy]} was not found!")
53
+ policy = nil
54
+ begin
55
+ policy = RsUserPolicy::Policy::JsonPolicy.new(:filename => opts[:policy])
56
+ rescue Exception => e
57
+ log.fatal("Unable to initialize policy from filename #{opts[:policy]}. Error: #{e.message}")
91
58
  exit 1
92
59
  end
93
60
 
94
- user_assignments = {}
95
- if opts[:user_assignments]
96
- if File.exists? opts[:user_assignments]
97
- user_assignments = JSON.parse(File.read(opts[:user_assignments]))
98
- else
99
- log.warn("The user_assigments file named #{opts[:user_assignments]} was not found. All users will be treated as immutable and written to the user_assigments output file.")
100
- end
101
- else
102
- log.warn("No user_assignments file was specified. All users will be treated as immutable and written to the user_assigments output file.")
61
+ user_assignments_options = opts[:user_assignments] ? { :filename => opts[:user_assignments] } : {}
62
+ user_assignments = RsUserPolicy::UserAssignments::JsonUserAssignments.new(user_assignments_options)
63
+ if user_assignments.length == 0
64
+ log.warn("No user_assignments file was specified or the file could not be found. All users will be treated as immutable and written to the user_assigments output file.")
103
65
  end
104
66
 
67
+ user_collection = RsUserPolicy::UserCollection.new
68
+
105
69
  client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => opts[:rs_acct_num])
106
70
  master_account = client.accounts(:id => opts[:rs_acct_num]).show()
107
- client.users().index.each do |user|
108
- user_href_resource_map[user.href] = user
109
- user_email_resource_map[user.email] = user
110
- end
71
+ user_collection.add_users(client.users.index)
72
+ user_collection.add_permissions(master_account.href, client.permissions.index)
111
73
 
112
74
  accounts << {:client => client, :href => master_account.href, :name => master_account.name}
113
75
 
114
76
  log.info("Operating on the Enterprise Master Account #{master_account.name}")
115
77
 
116
78
  begin
117
- client.child_accounts().index.each do |child|
118
- child_client = RightApi::Client.new(:email => opts[:rs_email], :password => opts[:rs_pass], :account_id => Utilities.id_from_href(child.href))
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))
119
81
  accounts << {:client => child_client, :href => child.href, :name => child.name}
120
82
 
121
- child_client.users().index.each do |user|
122
- user_href_resource_map[user.href] = user
123
- user_email_resource_map[user.email] = user
124
- end
83
+ user_collection.add_users(child_client.users.index)
84
+ user_collection.add_permissions(child.href, child_client.permissions.index)
125
85
  end
126
86
  rescue RightApi::Exceptions::ApiException => e
127
87
  if e.message =~ /Permission denied/
@@ -131,80 +91,38 @@ rescue RightApi::Exceptions::ApiException => e
131
91
  end
132
92
  end
133
93
 
134
- accounts.each do |account|
135
- child_client = account[:client]
136
- users = {}
137
- user_email_resource_map.each do |email,user|
138
- users[email] = {}
139
- end
140
-
141
- log.info("#{account[:name]} - #{account[:href]}")
94
+ begin
95
+ accounts.each do |account|
96
+ child_client = account[:client]
142
97
 
143
- permissions = child_client.permissions().index
144
- log.info("There are #{permissions.length} unique permissions")
145
- permissions.each do |permission|
146
- user_email = user_href_resource_map[permission.user.href].email || permission.user.href
147
- users[user_email] = {} unless users.key?(user_email)
148
- users[user_email][permission.role_title] = permission.href
149
- end
150
- log.info("There are #{users.length} unique Users")
98
+ log.info("#{account[:name]} - #{account[:href]}")
99
+ user_collection.users.each do |user|
100
+ email = user.email
101
+ user_role = user_assignments.get_roles(email)
102
+ existing_permissions = user.get_api_permissions(account[:href])
151
103
 
152
- users.each do |email,user|
153
- unless user_assignments.key?(email)
154
- user_assignments[email] = 'immutable'
155
- end
156
- # Allow some different options about possibly prompting the user etc
157
- # TODO: Put this in a begin/rescue/end that allows stuff to continue and create an audit_log in the event of an error
158
- case user_assignments[email]
159
- when 'immutable'
160
- # By design, do nothing
161
- when 'delete'
104
+ # Allow some different options about possibly prompting the user etc
105
+ if user_role.include?('delete')
162
106
  # Note: This is an explicit delete across all master and child accounts
163
107
  # a user can also be effectively deleted if they have an empty list of
164
108
  # permissions for a particular account
165
109
  unless opts[:dry_run]
166
- log.debug("Gonna delete #{JSON.pretty_generate(user)}")
167
-
168
- Utilities.yield_on_keys_in_order(permission_delete_order, user) do |role_title,perm_href|
169
- log.debug("I KEELZ U PERMISSION! #{email} - #{role_title}")
170
- child_client.permissions(:id => Utilities.id_from_href(perm_href)).destroy()
171
- deleted_users << email
172
- end
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)
173
113
  end
174
114
  audit_log.add_entry(email, account[:name], 'deleted', 'deleted')
175
- else
176
- user_policy = []
177
- if policy[user_assignments[email]].key?(account[:href])
178
- user_policy = policy[user_assignments[email]][account[:href]]
179
- elsif policy[user_assignments[email]].key?('default')
180
- user_policy = policy[user_assignments[email]]['default']
181
- end
182
- removed = (user_policy.length == 0) ? user.keys : user.keys - user_policy
183
- added = user_policy - user.keys
184
- changes = "-#{removed} +#{added}"
185
- unless opts[:dry_run]
186
- # Convert the role_title array into a hash with the hrefs
187
- remove_hash = Hash[removed.map {|role| [role, user[role]]}]
188
-
189
- Utilities.yield_on_keys_in_order(permission_delete_order, remove_hash) do |role_title, href|
190
- child_client.permissions(:id => Utilities.id_from_href(href)).destroy()
191
- end
192
-
193
- if added.length > 0
194
- add_hash = Hash[added.map {|x| [x, nil]}]
195
- Utilities.yield_on_keys_in_order(['observer'], add_hash) do |role_title,foo|
196
- child_client.permissions.create({'permission[user_href]' => user_email_resource_map[email].href, 'permission[role_title]' => role_title})
197
- end
198
- end
199
- end
115
+ 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)
118
+ changes = "-#{removed.values} +#{added.values}"
200
119
  audit_log.add_entry(email, account[:name], 'update_permissions', changes) unless removed.length + added.length == 0
120
+ end
201
121
  end
202
122
  end
123
+ rescue RightApi::Exceptions::ApiException => e
124
+ log.fatal("A RightScale API exception occurred - #{e}")
203
125
  end
204
126
 
205
- deleted_users.each do |email|
206
- user_assignments.delete(email)
207
- end
208
-
209
- File.open(user_assignments_output, 'w') {|f| f.write(JSON.pretty_generate(user_assignments))}
127
+ user_assignments.serialize(:filename => user_assignments_output)
210
128
  audit_log.write_file
@@ -0,0 +1,59 @@
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
+ class AuditLog
24
+
25
+ attr_accessor :filename, :audit_log
26
+
27
+ # Initializes a new AuditLog
28
+ #
29
+ # @param [Hash] options A hash of options that impact the audit log filename.
30
+ # @option options [String] :timestamp The timestamp to append to the filename
31
+ # @option options [Bool] :dry_run A boolean indicating if this is a dry run
32
+ def initialize(options={})
33
+ timestamp = options[:timestamp] || Time.now.to_i
34
+ @audit_log = {}
35
+ @filename = "audit_log#{options[:dry_run] ? '_dryrun' : ''}-#{timestamp}.json"
36
+ end
37
+
38
+ # Adds a new entry to the audit log
39
+ #
40
+ # @param [String] email The email address of the user impacted by the change
41
+ # @param [String] account The account name impacted by the change
42
+ # @param [String] action The action performed. Expected options are ['update_permissions', 'deleted']
43
+ # @param [String] changes A free form description of the changes
44
+ def add_entry(email, account, action, changes)
45
+ @audit_log[email] = [] unless audit_log[email]
46
+ @audit_log[email] << {
47
+ :account => account,
48
+ :action => action,
49
+ :changes => changes
50
+ }
51
+ end
52
+
53
+ # Writes the audit log to a file
54
+ #
55
+ def write_file
56
+ File.open(@filename, 'w') {|f| f.write(JSON.pretty_generate(@audit_log))}
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,88 @@
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
+ require File.expand_path(File.join(File.dirname(__FILE__), 'policy'))
23
+ require 'json'
24
+
25
+ module RsUserPolicy
26
+ module Policy
27
+ class JsonPolicy
28
+ include Policy
29
+
30
+ # Initializes a new Policy
31
+ #
32
+ # If more than one source is passed into options, the order of preference will be
33
+ # [:json, :json_str, :filename]
34
+ #
35
+ # @param [Hash] options A hash of inputs for the new JSONPolicy
36
+ # @option options [Hash] :json A hash containing the policy
37
+ # @option options [String] :json_str A JSON string containing the policy
38
+ # @option options [String] :filename Path and filename to a file containing the policy in JSON
39
+ #
40
+ # @raise [ArgumentError] If neither a filename or json object were supplied
41
+ # @raise [Errno::ENOENT] If :filename was specified but the policy file does not exist
42
+ # @raise [JSON::ParseError] If the policy is not valid JSON
43
+ def initialize(options={})
44
+ if ([:filename, :json, :json_str] & options.keys()).empty?
45
+ raise ArgumentError, "You must supply either a filename, JSON string, or a JSON object"
46
+ end
47
+
48
+ if options.has_key?(:json)
49
+ @policy = options[:json]
50
+ elsif options.has_key?(:json_str)
51
+ @policy = JSON.parse(options[:json_str])
52
+ else
53
+ @policy = JSON.parse(File.read(options[:filename]))
54
+ end
55
+
56
+ validate()
57
+ end
58
+
59
+ # Returns an array of permissions for a particular role in a particular RightScale account
60
+ #
61
+ # @param [Array<String>] roles An array of role names for which permissions should be fetched
62
+ # @param [String] account_href A RightScale API 1.5 href for the RightScale account
63
+ #
64
+ # @return [Array<String>] A list of permissions for the role and account pair requested. An empty array is returned if no policy exists for the requested pair
65
+ def get_permissions(roles, account_href)
66
+ permissions = []
67
+ roles.each do |role|
68
+ if @policy.has_key?(role)
69
+ permissions = permissions + (@policy[role][account_href] || @policy[role]['default'] || [])
70
+ end
71
+ end
72
+ permissions.uniq
73
+ end
74
+
75
+ private
76
+
77
+ def validate()
78
+ # TODO: Also validate that the policy file is in the correct form.
79
+ # I.E. {
80
+ # "policy-name": {
81
+ # "account-href-or-default": ["list", "of", "permissions"]
82
+ # }
83
+ #}
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -19,36 +19,18 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- class Utilities
23
-
24
- # Uses a regex to parse a RightScale API resource id from it's relative href
25
- #
26
- # === Parameters
27
- # href(String):: The relative href of the RightScale API resource
28
- #
29
- # === Return
30
- # The resources ID
31
- def self.id_from_href(href)
32
- matches = /.*\/([0-9]*)/.match(href)
33
- matches[1] || nil
34
- end
35
-
36
- # Operates on the key/value pairs in a hash in the order specified in 'order'
37
- # followed by any key/value pairs not specified in the order
38
- #
39
- # === Parameters
40
- # order(Array):: An array containing keys in the order they should be yielded to the block
41
- # hash(Hash):: The hash to operate on in the specified order
42
- # block(Closure):: A closure to yield to
43
- def self.yield_on_keys_in_order(order, hash, &block)
44
- order.each do |key|
45
- if hash.key?(key)
46
- yield key, hash.delete(key)
22
+ module RsUserPolicy
23
+ module Policy
24
+ module Policy
25
+ # Returns an array of permissions for a particular role in a particular RightScale account
26
+ #
27
+ # @param [Array<String>] roles An array of role names for which permissions should be fetched
28
+ # @param [String] account_href A RightScale API 1.5 href for the RightScale account
29
+ #
30
+ # @return [Array<String>] A list of permissions for the role and account pair requested. An empty array is returned if no policy exists for the requested pair
31
+ def get_permissions(roles, account_href)
32
+ raise NotImplementedError, "Please implement this in your concrete class"
47
33
  end
48
34
  end
49
- hash.each do |key,val|
50
- yield key, val
51
- end
52
35
  end
53
-
54
36
  end
@@ -0,0 +1,129 @@
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
+ # A set of utility methods for manipulating permissions using the RightScale right_api_client gem
25
+ #
26
+ # Allows bulk actions on permissions without worrying about the complexity of retrying,
27
+ # creating/deleting in the correct order, and the like.
28
+ #
29
+ class PermissionUtilities
30
+
31
+ @@permission_delete_order = [
32
+ 'enterprise_manager',
33
+ 'admin',
34
+ 'security_manager',
35
+ 'actor',
36
+ 'billing',
37
+ 'server_login',
38
+ 'publisher',
39
+ 'designer',
40
+ 'library',
41
+ 'lite_user',
42
+ 'observer'
43
+ ]
44
+
45
+ # Destroys all passed in permissions with the specified client.
46
+ # This method handles deleting permissions in the appropriate order to avoid the dreaded;
47
+ # RightApi::Exceptions::ApiException: Error: HTTP Code: 422, Response body: A user must have the observer role.
48
+ # TODO: Handle a 422 resulting from calling delete too quickly and attempting to remove "observer" when other deletes have not been committed
49
+ #
50
+ # @param [Array<RightApi::ResourceDetail>] permissions
51
+ # A hash of permissions where the key is the RightScale API href, and the
52
+ # value is the role_title. These permissions can be for one or many users, allowing a bulk actions.
53
+ # @param [RightApi::Client] client
54
+ # An active RightApi::Client instance for the account referenced in account_href
55
+ #
56
+ # @raise [RightApi::Exceptions::ApiException] If an unrecoverable API error has occurred.
57
+ #
58
+ # @return [Hash] A hash where the keys are the permission hrefs destroyed, and the values are the role_title of those permissions
59
+ def self.destroy_permissions(permissions, client)
60
+ perms_hash = {}
61
+ permissions.each{|p| perms_hash[p.href] = p.role_title }
62
+ RsUserPolicy::Utilities.yield_on_values_in_order(@@permission_delete_order, perms_hash) do |perm_href,role_title|
63
+ client.permissions(:id => RsUserPolicy::Utilities.id_from_href(perm_href)).destroy()
64
+ end
65
+ perms_hash
66
+ end
67
+
68
+ # Creates all the passed in permissions using the supplied client.
69
+ # This method handles creating permissions with "observer" first in order to avoide the dreaded;
70
+ # RightApi::Exceptions::ApiException: Error: HTTP Code: 422, Response body: A user must have the observer role.
71
+ #
72
+ # @param [Hash] permissions
73
+ # A hash where the key is a RightScale API User href, and the value is a hash where the key is the permission role_title that the user should be granted, and the value is nil.
74
+ #
75
+ # @param [RightApi::Client] client
76
+ # An active RightApi::Client instance for the account referenced in account_href
77
+ #
78
+ # @raise [RightApi::Exceptions::ApiException] If an unrecoverable API error has occurred.
79
+ #
80
+ # @return [Hash] The permissions input hash, where the nil values have been replaced with the href of the permission which was created.
81
+ #
82
+ # @example Create "observer" and "admin" permissions for two users
83
+ # client = RightApi::Client.new(</snip>)
84
+ #
85
+ # permissions = {
86
+ # '/api/users/123' => {
87
+ # 'observer' => nil,
88
+ # 'admin' => nil
89
+ # },
90
+ # '/api/users/456' => {
91
+ # 'observer' => nil,
92
+ # 'admin' => nil
93
+ # }
94
+ # }
95
+ #
96
+ # response = RsUserPolicy::RightApi::PermissionUtilities.create_permissions(permissions, client)
97
+ #
98
+ # puts JSON.pretty_generate(response)
99
+ #
100
+ # # Output would be as follows
101
+ # {
102
+ # '/api/users/123' => {
103
+ # 'observer' => '/api/permissions/1',
104
+ # 'admin' => '/api/permissions/2'
105
+ # },
106
+ # '/api/users/456' => {
107
+ # 'observer' => '/api/permissions/3',
108
+ # 'admin' => '/api/permissions/4'
109
+ # }
110
+ # }
111
+ def self.create_permissions(permissions, client)
112
+ permissions.each do |user_href,perm_ary|
113
+ user_perms_hash = Hash[perm_ary.keys.map{|p| [p, user_href]}]
114
+ RsUserPolicy::Utilities.yield_on_values_in_order(['observer'], user_perms_hash) do |role_title,user_href|
115
+ created_permission = client.permissions.create(
116
+ {
117
+ 'permission[user_href]' => user_href,
118
+ 'permission[role_title]' => role_title
119
+ }
120
+ )
121
+ permissions[user_href][role_title] = created_permission.href
122
+ end
123
+ end
124
+ permissions
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,119 @@
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
+ class User
24
+ attr_reader :email, :href
25
+
26
+ # Initializes read only attributes for an RsUserPolicy::User
27
+ #
28
+ # @param [String] email The email address of the user
29
+ # @param [String] href The RightScale API href of the user
30
+ def initialize(email, href)
31
+ @email = email
32
+ @href = href
33
+ @permissions = {}
34
+ end
35
+
36
+ # Adds a single permission for a single RightScale account
37
+ #
38
+ # @param [String] account_href The RightScale API href of the account
39
+ # @param [RightApi::ResourceDetail] permission A single RightApi::ResourceDetail for a permission.
40
+ def add_permission(account_href, permission)
41
+ @permissions[account_href] ||= []
42
+ @permissions[account_href] << permission
43
+ end
44
+
45
+ # Returns the RightScale permissions the user has for the specified account href
46
+ #
47
+ # @param [String] account_href The RightScale API href of the account
48
+ #
49
+ # @return [Array<RightApi::ResourceDetail>] An array of permission RightApi::ResourceDetail objects
50
+ def get_api_permissions(account_href)
51
+ @permissions[account_href] || []
52
+ end
53
+
54
+ # Removes all permissions for the user in the specified rightscale account using the supplied client
55
+ #
56
+ # @param [String] account_href The RightScale API href of the account
57
+ # @param [RightApi::Client] client An active RightApi::Client instance for the account referenced in account_href
58
+ # @param [Hash] options Optional parameters
59
+ # @option options [Bool] :dry_run If true, no API calls will be made, but the return value will contain the actions which would have been taken
60
+ #
61
+ # @raise [RightApi::Exceptions::ApiException] If an unrecoverable API error has occurred.
62
+ #
63
+ # @return [Hash] A hash where the keys are the permission hrefs destroyed, and the keys are the role_title of those permissions
64
+ def clear_permissions(account_href, client, options={})
65
+ options = {:dry_run => false}.merge(options)
66
+ current_permissions = get_api_permissions(account_href)
67
+ if options[:dry_run]
68
+ Hash[current_permissions.map{|p| [p.href, p.role_title]}]
69
+ else
70
+ RsUserPolicy::RightApi::PermissionUtilities.destroy_permissions(
71
+ current_permissions,
72
+ client
73
+ )
74
+ end
75
+ end
76
+
77
+ # Removes and adds permissions as appropriate so that the users current permissions reflect
78
+ # the desired set passed in as "permissions"
79
+ #
80
+ # @param [Array<String>] permissions The list of desired permissions for the user in the specified account
81
+ # @param [String] account_href The RightScale API href of the account
82
+ # @param [RightApi::Client] client An active RightApi::Client instance for the account referenced in account_href
83
+ # @param [Hash] options Optional parameters
84
+ # @option options [Bool] :dry_run If true, no API calls will be made, but the return value will contain the actions which would have been taken
85
+ #
86
+ # @raise [RightApi::Exceptions::ApiException] If an unrecoverable API error has occurred.
87
+ #
88
+ # @return [Hash,Hash] A tuple where two hashes are returned. The keys of the hashes are the href of the permission, and the values are the role_title of the permission. The first hash is the permissions removed, and the second hash is the permissions added
89
+ def set_api_permissions(permissions, account_href, client, options={})
90
+ options = {:dry_run => false}.merge(options)
91
+ existing_api_permissions_response = get_api_permissions(account_href)
92
+ existing_api_permissions = Hash[existing_api_permissions_response.map{|p| [p.role_title, p] }]
93
+ if permissions.length == 0
94
+ return clear_permissions(account_href, client, options), {}
95
+ else
96
+ permissions_to_remove = (existing_api_permissions.keys - permissions).map{|p| existing_api_permissions[p]}
97
+ remove_response = Hash[permissions_to_remove.map{|p| [p.href, p.role_title]}]
98
+ unless options[:dry_run]
99
+ remove_response = RsUserPolicy::RightApi::PermissionUtilities.destroy_permissions(permissions_to_remove, client)
100
+ end
101
+
102
+ permissions_to_add = {
103
+ @href => Hash[(permissions - existing_api_permissions.keys).map{|p| [p,nil]}]
104
+ }
105
+ add_response = {}
106
+ if options[:dry_run]
107
+ href_idx = 0
108
+ add_response = {
109
+ @href => Hash[(permissions - existing_api_permissions.keys).map{|p| [p,(href_idx += 1)]}]
110
+ }
111
+ else
112
+ add_response = RsUserPolicy::RightApi::PermissionUtilities.create_permissions(permissions_to_add, client)
113
+ end
114
+
115
+ return remove_response, Hash[add_response[@href].keys.map{|p| [add_response[@href][p],p]}]
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,114 @@
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
+ require File.expand_path(File.join(File.dirname(__FILE__), 'user_assignments'))
23
+ require 'json'
24
+
25
+ module RsUserPolicy
26
+ module UserAssignments
27
+ class JsonUserAssignments
28
+ include UserAssignments
29
+
30
+ # Initializes a new UserAssignments
31
+ #
32
+ # If more than one source is passed into options, the order of preference will be
33
+ # [:json, :json_str, :filename]
34
+ #
35
+ # @param [Hash] options A hash of inputs for the new JsonUserAssignments, where the keys are;
36
+ # @option options [Hash] :json A hash containing the user assignments
37
+ # @option options [String] :json_str A JSON string containing the user assignments
38
+ # @option options [String] :filename Path and filename to a file containing the user assignments in JSON
39
+ #
40
+ # @raise [Errno::ENOENT] If :filename was specified but the policy file does not exist
41
+ # @raise [JSON::ParserError] If the policy is not valid JSON
42
+ def initialize(options={})
43
+ begin
44
+ if options.has_key?(:json)
45
+ @user_assignments = options[:json]
46
+ elsif options.has_key?(:json_str)
47
+ @user_assignments = JSON.parse(options[:json_str])
48
+ elsif options.has_key?(:filename)
49
+ @user_assignments = JSON.parse(File.read(options[:filename]))
50
+ else
51
+ @user_assignments = {}
52
+ end
53
+ rescue Errno::ENOENT, JSON::ParserError
54
+ @user_assignments = {}
55
+ end
56
+
57
+ validate()
58
+ end
59
+
60
+ # @return [Int] The number of users in the user assignments object
61
+ def length
62
+ @user_assignments.length
63
+ end
64
+
65
+ # @return [Int] The number of users in the user assignments object
66
+ def size
67
+ self.length
68
+ end
69
+
70
+ # Returns the roles assigned to the user. If the user does not exist
71
+ # they should be automatically created with the role "immutable"
72
+ #
73
+ # @param [String] email The email address for the user
74
+ #
75
+ # @return [Array<String>] The roles assigned to the user
76
+ def get_roles(email)
77
+ # TODO: This seems expensive to do in an accessor?
78
+ unless @user_assignments.key?(email)
79
+ @user_assignments[email] = {'roles' => ['immutable']}
80
+ end
81
+ @user_assignments[email]['roles']
82
+ end
83
+
84
+ # Deletes a user from the user assignments
85
+ #
86
+ # @param [String] email The email address for the user
87
+ def delete(email)
88
+ @user_assignments.delete(email)
89
+ end
90
+
91
+ # Commits any changes made to the UserAssignments object back to
92
+ # it's original data store. This is an opportunity to perform
93
+ # DB flushes or write back to a source file.
94
+ #
95
+ # @param [Hash] options A hash containing only one key;
96
+ # @option options [String] :filename The filename to write out the JSON state of this JsonUserAssignments object
97
+ #
98
+ # @raise [ArgumentError] When no output file is specified
99
+ def serialize(options={})
100
+ raise ArgumentError, "You must specify the :filename option" unless options.has_key?(:filename)
101
+ File.open(options[:filename], 'w') {|f| f.write(JSON.pretty_generate(@user_assignments))}
102
+ end
103
+
104
+ private
105
+
106
+ def validate()
107
+ # TODO: Also validate that the user assignments file is in the correct form.
108
+ # I.E. {
109
+ # "email@address.com": "role"
110
+ #}
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,62 @@
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 UserAssignments
24
+ module UserAssignments
25
+ # @return [Int] The number of users in the user assignments object
26
+ def length
27
+ raise NotImplementedError, "Please implement this in your concrete class"
28
+ end
29
+
30
+ # @return [Int] The number of users in the user assignments object
31
+ def size
32
+ raise NotImplementedError, "Please implement this in your concrete class"
33
+ end
34
+
35
+ # Returns the roles assigned to the user. If the user does not exist
36
+ # they should be automatically created with the role "immutable"
37
+ #
38
+ # @param [String] email The email address for the user
39
+ #
40
+ # @return [Array<String>] The roles assigned to the user
41
+ def get_roles(email)
42
+ raise NotImplementedError, "Please implement this in your concrete class"
43
+ end
44
+
45
+ # Deletes a user from the user assignments
46
+ #
47
+ # @param [String] email The email address for the user
48
+ def delete(email)
49
+ raise NotImplementedError, "Please implement this in your concrete class"
50
+ end
51
+
52
+ # Commits any changes made to the UserAssignments object back to
53
+ # it's original data store. This is an opportunity to perform
54
+ # DB flushes or write back to a source file.
55
+ #
56
+ # @param [Hash] options A hash of values which may be required to serialize
57
+ def serialize(options={})
58
+ raise NotImplementedError, "Please implement this in your concrete class"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
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
+ class UserCollection
24
+
25
+ def initialize
26
+ @users_by_href = {}
27
+ end
28
+
29
+ # TODO: An .each iterator.. instead of a .users accessor
30
+
31
+ # @return [Array<RsUserPolicy::User>] An array of RsUserPolicy::User added to the collection
32
+ def users
33
+ @users_by_href.values
34
+ end
35
+
36
+ # Adds users to this collection only if the collection does not already
37
+ # include the specified users. The users RightScale API href is used
38
+ # as the unique identifier for deduplication
39
+ #
40
+ # @param [Array<RightApi::ResourceDetail>] An array of ResourceDetail from the Right API Client for users. Returned by client.users.index
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)
45
+ end
46
+ end
47
+ end
48
+
49
+ def [](idx)
50
+ @users_by_href[idx]
51
+ end
52
+
53
+ def add_permissions(account_href, permissions)
54
+ permissions.each do |permission|
55
+ user_href = permission.user.href
56
+ add_users([permission.user])
57
+ @users_by_href[user_href].add_permission(account_href, permission)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,60 @@
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
+ class Utilities
24
+ # Uses a regex to parse a RightScale API resource id from it's relative href
25
+ #
26
+ # @param [String] href The relative href of the RightScale API resource
27
+ # @return [String] The unique ID of the resource
28
+ def self.id_from_href(href)
29
+ matches = /.*\/([0-9]*)/.match(href)
30
+ matches[1] || nil
31
+ end
32
+
33
+ # Operates on the key/value pairs in a hash in the order specified in 'order'
34
+ # followed by any key/value pairs not specified in the order
35
+ #
36
+ # @param [Array] order An array containing keys in the order they should be yielded to the block
37
+ # @param [Hash] hash The hash to operate on in the specified order
38
+ # @param [Closure] block A closure to yield to
39
+ def self.yield_on_keys_in_order(order, hash, &block)
40
+ order.each do |key|
41
+ hash.select{|k,v| k == key}.each{|k,v| yield k,v }
42
+ end
43
+ hash.select{|k,v| !order.include?(k)}.each{|k,v| yield k,v}
44
+ end
45
+
46
+ # Operates on the key/value pairs in a hash in the order specified in 'order'
47
+ # followed by any key/value pairs not specified in the order
48
+ #
49
+ # @param [Array] order An array containing values in the order they should be yielded to the block
50
+ # @param [Hash] hash The hash to operate on in the specified order
51
+ # @param [Closure] block A closure to yield to
52
+ def self.yield_on_values_in_order(order, hash, &block)
53
+ order.each do |value|
54
+ hash.select{|k,v| v == value}.each{|k,v| yield k,v }
55
+ end
56
+ hash.select{|k,v| !order.include?(v) }.each{|k,v| yield k,v }
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,23 @@
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
+ glob_path = File.expand_path(File.join(File.dirname(__FILE__), 'rs_user_policy')) + '/**/*.rb'
23
+ Dir.glob(glob_path, &method(:require))
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.3
4
+ version: 0.0.4
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-11-20 00:00:00.000000000 Z
12
+ date: 2012-12-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: right_api_client
@@ -51,8 +51,16 @@ executables:
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
- - lib/audit_log.rb
55
- - lib/utilities.rb
54
+ - lib/rs_user_policy/audit_log.rb
55
+ - lib/rs_user_policy/policy/json_policy.rb
56
+ - lib/rs_user_policy/policy/policy.rb
57
+ - lib/rs_user_policy/right_api/permission_utilities.rb
58
+ - lib/rs_user_policy/user.rb
59
+ - lib/rs_user_policy/user_assignments/json_user_assignments.rb
60
+ - lib/rs_user_policy/user_assignments/user_assignments.rb
61
+ - lib/rs_user_policy/user_collection.rb
62
+ - lib/rs_user_policy/utilities.rb
63
+ - lib/rs_user_policy.rb
56
64
  - bin/rs_user_policy
57
65
  - LICENSE.txt
58
66
  - README.rdoc
@@ -71,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
79
  version: '0'
72
80
  segments:
73
81
  - 0
74
- hash: -2736044117757199136
82
+ hash: -4206610464842566721
75
83
  required_rubygems_version: !ruby/object:Gem::Requirement
76
84
  none: false
77
85
  requirements:
@@ -80,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
88
  version: '0'
81
89
  segments:
82
90
  - 0
83
- hash: -2736044117757199136
91
+ hash: -4206610464842566721
84
92
  requirements: []
85
93
  rubyforge_project:
86
94
  rubygems_version: 1.8.24
data/lib/audit_log.rb DELETED
@@ -1,59 +0,0 @@
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
- class AuditLog
23
-
24
- attr_accessor :filename, :audit_log
25
-
26
- # Initializes a new AuditLog
27
- #
28
- # === Parameters
29
- # options(Hash):: A hash of options that impact the audit log filename. Possible options are;
30
- # :timestamp(String):: The timestamp to append to the filename
31
- # :dry_run(Bool):: A boolean indicating if this is a dry run
32
- def initialize(options={})
33
- timestamp = options[:timestamp] || Time.now.to_i
34
- @audit_log = {}
35
- @filename = "audit_log#{options[:dry_run] ? '_dryrun' : ''}-#{timestamp}.json"
36
- end
37
-
38
- # Adds a new entry to the audit log
39
- #
40
- # === Parameters
41
- # email(String):: The email address of the user impacted by the change
42
- # account(String):: The account name impacted by the change
43
- # action(String):: The action performed. Expected options are ['update_permissions', 'deleted']
44
- # changes(String):: A free form description of the changes
45
- def add_entry(email, account, action, changes)
46
- @audit_log[email] = [] unless audit_log[email]
47
- @audit_log[email] << {
48
- :account => account,
49
- :action => action,
50
- :changes => changes
51
- }
52
- end
53
-
54
- # Writes the audit log to a file
55
- #
56
- def write_file
57
- File.open(@filename, 'w') {|f| f.write(JSON.pretty_generate(@audit_log))}
58
- end
59
- end