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