cf-uaac 1.3.0

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