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.
@@ -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