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.
- data/.gitignore +8 -0
- data/Gemfile +16 -0
- data/README.md +48 -0
- data/Rakefile +50 -0
- data/bin/completion-helper +80 -0
- data/bin/uaac +5 -0
- data/bin/uaac-completion.sh +34 -0
- data/bin/uaas +7 -0
- data/cf-uaac.gemspec +48 -0
- data/lib/cli.rb +15 -0
- data/lib/cli/base.rb +277 -0
- data/lib/cli/client_reg.rb +103 -0
- data/lib/cli/common.rb +187 -0
- data/lib/cli/config.rb +163 -0
- data/lib/cli/favicon.ico +0 -0
- data/lib/cli/group.rb +85 -0
- data/lib/cli/info.rb +54 -0
- data/lib/cli/runner.rb +52 -0
- data/lib/cli/token.rb +217 -0
- data/lib/cli/user.rb +108 -0
- data/lib/cli/version.rb +18 -0
- data/lib/stub/scim.rb +387 -0
- data/lib/stub/server.rb +310 -0
- data/lib/stub/uaa.rb +485 -0
- data/spec/client_reg_spec.rb +104 -0
- data/spec/common_spec.rb +89 -0
- data/spec/group_spec.rb +93 -0
- data/spec/http_spec.rb +165 -0
- data/spec/info_spec.rb +74 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/token_spec.rb +119 -0
- data/spec/user_spec.rb +61 -0
- metadata +292 -0
data/lib/cli/favicon.ico
ADDED
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
|