rs_user_policy 0.0.5 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -10,13 +10,15 @@ While the tests are not exhaustive, the current build status is..
10
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
11
 
12
12
  Options:
13
- --rs-email, -r <s>: You RightScale User Email Address
13
+ --rs-email, -r <s>: You RightScale User Email Address
14
14
  --rs-pass, -s <s>: Your RightScale User Password
15
15
  --rs-acct-num, -a <s>: A RightScale Enterprise Master Account ID
16
16
  --policy, -p <s>: The path to a JSON file containing the role to permissions policy to enforce
17
17
  --user-assignments, -u <s>: The path to a JSON file containing email address => role pairs for user assignments
18
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
19
  the audit_log.json produced
20
+ --authority, -t: A flag indicating that all users in the user_assignments file "MUST" exist, and will always be created. Effectively
21
+ asserting that the user_assignments is your canonical authority for users.
20
22
  --help, -h: Show this message
21
23
 
22
24
  Example (One account)
@@ -57,8 +59,6 @@ User Assignment JSON should be in the following format
57
59
  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.
58
60
  Both "immutable" and "delete", if present will take precedence over any other roles assigned to the user.
59
61
 
60
- Many email:role pairs can be specified. in the user assignments JSON
61
-
62
62
  So, given a policy file like;
63
63
 
64
64
  {
@@ -103,14 +103,24 @@ This tool can also be used to create net-new users who have either never used Ri
103
103
  "company": "RightScale",
104
104
  "first_name": "Net",
105
105
  "last_name": "New",
106
- "phone": "9999999999"
106
+ "phone": "9999999999",
107
+ "create": "yes"
107
108
  }
108
109
  }
109
110
 
110
111
  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
 
113
+ *NOTE:* See the Authority section below for details on the "create" property
114
+
112
115
  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
116
 
117
+ ==== Authority
118
+
119
+ By default, rs_user_policy assumes that RightScale is the authority for the existence of users. Meaning, if a user exists in the user_assigments, but does not exist in RightScale, the user will not be created by rs_user_policy. In order to override this there are two options.
120
+
121
+ 1. Specifying the --authority commandline option implies that ALL users who are in the user_assignments should be created with the provided parameters
122
+ 2. For individual users in the user_assignments, you can add a property named "create" with any value. The user will be created, and the "create" property will be removed.
123
+
114
124
  == Output
115
125
 
116
126
  When the script is run, it will produce two JSON files as output.
@@ -123,10 +133,9 @@ Second is the user_assignments-<timestamp>.json file. This will be a combinatio
123
133
 
124
134
  * In absence of a policy.json, create a new policy.json with base roles for each account discovered (I.E. Admin, Observer, Designer, etc)
125
135
  * Perhaps allow a role to inherit from another, or be a concatenation of several?
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
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?
136
+ * Provide a mechanism for "temporary" users with an expiration date
137
+ * Perhaps allow the user to enter a different role after the expiration date, rather than being removed completely?
129
138
 
130
139
  == Copyright
131
140
 
132
- Copyright (c) 2012 Ryan J. Geyer. See LICENSE.txt for further details.
141
+ Copyright (c) 2012-2013 Ryan J. Geyer. See LICENSE.txt for further details.
data/bin/rs_user_policy CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright (c) 2012 Ryan J. Geyer
3
+ # Copyright (c) 2012-2013 Ryan J. Geyer
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining
6
6
  # a copy of this software and associated documentation files (the
@@ -27,6 +27,15 @@ require 'logger'
27
27
  require 'digest/md5'
28
28
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rs_user_policy'))
29
29
 
30
+ class UserResourceDetailMock
31
+ attr_reader :email, :href
32
+
33
+ def initialize(email, href)
34
+ @email = email
35
+ @href = href
36
+ end
37
+ end
38
+
30
39
  opts = Trollop::options do
31
40
  banner = "Manages users across many different child accounts of a RightScale Enterprise Master Account"
32
41
 
@@ -36,6 +45,7 @@ opts = Trollop::options do
36
45
  opt :policy, "The path to a JSON file containing the role to permissions policy to enforce", :type => :string, :required => true
37
46
  opt :user_assignments, "The path to a JSON file containing email address => role pairs for user assignments", :type => :string
38
47
  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"
48
+ opt :authority, "A flag indicating that all users in the user_assignments file \"MUST\" exist, and will always be created. Effectively asserting that the user_assignments is your canonical authority for users."
39
49
  end
40
50
 
41
51
  log = Logger.new(STDOUT)
@@ -72,33 +82,54 @@ multi_client.each do |account_id, account|
72
82
  child_client = account[:client]
73
83
  child_account = child_client.accounts(:id => account_id).show()
74
84
  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)
85
+ user_collection.add_users(child_client.users.index)
77
86
  user_collection.add_permissions(account_href, child_client.permissions.index)
78
87
  end
79
88
 
80
- # Populate the user_assignments with hrefs
89
+ # Populate the user_assignments with extra bits..
81
90
  user_collection.users.each do |user|
82
- user_assignments[user.email]["href"] = user.href
91
+ user_hash = user.to_hash
92
+ user_hash.delete(:permissions)
93
+ user_assignments[user.email].merge!(user_hash)
83
94
  end
84
95
 
85
- net_new_users = user_assignments.list - user_collection.users.map{|u| u.email }
96
+ user_assignments_without_delete = user_assignments.list.select do |assn|
97
+ roles = user_assignments.get_roles(assn)
98
+ !roles.include?("delete")
99
+ end
100
+
101
+ net_new_users = user_assignments_without_delete - user_collection.users.map{|u| u.email }
86
102
 
87
103
  net_new_users.each do |net_new_user_email|
88
104
  client = multi_client[opts[:rs_acct_num].first()][:client]
89
105
  user_create_params = user_assignments[net_new_user_email]
90
106
  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
95
- end
96
107
  net_new_user_href = Digest::MD5.hexdigest((Time.now.to_i + rand(256)).to_s)
108
+
109
+ unless opts[:authority] || user_create_params.key?("create")
110
+ log.info("User (#{net_new_user_email}) was not found in any of the accounts or child accounts being operated on. rs_user_policy was not executed with --authority, nor did (#{net_new_user_email}) have a \"create\" property, so no action is being taken.")
111
+ next
112
+ end
113
+
114
+ net_new_user = UserResourceDetailMock.new(net_new_user_email, net_new_user_href)
97
115
  unless opts[:dry_run]
98
- net_new_user = client.users.create(:user => user_create_params)
99
- net_new_user_href = net_new_user.href
116
+ unless user_create_params.key?("password") || (user_create_params.key?("identity_provider_href") && user_create_params.key?("principal_uid"))
117
+ pass = RsUserPolicy::Utilities.generate_compliant_password
118
+ user_create_params["password"] = pass
119
+ user_assignments[net_new_user_email]["password"] = pass
120
+ end
121
+
122
+ begin
123
+ net_new_user = client.users.create(:user => user_create_params)
124
+ net_new_user_href = net_new_user.href
125
+ user_assignments[net_new_user_email].delete("create")
126
+ rescue RightApi::Exceptions::ApiException => e
127
+ log.error("Failed to create a user with the following properties.\n Properties: #{JSON.pretty_generate(user_create_params)}\n Error: #{e}")
128
+ next
129
+ end
100
130
  end
101
- user_collection.add_users({net_new_user_href => net_new_user_email})
131
+ audit_log.add_entry(net_new_user_email, net_new_user_href, "created", "created")
132
+ user_collection.add_users([net_new_user])
102
133
  user_assignments[net_new_user_email]["href"] = net_new_user_href
103
134
  end
104
135
 
@@ -145,4 +176,4 @@ user_collection.users.each do |user|
145
176
  end unless opts[:dry_run]
146
177
 
147
178
  user_assignments.serialize(:filename => user_assignments_output)
148
- audit_log.write_file
179
+ audit_log.write_file
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Ryan J. Geyer
1
+ # Copyright (c) 2012-2013 Ryan J. Geyer
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -39,7 +39,7 @@ module RsUserPolicy
39
39
  #
40
40
  # @param [String] email The email address of the user impacted by the change
41
41
  # @param [String] account The account name impacted by the change
42
- # @param [String] action The action performed. Expected options are ['update_permissions', 'deleted']
42
+ # @param [String] action The action performed. Expected options are ['update_permissions', 'created', 'deleted']
43
43
  # @param [String] changes A free form description of the changes
44
44
  def add_entry(email, account, action, changes)
45
45
  @audit_log[email] = [] unless audit_log[email]
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Ryan J. Geyer
1
+ # Copyright (c) 2012-2013 Ryan J. Geyer
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -25,14 +25,25 @@ module RsUserPolicy
25
25
 
26
26
  # Initializes read only attributes for an RsUserPolicy::User
27
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
28
+ # @param [RightApi::ResourceDetail] user The user detail returned by RightApi::Client
29
+ def initialize(user)
30
+ @email = user.email
31
+ @href = user.href
32
+ @user = user
33
33
  @permissions = {}
34
34
  end
35
35
 
36
+ # Converts this object to a hash which can be serialized
37
+ def to_hash()
38
+ rethash = {
39
+ :permissions => @permissions
40
+ }
41
+ (@user.attributes - [:links]).each do |attr_sym|
42
+ rethash[attr_sym.to_sym] = @user.send(attr_sym.to_s)
43
+ end
44
+ rethash
45
+ end
46
+
36
47
  # Adds a single permission for a single RightScale account
37
48
  #
38
49
  # @param [String] account_href The RightScale API href of the account
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Ryan J. Geyer
1
+ # Copyright (c) 2012-2013 Ryan J. Geyer
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -37,11 +37,11 @@ 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 [Hash] users A hash where the key is a users href, and the value is the users email
40
+ # @param [Array<RightApi::ResourceDetail>] users An array of RightAPI::ResourceDetail for the resource type "user"
41
41
  def add_users(users)
42
- users.each do |href, email|
43
- unless @users_by_href.has_key?(href)
44
- @users_by_href[href] = RsUserPolicy::User.new(email, href)
42
+ users.each do |user|
43
+ unless @users_by_href.has_key?(user.href)
44
+ @users_by_href[user.href] = RsUserPolicy::User.new(user)
45
45
  end
46
46
  end
47
47
  end
@@ -55,7 +55,7 @@ module RsUserPolicy
55
55
  user_href = permission.user.href
56
56
  unless @users_by_href.has_key?(user_href)
57
57
  user = permission.user.show()
58
- @users_by_href[user.href] = RsUserPolicy::User.new(user.email, user.href)
58
+ @users_by_href[user.href] = RsUserPolicy::User.new(user)
59
59
  end
60
60
  @users_by_href[user_href].add_permission(account_href, permission)
61
61
  end
@@ -57,13 +57,19 @@ module RsUserPolicy
57
57
  end
58
58
 
59
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
60
+ compliant = nil
61
+ password = ''
62
+ until compliant
63
+ chars = (
64
+ ('a'..'z').to_a +
65
+ ('A'..'Z').to_a +
66
+ ('0'..'9').to_a +
67
+ ["!","@","#","$","%","^","&","*","(",")","-","_","=","+"]
68
+ ) - %w(i o 0 1 l 0)
69
+ password = (1..size).collect{|a| chars[rand(chars.size)] }.join
70
+ compliant = password =~ /^(?=.*[^a-zA-Z])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@\#\$%\^&\*\(\)\-_=\+]).+$/
71
+ end
72
+ password
67
73
  end
68
74
 
69
75
  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.5
4
+ version: 0.1.3
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-18 00:00:00.000000000 Z
12
+ date: 2013-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: right_api_client
@@ -80,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
80
  version: '0'
81
81
  segments:
82
82
  - 0
83
- hash: 3250796022551410117
83
+ hash: -2197135940528599574
84
84
  required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  version: '0'
90
90
  segments:
91
91
  - 0
92
- hash: 3250796022551410117
92
+ hash: -2197135940528599574
93
93
  requirements: []
94
94
  rubyforge_project:
95
95
  rubygems_version: 1.8.24