cf-uaac 1.3.0

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