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
@@ -0,0 +1,103 @@
|
|
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 ClientCli < CommonCli
|
19
|
+
|
20
|
+
topic "Client Application Registrations", "reg"
|
21
|
+
|
22
|
+
CLIENT_SCHEMA = { scope: "list", authorized_grant_types: "list",
|
23
|
+
authorities: "list", access_token_validity: "seconds",
|
24
|
+
refresh_token_validity: "seconds", redirect_uri: "list" }
|
25
|
+
CLIENT_SCHEMA.each { |k, v| define_option(k, "--#{k} <#{v}>") }
|
26
|
+
|
27
|
+
def client_info(defaults)
|
28
|
+
info = {client_id: defaults["client_id"] || opts[:client_id]}
|
29
|
+
info[:client_secret] = opts[:secret] if opts[:secret]
|
30
|
+
del_attrs = Util.arglist(opts[:del_attrs], [])
|
31
|
+
CLIENT_SCHEMA.each_with_object(info) do |(k, p), info|
|
32
|
+
next if del_attrs.include?(k)
|
33
|
+
default = Util.strlist(defaults[k.to_s])
|
34
|
+
if opts.key?(k)
|
35
|
+
info[k] = opts[k].nil? || opts[k].empty? ? default : opts[k]
|
36
|
+
else
|
37
|
+
info[k] = opts[:interact] ?
|
38
|
+
info[k] = askd("#{k.to_s.gsub('_', ' ')} (#{p})", default): default
|
39
|
+
end
|
40
|
+
info[k] = Util.arglist(info[k]) if p == "list"
|
41
|
+
info.delete(k) unless info[k]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "clients", "List client registrations" do
|
46
|
+
pp scim_request { |cr| cr.all_pages(:client) }
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "client get [name]", "Get specific client registration" do |name|
|
50
|
+
pp scim_request { |cr| cr.get(:client, cr.id(:client, clientname(name))) }
|
51
|
+
end
|
52
|
+
|
53
|
+
define_option :clone, "--clone <other_client>", "get default client settings from existing client"
|
54
|
+
define_option :interact, "--[no-]interactive", "-i", "interactively verify all values"
|
55
|
+
|
56
|
+
desc "client add [name]", "Add client registration",
|
57
|
+
*CLIENT_SCHEMA.keys, :clone, :secret, :interact do |name|
|
58
|
+
pp scim_request { |cr|
|
59
|
+
opts[:client_id] = clientname(name)
|
60
|
+
opts[:secret] = verified_pwd("New client secret", opts[:secret])
|
61
|
+
defaults = opts[:clone] ? cr.get(opts[:clone]) : {}
|
62
|
+
defaults.delete("client_id")
|
63
|
+
cr.add(:client, client_info(defaults))
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "client update [name]", "Update client registration", *CLIENT_SCHEMA.keys,
|
68
|
+
:del_attrs, :interact do |name|
|
69
|
+
pp scim_request { |cr|
|
70
|
+
opts[:client_id] = clientname(name)
|
71
|
+
info = client_info(cr.get(:client, opts[:client_id]))
|
72
|
+
info.length > 1 ? cr.put(:client, info) : gripe("Nothing to update. Use -i for interactive update.")
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "client delete [name]", "Delete client registration" do |name|
|
77
|
+
pp scim_request { |cr|
|
78
|
+
cr.delete(:client, clientname(name))
|
79
|
+
"client registration deleted"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "secret set [name]", "Set client secret", :secret do |name|
|
84
|
+
pp scim_request { |cr|
|
85
|
+
cr.change_secret(clientname(name), verified_pwd("New secret", opts[:secret]))
|
86
|
+
"client secret successfully set"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
define_option :old_secret, "-o", "--old_secret <secret>", "current secret"
|
91
|
+
desc "secret change", "Change secret for authenticated client in current context", :old_secret, :secret do
|
92
|
+
return gripe "context not set" unless client_id = Config.context.to_s
|
93
|
+
scim_request { |cr|
|
94
|
+
old = opts[:old_secret] || ask_pwd("Current secret")
|
95
|
+
cr.change_secret(client_id, verified_pwd("New secret", opts[:secret]), old)
|
96
|
+
"client secret successfully changed"
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
data/lib/cli/common.rb
ADDED
@@ -0,0 +1,187 @@
|
|
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/base'
|
15
|
+
require 'cli/config'
|
16
|
+
require 'uaa'
|
17
|
+
|
18
|
+
module CF::UAA
|
19
|
+
|
20
|
+
class CommonCli < Topic
|
21
|
+
|
22
|
+
def trace?; opts[:trace] end
|
23
|
+
def debug?; opts[:debug] end
|
24
|
+
|
25
|
+
def auth_header
|
26
|
+
unless (ttype = Config.value(:token_type)) && (token = Config.value(:access_token))
|
27
|
+
raise "Need an access token to complete this command. Please login."
|
28
|
+
end
|
29
|
+
"#{ttype} #{token}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def username(name); name || ask("User name") end
|
33
|
+
def userpwd(pwd = opts[:password]); pwd || ask_pwd("Password") end
|
34
|
+
def clientname(name = opts[:client]); name || ask("Client name") end
|
35
|
+
def clientsecret(name = opts[:secret]); name || ask_pwd("Client secret") end
|
36
|
+
|
37
|
+
def verified_pwd(prompt, pwd = nil)
|
38
|
+
while pwd.nil?
|
39
|
+
pwd_a = ask_pwd prompt
|
40
|
+
pwd_b = ask_pwd "Verify #{prompt.downcase}"
|
41
|
+
pwd = pwd_a if pwd_a == pwd_b
|
42
|
+
end
|
43
|
+
pwd
|
44
|
+
end
|
45
|
+
|
46
|
+
def askd(prompt, defary)
|
47
|
+
return ask(prompt) unless defary
|
48
|
+
result = ask("#{prompt} [#{Util.strlist(defary)}]")
|
49
|
+
result.nil? || result.empty? ? defary : result
|
50
|
+
end
|
51
|
+
|
52
|
+
def complain(e)
|
53
|
+
case e
|
54
|
+
when TargetError then gripe "\n#{e.message}:\n#{JSON.pretty_generate(e.info)}"
|
55
|
+
when Exception
|
56
|
+
gripe "\n#{e.class}: #{e.message}\n\n"
|
57
|
+
gripe e.backtrace if trace?
|
58
|
+
when String then gripe e
|
59
|
+
else gripe "unknown type of gripe: #{e.class}, #{e}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_request
|
64
|
+
yield
|
65
|
+
rescue Exception => e
|
66
|
+
complain e
|
67
|
+
end
|
68
|
+
|
69
|
+
def scim_request
|
70
|
+
yield Scim.new(Config.target, auth_header)
|
71
|
+
rescue Exception => e
|
72
|
+
complain e
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_target_info(info = nil)
|
76
|
+
return if !info && Config.target_value(:prompts)
|
77
|
+
info ||= Misc.server(Config.target)
|
78
|
+
Config.target_opts(prompts: info['prompts'])
|
79
|
+
Config.target_opts(token_endpoint: info['token_endpoint']) if info['token_endpoint']
|
80
|
+
info
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class MiscCli < CommonCli
|
86
|
+
|
87
|
+
topic "Miscellaneous", "misc"
|
88
|
+
|
89
|
+
desc "version", "Display version" do
|
90
|
+
say "UAA client #{CLI_VERSION}"
|
91
|
+
end
|
92
|
+
|
93
|
+
define_option :trace, "--[no-]trace", "-t", "display extra verbose debug information"
|
94
|
+
define_option :debug, "--[no-]debug", "-d", "display debug information"
|
95
|
+
define_option :help, "--[no-]help", "-h", "display helpful information"
|
96
|
+
define_option :version, "--[no-]version", "-v", "show version"
|
97
|
+
define_option :config, "--config [string|file]", "file to get/save configuration information or yaml string"
|
98
|
+
|
99
|
+
desc "help [topic|command...]", "Display summary or details of command or topic" do |*args|
|
100
|
+
# handle hidden command, output commands in form for bash completion
|
101
|
+
return say_commands if args.length == 1 && args[0] == "commands"
|
102
|
+
args.empty? ? say_help : say_command_help(args)
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_url(url, scheme = nil)
|
106
|
+
url = url.strip.gsub(/\/*$/, "")
|
107
|
+
raise ArgumentError, "invalid whitespace in target url" if url =~ /\s/
|
108
|
+
unless url =~ /^https?:\/\//
|
109
|
+
return unless scheme
|
110
|
+
url = "#{scheme}://#{url}"
|
111
|
+
end
|
112
|
+
url = URI.parse(url)
|
113
|
+
url.host.downcase!
|
114
|
+
url.to_s.to_sym
|
115
|
+
end
|
116
|
+
|
117
|
+
def bad_uaa_url(url, info)
|
118
|
+
info.replace(Misc.server(url.to_s))
|
119
|
+
nil
|
120
|
+
rescue Exception => e
|
121
|
+
"failed to access #{url}: #{e.message}"
|
122
|
+
end
|
123
|
+
|
124
|
+
define_option :force, "--[no-]force", "-f", "set even if target does not respond"
|
125
|
+
desc "target [uaa_url]", "Display current or set new target", :force do |uaa_url|
|
126
|
+
msg, info = nil, {}
|
127
|
+
if uaa_url
|
128
|
+
if uaa_url.to_i.to_s == uaa_url
|
129
|
+
return gripe "invalid target index" unless url = Config.target?(uaa_url.to_i)
|
130
|
+
elsif url = normalize_url(uaa_url)
|
131
|
+
return gripe msg if (msg = bad_uaa_url(url, info)) unless opts[:force] || Config.target?(url)
|
132
|
+
elsif !Config.target?(url = normalize_url(uaa_url, "https")) &&
|
133
|
+
!Config.target?(url = normalize_url(uaa_url, "http"))
|
134
|
+
if opts[:force]
|
135
|
+
url = normalize_url(uaa_url, "https")
|
136
|
+
elsif bad_uaa_url((url = normalize_url(uaa_url, "https")), info)
|
137
|
+
return gripe msg if msg = bad_uaa_url((url = normalize_url(uaa_url, "http")), info)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
Config.target = url # we now have a canonical url set to https if possible
|
141
|
+
update_target_info(info) if info[:prompts]
|
142
|
+
end
|
143
|
+
return say "no target set" unless Config.target
|
144
|
+
return say "target set to #{Config.target}" unless Config.context
|
145
|
+
say "target set to #{Config.target}, with context #{Config.context}"
|
146
|
+
end
|
147
|
+
|
148
|
+
desc "targets", "Display all targets" do
|
149
|
+
cfg = Config.config
|
150
|
+
return say "\nno targets\n" if cfg.empty?
|
151
|
+
cfg.each_with_index { |(k, v), i| pp "#{i} #{v[:current] ? '*' : ' '} #{k}" }
|
152
|
+
say "\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
def config_pp(tgt = nil, ctx = nil)
|
156
|
+
Config.config.each_with_index do |(k, v), i|
|
157
|
+
next if tgt && tgt != k
|
158
|
+
say ""
|
159
|
+
splat = v[:current] ? '*' : ' '
|
160
|
+
pp "[#{i}]#{splat}[#{k}]"
|
161
|
+
v.each {|tk, tv| pp(tv, 2, terminal_columns, tk) unless [:contexts, :current, :prompts].include?(tk)}
|
162
|
+
next unless v[:contexts]
|
163
|
+
v[:contexts].each_with_index do |(sk, sv), si|
|
164
|
+
next if ctx && ctx != sk
|
165
|
+
say ""
|
166
|
+
splat = sv[:current] && v[:current]? '*' : ' '
|
167
|
+
sv.delete(:current)
|
168
|
+
pp "[#{si}]#{splat}[#{sk}]", 2
|
169
|
+
pp sv, 4
|
170
|
+
end
|
171
|
+
end
|
172
|
+
say ""
|
173
|
+
end
|
174
|
+
|
175
|
+
desc "context [name]", "Display or set current context" do |ctx|
|
176
|
+
ctx = ctx.to_i if ctx.to_i.to_s == ctx
|
177
|
+
Config.context = ctx if ctx && Config.valid_context(ctx)
|
178
|
+
(opts[:trace] ? Config.add_opts(trace: true) : Config.delete_attr(:trace)) if opts.key?(:trace)
|
179
|
+
return say "no context set in target #{Config.target}" unless Config.context
|
180
|
+
config_pp Config.target, Config.context
|
181
|
+
end
|
182
|
+
|
183
|
+
desc "contexts", "Display all contexts" do config_pp end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
data/lib/cli/config.rb
ADDED
@@ -0,0 +1,163 @@
|
|
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 'yaml'
|
15
|
+
require 'uaa/util'
|
16
|
+
|
17
|
+
module CF::UAA
|
18
|
+
|
19
|
+
class Config
|
20
|
+
|
21
|
+
class << self; attr_reader :target, :context end
|
22
|
+
|
23
|
+
def self.config; @config ? @config.dup : {} end
|
24
|
+
def self.loaded?; !!@config end
|
25
|
+
def self.yaml; YAML.dump(Util.hash_keys(@config, :tostr)) end
|
26
|
+
def self.target?(tgt) tgt if @config[tgt = subhash_key(@config, tgt)] end
|
27
|
+
|
28
|
+
# if a yaml string is provided, config is loaded from the string, otherwise
|
29
|
+
# config is assumed to be a file name to read and store config.
|
30
|
+
# config can be retrieved in yaml form from Config.yaml
|
31
|
+
def self.load(config = nil)
|
32
|
+
@config = {}
|
33
|
+
return unless config
|
34
|
+
if config =~ /^---/ || config == ""
|
35
|
+
@config = config == "" ? {} : YAML.load(config)
|
36
|
+
@config_file = nil
|
37
|
+
elsif File.exists?(@config_file = config)
|
38
|
+
if (@config = YAML.load_file(@config_file)) && @config.is_a?(Hash)
|
39
|
+
@config.each { |k, v| break @config = nil if k.to_s =~ / / }
|
40
|
+
end
|
41
|
+
unless @config && @config.is_a?(Hash)
|
42
|
+
STDERR.puts "", "Invalid config file #{@config_file}.",
|
43
|
+
"If it's from an old version of uaac, please remove it.",
|
44
|
+
"Note that the uaac command structure has changed.",
|
45
|
+
"Please review the new commands with 'uaac help'", ""
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
else # file doesn't exist, make sure we can write it now
|
49
|
+
File.open(@config_file, 'w') { |f| f.write("--- {}\n\n") }
|
50
|
+
end
|
51
|
+
Util.hash_keys!(@config, :tosym)
|
52
|
+
@context = current_subhash(@config[@target][:contexts]) if @target = current_subhash(@config)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.save
|
56
|
+
File.open(@config_file, 'w') { |f| YAML.dump(Util.hash_keys(@config, :tostr), f) } if @config_file
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.target=(tgt)
|
61
|
+
unless t = set_current_subhash(@config, tgt, @target)
|
62
|
+
raise ArgumentError, "invalid target, #{tgt}"
|
63
|
+
end
|
64
|
+
@context = current_subhash(@config[t][:contexts])
|
65
|
+
save
|
66
|
+
@target = t
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.target_opts(hash)
|
70
|
+
raise ArgumentError, "target not set" unless @target
|
71
|
+
return unless hash and !hash.empty?
|
72
|
+
raise ArgumentError, "'contexts' is a reserved key" if hash.key?(:contexts)
|
73
|
+
@config[@target].merge! Util.hash_keys(hash, :tosym)
|
74
|
+
save
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.target_value(attr)
|
78
|
+
raise ArgumentError, "target not set" unless @target
|
79
|
+
@config[@target][attr]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.context=(ctx)
|
83
|
+
raise ArgumentError, "target not set" unless @target
|
84
|
+
unless c = set_current_subhash(@config[@target][:contexts] ||= {}, ctx, @context)
|
85
|
+
raise ArgumentError, "invalid context, #{ctx}"
|
86
|
+
end
|
87
|
+
save
|
88
|
+
@context = c
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.valid_context(ctx)
|
92
|
+
raise ArgumentError, "target not set" unless @target
|
93
|
+
k = existing_key(@config[@target][:contexts] ||= {}, ctx)
|
94
|
+
raise ArgumentError, "unknown context #{ctx}" unless k
|
95
|
+
k
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.delete(tgt = nil, ctx = nil)
|
99
|
+
if tgt && ctx
|
100
|
+
@config[tgt][:contexts].delete(ctx = valid_context(ctx))
|
101
|
+
@context = nil if tgt == @target && ctx == @context
|
102
|
+
elsif tgt
|
103
|
+
@config.delete(tgt)
|
104
|
+
@target = @context = nil if tgt == @target
|
105
|
+
else
|
106
|
+
@target, @context, @config = nil, nil, {}
|
107
|
+
end
|
108
|
+
save
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.add_opts(hash)
|
112
|
+
raise ArgumentError, "target and context not set" unless @target && @context
|
113
|
+
return unless hash and !hash.empty?
|
114
|
+
@config[@target][:contexts][@context].merge! Util.hash_keys(hash, :tosym)
|
115
|
+
save
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.value(attr)
|
119
|
+
raise ArgumentError, "target and context not set" unless @target && @context
|
120
|
+
@config[@target][:contexts][@context][attr]
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.delete_attr(attr)
|
124
|
+
raise ArgumentError, "target and context not set" unless @target && @context
|
125
|
+
@config[@target][:contexts][@context].delete(attr)
|
126
|
+
end
|
127
|
+
|
128
|
+
# these are all class methods and so can't really be private, but the
|
129
|
+
# methods below here are not intended to be part of the public interface
|
130
|
+
private
|
131
|
+
|
132
|
+
def self.current_subhash(hash)
|
133
|
+
return unless hash
|
134
|
+
key = nil
|
135
|
+
hash.each { |k, v| key ? v.delete(:current) : (key = k if v[:current]) }
|
136
|
+
key
|
137
|
+
end
|
138
|
+
|
139
|
+
# key can be an integer index of the desired subhash or the key symbol or string
|
140
|
+
def self.subhash_key(hash, key)
|
141
|
+
case key
|
142
|
+
when Integer then hash.each_with_index { |(k, v), i| return k if i == key }; nil
|
143
|
+
when String then key.downcase.to_sym
|
144
|
+
when Symbol then key.to_s.downcase.to_sym
|
145
|
+
else nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.existing_key(hash, key)
|
150
|
+
k = subhash_key(hash, key)
|
151
|
+
k if hash[k]
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.set_current_subhash(hash, newcurrent, oldcurrent)
|
155
|
+
return unless k = subhash_key(hash, newcurrent)
|
156
|
+
hash[oldcurrent].delete(:current) if oldcurrent
|
157
|
+
(hash[k] ||= {}).merge!(current: true)
|
158
|
+
k
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|