cf-uaac 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
data/lib/cli/group.rb ADDED
@@ -0,0 +1,85 @@
1
+ #--
2
+ # Cloud Foundry 2012.02.03 Beta
3
+ # Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
4
+ #
5
+ # This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ # You may not use this product except in compliance with the License.
7
+ #
8
+ # This product includes a number of subcomponents with
9
+ # separate copyright notices and license terms. Your use of these
10
+ # subcomponents is subject to the terms and conditions of the
11
+ # subcomponent's license, as noted in the LICENSE file.
12
+ #++
13
+
14
+ require 'set'
15
+ require 'cli/common'
16
+ require 'uaa'
17
+
18
+ module CF::UAA
19
+
20
+ class GroupCli < CommonCli
21
+
22
+ topic "Groups", "group"
23
+
24
+ def gname(name) name || ask("Group name") end
25
+
26
+ desc "groups [filter]", "List groups", :attrs, :start, :count do |filter|
27
+ pp scim_request { |ua|
28
+ query = { attributes: opts[:attrs], filter: filter }
29
+ opts[:start] || opts[:count] ?
30
+ ua.query_groups(query.merge!(startIndex: opts[:start], count: opts[:count])):
31
+ ua.all_pages(:group, query)
32
+ }
33
+ end
34
+
35
+ desc "group get [name]", "Get specific group information" do |name|
36
+ pp scim_request { |ua| ua.get(:group, ua.id(:group, gname(name))) }
37
+ end
38
+
39
+ desc "group add [name]", "Adds a group" do |name|
40
+ pp scim_request { |ua| ua.add(:group, displayName: gname(name)) }
41
+ end
42
+
43
+ desc "group delete [name]", "Delete group" do |name|
44
+ pp scim_request { |ua|
45
+ ua.delete(:delete, ua.id(:group, gname(name)))
46
+ "success"
47
+ }
48
+ end
49
+
50
+ def id_set(objs)
51
+ objs.each_with_object(Set.new) {|o, s|
52
+ s << (o.is_a?(String)? o: (o["id"] || o["value"]))
53
+ }
54
+ end
55
+
56
+ desc "member add [name] [members...]", "add members to a group" do |name, *members|
57
+ pp scim_request { |ua|
58
+ group = ua.get(:group, ua.id(:group, gname(name)))
59
+ old_ids = id_set(group["members"] || [])
60
+ new_ids = id_set(ua.ids(:user, *members))
61
+ raise "not all members found, none added" unless new_ids.size == members.size
62
+ group["members"] = (old_ids + new_ids).to_a
63
+ raise "no new members given" unless group["members"].size > old_ids.size
64
+ ua.put(:group, group)
65
+ "success"
66
+ }
67
+ end
68
+
69
+ desc "member delete [name] [members...]", "remove members from a group" do |name, *members|
70
+ pp scim_request { |ua|
71
+ group = ua.get(:group, ua.id(:group, gname(name)))
72
+ old_ids = id_set(group["members"] || [])
73
+ new_ids = id_set(ua.ids(:user, *members))
74
+ raise "not all members found, none deleted" unless new_ids.size == members.size
75
+ group["members"] = (old_ids - new_ids).to_a
76
+ raise "no existing members to delete" unless group["members"].size < old_ids.size
77
+ group.delete("members") if group["members"].empty?
78
+ ua.put(:group, group)
79
+ "success"
80
+ }
81
+ end
82
+
83
+ end
84
+
85
+ end
data/lib/cli/info.rb ADDED
@@ -0,0 +1,54 @@
1
+ #--
2
+ # Cloud Foundry 2012.02.03 Beta
3
+ # Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
4
+ #
5
+ # This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ # You may not use this product except in compliance with the License.
7
+ #
8
+ # This product includes a number of subcomponents with
9
+ # separate copyright notices and license terms. Your use of these
10
+ # subcomponents is subject to the terms and conditions of the
11
+ # subcomponent's license, as noted in the LICENSE file.
12
+ #++
13
+
14
+ require 'cli/common'
15
+ require 'uaa'
16
+
17
+ module CF::UAA
18
+
19
+ class InfoCli < CommonCli
20
+
21
+ topic "System Information", "sys", "info"
22
+
23
+ def misc_request(&blk) Config.target ? handle_request(&blk) : gripe("target not set") end
24
+
25
+ desc "info", "get information about current target" do
26
+ pp misc_request { update_target_info(Misc.server(Config.target)) }
27
+ end
28
+
29
+ desc "me", "get authenticated user information" do
30
+ pp misc_request { Misc.whoami Config.target, auth_header }
31
+ end
32
+
33
+ desc "prompts", "Show prompts for credentials required for implicit grant post" do
34
+ pp misc_request { update_target_info(Misc.server(Config.target))['prompts'] }
35
+ end
36
+
37
+ desc "signing key", "get the UAA's token signing key(s)", :client, :secret do
38
+ info = misc_request { Misc.validation_key(Config.target,
39
+ (clientname if opts.key?(:client)), (clientsecret if opts.key?(:client))) }
40
+ Config.target_opts(signing_alg: info['alg'], signing_key: info['value'])
41
+ pp info
42
+ end
43
+
44
+ desc "stats", "Show UAA's current usage statistics", :client, :secret do
45
+ pp misc_request { Misc.varz(Config.target, clientname, clientsecret) }
46
+ end
47
+
48
+ desc "password strength [password]", "calculate strength score of a password" do |pwd|
49
+ pp misc_request { Misc.password_strength(Config.target, userpwd(pwd)) }
50
+ end
51
+
52
+ end
53
+
54
+ end
data/lib/cli/runner.rb ADDED
@@ -0,0 +1,52 @@
1
+ #--
2
+ # Cloud Foundry 2012.02.03 Beta
3
+ # Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
4
+ #
5
+ # This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ # You may not use this product except in compliance with the License.
7
+ #
8
+ # This product includes a number of subcomponents with
9
+ # separate copyright notices and license terms. Your use of these
10
+ # subcomponents is subject to the terms and conditions of the
11
+ # subcomponent's license, as noted in the LICENSE file.
12
+ #++
13
+
14
+ require 'cli/common'
15
+ require 'cli/token'
16
+ require 'cli/user'
17
+ require 'cli/group'
18
+ require 'cli/info'
19
+ require 'cli/client_reg'
20
+
21
+ module CF::UAA
22
+
23
+ class Cli < BaseCli
24
+ @overview = "UAA Command Line Interface"
25
+ @topics = [MiscCli, InfoCli, TokenCli, UserCli, GroupCli, ClientCli]
26
+ @global_options = [:help, :version, :debug, :trace, :config]
27
+
28
+ def self.configure(config_file = "", input = $stdin, output = $stdout,
29
+ print_on_trace = false)
30
+ @config_file, @input, @output = config_file, input, output
31
+ @print_on_trace = print_on_trace
32
+ self
33
+ end
34
+
35
+ def self.too_many_args(cmd)
36
+ @output.puts "\nToo many command line parameters given."
37
+ run cmd.unshift("help")
38
+ end
39
+
40
+ def self.preprocess_options(args, opts)
41
+ return args.replace(["version"]) if opts[:version]
42
+ return args.unshift("help") if args.empty? || opts[:help] && args[0] != "version"
43
+ Config.load(opts[:config] || @config_file) if opts.key?(:config) || !Config.loaded?
44
+ [:trace, :debug].each do |k|
45
+ opts[k] = true if !opts.key?(k) && Config.target && Config.context && Config.value(k)
46
+ end
47
+ Misc.logger = Util.default_logger(opts[:trace]? :trace: opts[:debug]? :debug: :warn, @output)
48
+ end
49
+
50
+ end
51
+
52
+ end
data/lib/cli/token.rb ADDED
@@ -0,0 +1,217 @@
1
+ #--
2
+ # Cloud Foundry 2012.02.03 Beta
3
+ # Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
4
+ #
5
+ # This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ # You may not use this product except in compliance with the License.
7
+ #
8
+ # This product includes a number of subcomponents with
9
+ # separate copyright notices and license terms. Your use of these
10
+ # subcomponents is subject to the terms and conditions of the
11
+ # subcomponent's license, as noted in the LICENSE file.
12
+ #++
13
+
14
+ require 'cli/common'
15
+ require 'launchy'
16
+ require 'uaa'
17
+ require 'stub/server'
18
+
19
+ module CF::UAA
20
+
21
+ class TokenCatcher < Stub::Base
22
+
23
+ def process_grant(data)
24
+ server.logger.debug "processing grant for path #{request.path}"
25
+ secret = server.info.delete(:client_secret)
26
+ ti = TokenIssuer.new(Config.target, server.info.delete(:client_id), secret,
27
+ Config.target_value(:token_target))
28
+ tkn = secret ? ti.authcode_grant(server.info.delete(:uri), data) :
29
+ ti.implicit_grant(server.info.delete(:uri), data)
30
+ server.info.update(Util.hash_keys!(tkn.info, :tosym))
31
+ reply.text "you are now logged in and can close this window"
32
+ rescue TargetError => e
33
+ reply.text "#{e.message}:\r\n#{JSON.pretty_generate(e.info)}\r\n#{e.backtrace}"
34
+ rescue Exception => e
35
+ reply.text "#{e.message}\r\n#{e.backtrace}"
36
+ ensure
37
+ server.logger.debug "reply: #{reply.body}"
38
+ end
39
+
40
+ route :get, '/favicon.ico' do
41
+ reply.headers['content-type'] = "image/vnd.microsoft.icon"
42
+ reply.body = File.read File.expand_path(File.join(__FILE__, '..', 'favicon.ico'))
43
+ end
44
+
45
+ route :get, %r{^/authcode\?(.*)$} do process_grant match[1] end
46
+ route :post, '/callback' do process_grant request.body end
47
+ route :get, '/callback' do
48
+ server.logger.debug "caught redirect back from UAA after authentication"
49
+ reply.headers['content-type'] = "text/html"
50
+ reply.body = <<-HTML.gsub(/^ +/, '')
51
+ <html><body><script type="text/javascript">
52
+ var fragment = location.hash.substring(1);
53
+ var req = new XMLHttpRequest();
54
+ //document.write(fragment + "<br><br>");
55
+ req.open('POST', "/callback", false);
56
+ req.setRequestHeader("Content-type","application/x-www-form-urlencoded");
57
+ req.send(fragment);
58
+ document.write(req.responseText);
59
+ </script></body></html>
60
+ HTML
61
+ end
62
+ end
63
+
64
+ class TokenCli < CommonCli
65
+
66
+ topic "Tokens", "token", "login"
67
+
68
+ def say_success(grant)
69
+ say "\nSuccessfully fetched token via a #{grant} grant.\nTarget: #{Config.target}\nContext: #{Config.context}\n"
70
+ end
71
+
72
+ def issuer_request(client_id, secret = nil)
73
+ update_target_info
74
+ yield TokenIssuer.new(Config.target.to_s, client_id, secret, Config.target_value(:token_endpoint))
75
+ rescue Exception => e
76
+ complain e
77
+ end
78
+
79
+ define_option :client, "--client <name>", "-c"
80
+ define_option :scope, "--scope <list>"
81
+ desc "token get [credentials...]",
82
+ "Gets a token by posting user credentials with an implicit grant request",
83
+ :client, :scope do |*args|
84
+ client_name = opts[:client] || "vmc"
85
+ token = issuer_request(client_name, "") { |ti|
86
+ prompts = ti.prompts
87
+ creds = {}
88
+ prompts.each do |k, v|
89
+ if arg = args.shift
90
+ creds[k] = arg
91
+ elsif v[0] == "text"
92
+ creds[k] = ask(v[1])
93
+ elsif v[0] == "password"
94
+ creds[k] = ask_pwd v[1]
95
+ else
96
+ raise "Unknown prompt type \"#{v[0]}\" received from #{Context.target}"
97
+ end
98
+ end
99
+ ti.implicit_grant_with_creds(creds, opts[:scope]).info
100
+ }
101
+ return gripe "attempt to get token failed\n" unless token && token["access_token"]
102
+ tokinfo = TokenCoder.decode(token["access_token"], nil, nil, false)
103
+ Config.context = tokinfo["user_name"]
104
+ Config.add_opts(user_id: tokinfo["user_id"])
105
+ Config.add_opts token
106
+ say_success "implicit (with posted credentials)"
107
+ end
108
+
109
+ define_option :secret, "--secret <secret>", "-s", "client secret"
110
+ desc "token client get [name]",
111
+ "Gets a token with client credentials grant", :secret, :scope do |id|
112
+ id = clientname(id)
113
+ return unless info = issuer_request(id, clientsecret) { |ti|
114
+ ti.client_credentials_grant(opts[:scope]).info
115
+ }
116
+ Config.context = id
117
+ Config.add_opts info
118
+ say_success "client credentials"
119
+ end
120
+
121
+ define_option :password, "-p", "--password <password>", "user password"
122
+ desc "token owner get [client] [user]", "Gets a token with a resource owner password grant",
123
+ :secret, :password, :scope do |client, user|
124
+ return unless info = issuer_request(clientname(client), clientsecret) { |ti|
125
+ ti.owner_password_grant(user = username(user), userpwd, opts[:scope]).info
126
+ }
127
+ Config.context = user
128
+ Config.add_opts info
129
+ say_success "owner password"
130
+ end
131
+
132
+ desc "token refresh [refreshtoken]", "Gets a new access token from a refresh token", :client, :secret, :scope do |rtok|
133
+ rtok ||= Config.value(:refresh_token)
134
+ Config.add_opts issuer_request(clientname, clientsecret) { |ti| ti.refresh_token_grant(rtok, opts[:scope]).info }
135
+ say_success "refresh"
136
+ end
137
+
138
+ VMC_TOKEN_FILE = File.join ENV["HOME"], ".vmc_token"
139
+ VMC_TARGET_FILE = File.join ENV["HOME"], ".vmc_target"
140
+
141
+ def use_browser(client_id, secret = nil)
142
+ catcher = Stub::Server.new(TokenCatcher,
143
+ Util.default_logger(debug? ? :debug : trace? ? :trace : :info),
144
+ client_id: client_id, client_secret: secret).run_on_thread("localhost", opts[:port])
145
+ uri = issuer_request(client_id, secret) { |ti|
146
+ secret ? ti.authcode_uri("#{catcher.url}/authcode", opts[:scope]) :
147
+ ti.implicit_uri("#{catcher.url}/callback", opts[:scope])
148
+ }
149
+ return unless catcher.info[:uri] = uri
150
+ say "launching browser with #{uri}" if trace?
151
+ Launchy.open(uri, debug: true, dry_run: false)
152
+ print "waiting for token "
153
+ while catcher.info[:uri] || !catcher.info[:access_token]
154
+ sleep 5
155
+ print "."
156
+ end
157
+ Config.context = TokenCoder.decode(catcher.info[:access_token], nil, nil, false)[:user_name]
158
+ Config.add_opts catcher.info
159
+ say_success secret ? "authorization code" : "implicit"
160
+ return unless opts[:vmc]
161
+ begin
162
+ vmc_target = File.open(VMC_TARGET_FILE, 'r') { |f| f.read.strip }
163
+ tok_json = File.open(VMC_TOKEN_FILE, 'r') { |f| f.read } if File.exists?(VMC_TOKEN_FILE)
164
+ vmc_tokens = Util.json_parse(tok_json, :none) || {}
165
+ vmc_tokens[vmc_target] = auth_header
166
+ File.open(VMC_TOKEN_FILE, 'w') { |f| f.write(vmc_tokens.to_json) }
167
+ rescue Exception => e
168
+ gripe "\nUnable to save token to vmc token file"
169
+ complain e
170
+ end
171
+ end
172
+
173
+ define_option :port, "--port <number>", "pin internal server to specific port"
174
+ define_option :vmc, "--[no-]vmc", "save token in the ~/.vmc_tokens file"
175
+ desc "token authcode get", "Gets a token using the authcode flow with browser",
176
+ :client, :secret, :scope, :vmc, :port do use_browser(clientname, clientsecret) end
177
+
178
+ desc "token implicit get", "Gets a token using the implicit flow with browser",
179
+ :client, :scope, :vmc, :port do use_browser opts[:client] || "vmc" end
180
+
181
+ define_option :key, "--key <key>", "Token validation key"
182
+ desc "token decode [token] [tokentype]", "Show token contents as parsed locally or by the UAA. " +
183
+ "Decodes locally unless --client and --secret are given. Validates locally if --key given or server's signing key has been retrieved",
184
+ :key, :client, :secret do |token, ttype|
185
+ ttype = "bearer" if token && !ttype
186
+ token ||= Config.value(:access_token)
187
+ ttype ||= Config.value(:token_type)
188
+ return say "no token to decode" unless token && ttype
189
+ handle_request do
190
+ if opts[:client] && opts[:secret]
191
+ pp Misc.decode_token(Config.target, opts[:client], opts[:secret], token, ttype)
192
+ else
193
+ seckey = opts[:key] || (Config.target_value(:signing_key) if Config.target_value(:signing_alg) !~ /rsa$/i)
194
+ pubkey = opts[:key] || (Config.target_value(:signing_key) if Config.target_value(:signing_alg) =~ /rsa$/i)
195
+ info = TokenCoder.decode(token, seckey, pubkey, seckey || pubkey)
196
+ say seckey || pubkey ? "\nValid token signature\n\n": "\nNote: no key given to validate token signature\n\n"
197
+ pp info
198
+ end
199
+ end
200
+ end
201
+
202
+ define_option :all, "--[no-]all", "remove all contexts"
203
+ desc "token delete [contexts...]",
204
+ "Delete current or specified context tokens and settings", :all do |*args|
205
+ begin
206
+ return Config.delete if opts[:all]
207
+ return args.each { |arg| Config.delete(Config.target, arg.to_i.to_s == arg ? arg.to_i : arg) } unless args.empty?
208
+ return Config.delete(Config.target, Config.context) if Config.context
209
+ say "no target set, no contexts given -- nothing to delete"
210
+ rescue Exception => e
211
+ complain e
212
+ end
213
+ end
214
+
215
+ end
216
+
217
+ end
data/lib/cli/user.rb ADDED
@@ -0,0 +1,108 @@
1
+ #--
2
+ # Cloud Foundry 2012.02.03 Beta
3
+ # Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
4
+ #
5
+ # This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ # You may not use this product except in compliance with the License.
7
+ #
8
+ # This product includes a number of subcomponents with
9
+ # separate copyright notices and license terms. Your use of these
10
+ # subcomponents is subject to the terms and conditions of the
11
+ # subcomponent's license, as noted in the LICENSE file.
12
+ #++
13
+
14
+ require 'cli/common'
15
+
16
+ module CF::UAA
17
+
18
+ class UserCli < CommonCli
19
+
20
+ topic "User Accounts", "account"
21
+
22
+ define_option :givenName, "--given_name <name>"
23
+ define_option :familyName, "--family_name <name>"
24
+ define_option :emails, "--emails <addresses>"
25
+ define_option :phoneNumbers, "--phones <phone_numbers>"
26
+ USER_INFO_OPTS = [:givenName, :familyName, :emails, :phoneNumbers]
27
+
28
+ def user_opts(info = {})
29
+ [:emails, :phoneNumbers].each do |o|
30
+ next unless opts[o]
31
+ info[o] = Util.arglist(opts[o]).each_with_object([]) { |v, a| a << {:value => v} }
32
+ end
33
+ n = [:givenName, :familyName].each_with_object({}) { |o, n| n[o] = opts[o] if opts[o] }
34
+ info[:name] = n unless n.empty?
35
+ info
36
+ end
37
+
38
+ define_option :attrs, "-a", "--attributes <names>", "output for each user"
39
+ define_option :start, "--start <number>", "start of output page"
40
+ define_option :count, "--count <number>", "max number per page"
41
+ desc "users [filter]", "List user accounts", :attrs, :start, :count do |filter|
42
+ pp scim_request { |ua|
43
+ query = { attributes: opts[:attrs], filter: filter }
44
+ opts[:start] || opts[:count] ?
45
+ ua.query(:user, query.merge!(startIndex: opts[:start], count: opts[:count])):
46
+ ua.all_pages(:user, query)
47
+ }
48
+ end
49
+
50
+ desc "user get [name]", "Get specific user account" do |name|
51
+ pp scim_request { |ua| ua.get(:user, ua.id(:user, username(name))) }
52
+ end
53
+
54
+ desc "user add [name]", "Add a user account", *USER_INFO_OPTS, :password do |name|
55
+ info = {userName: username(name), password: verified_pwd("Password", opts[:password])}
56
+ pp scim_request { |ua|
57
+ ua.add(:user, user_opts(info))
58
+ "user account successfully added"
59
+ }
60
+ end
61
+
62
+ define_option :del_attrs, "--del_attrs <attr_names>", "list of attributes to delete"
63
+ desc "user update [name]", "Update a user account with specified options",
64
+ *USER_INFO_OPTS, :del_attrs do |name|
65
+ return say "no user updates specified" if (updates = user_opts).empty?
66
+ pp scim_request { |ua|
67
+ info = ua.get(:user, ua.id(:user, username(name)))
68
+ opts[:del_attrs].each { |a| info.delete(a.to_s) } if opts[:del_attrs]
69
+ ua.put(:user, info.merge(updates))
70
+ "user account successfully updated"
71
+ }
72
+ end
73
+
74
+ desc "user delete [name]", "Delete user account" do |name|
75
+ pp scim_request { |ua|
76
+ ua.delete(:user, ua.id(:user, username(name)))
77
+ "user account successfully deleted"
78
+ }
79
+ end
80
+
81
+ desc "user ids [username|id...]", "Gets user names and ids for the given users" do |*users|
82
+ pp scim_request { |ua|
83
+ users = Util.arglist(ask("names or ids of users")) if !users || users.empty?
84
+ ids = ua.ids(:user_id, *users)
85
+ raise NotFound, "no users found" unless ids && ids.length > 0
86
+ ids
87
+ }
88
+ end
89
+
90
+ desc "password set [name]", "Set password", :password do |name|
91
+ pp scim_request { |ua|
92
+ ua.change_password(ua.id(:user, username(name)), verified_pwd("New password", opts[:password]))
93
+ "password successfully set"
94
+ }
95
+ end
96
+
97
+ define_option :old_password, "-o", "--old_password <password>", "current password"
98
+ desc "password change", "Change password for authenticated user in current context", :old_password, :password do
99
+ pp scim_request { |ua|
100
+ oldpwd = opts[:old_password] || ask_pwd("Current password")
101
+ ua.change_password(Config.value(:user_id), verified_pwd("New password", opts[:password]), oldpwd)
102
+ "password successfully changed"
103
+ }
104
+ end
105
+
106
+ end
107
+
108
+ end