cloulu 0.0.0 → 0.1.1
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/bin/{cloulu → cl} +0 -0
- data/lib/cc_api_stub/applications.rb +53 -0
- data/lib/cc_api_stub/domains.rb +16 -0
- data/lib/cc_api_stub/frameworks.rb +22 -0
- data/lib/cc_api_stub/helper.rb +131 -0
- data/lib/cc_api_stub/login.rb +21 -0
- data/lib/cc_api_stub/organization_users.rb +21 -0
- data/lib/cc_api_stub/organizations.rb +70 -0
- data/lib/cc_api_stub/routes.rb +26 -0
- data/lib/cc_api_stub/runtimes.rb +22 -0
- data/lib/cc_api_stub/service_bindings.rb +22 -0
- data/lib/cc_api_stub/service_instances.rb +22 -0
- data/lib/cc_api_stub/services.rb +25 -0
- data/lib/cc_api_stub/spaces.rb +49 -0
- data/lib/cc_api_stub/users.rb +84 -0
- data/lib/cc_api_stub.rb +17 -0
- data/lib/cfoundry/auth_token.rb +63 -0
- data/lib/cfoundry/baseclient.rb +201 -0
- data/lib/cfoundry/chatty_hash.rb +46 -0
- data/lib/cfoundry/client.rb +46 -0
- data/lib/cfoundry/concerns/login_helpers.rb +13 -0
- data/lib/cfoundry/errors.rb +160 -0
- data/lib/cfoundry/rest_client.rb +299 -0
- data/lib/cfoundry/test_support.rb +3 -0
- data/lib/cfoundry/trace_helpers.rb +40 -0
- data/lib/cfoundry/uaaclient.rb +112 -0
- data/lib/cfoundry/upload_helpers.rb +187 -0
- data/lib/cfoundry/v1/app.rb +363 -0
- data/lib/cfoundry/v1/base.rb +72 -0
- data/lib/cfoundry/v1/client.rb +193 -0
- data/lib/cfoundry/v1/framework.rb +21 -0
- data/lib/cfoundry/v1/model.rb +178 -0
- data/lib/cfoundry/v1/model_magic.rb +129 -0
- data/lib/cfoundry/v1/runtime.rb +24 -0
- data/lib/cfoundry/v1/service.rb +39 -0
- data/lib/cfoundry/v1/service_instance.rb +32 -0
- data/lib/cfoundry/v1/service_plan.rb +19 -0
- data/lib/cfoundry/v1/user.rb +22 -0
- data/lib/cfoundry/v2/app.rb +392 -0
- data/lib/cfoundry/v2/base.rb +83 -0
- data/lib/cfoundry/v2/client.rb +138 -0
- data/lib/cfoundry/v2/domain.rb +11 -0
- data/lib/cfoundry/v2/framework.rb +14 -0
- data/lib/cfoundry/v2/model.rb +148 -0
- data/lib/cfoundry/v2/model_magic.rb +449 -0
- data/lib/cfoundry/v2/organization.rb +16 -0
- data/lib/cfoundry/v2/route.rb +15 -0
- data/lib/cfoundry/v2/runtime.rb +12 -0
- data/lib/cfoundry/v2/service.rb +19 -0
- data/lib/cfoundry/v2/service_auth_token.rb +9 -0
- data/lib/cfoundry/v2/service_binding.rb +10 -0
- data/lib/cfoundry/v2/service_instance.rb +14 -0
- data/lib/cfoundry/v2/service_plan.rb +12 -0
- data/lib/cfoundry/v2/space.rb +18 -0
- data/lib/cfoundry/v2/user.rb +64 -0
- data/lib/cfoundry/validator.rb +39 -0
- data/lib/cfoundry/version.rb +4 -0
- data/lib/cfoundry/zip.rb +56 -0
- data/lib/cfoundry.rb +2 -0
- data/lib/manifests-vmc-plugin/errors.rb +21 -0
- data/lib/manifests-vmc-plugin/loader/builder.rb +34 -0
- data/lib/manifests-vmc-plugin/loader/normalizer.rb +149 -0
- data/lib/manifests-vmc-plugin/loader/resolver.rb +79 -0
- data/lib/manifests-vmc-plugin/loader.rb +31 -0
- data/lib/manifests-vmc-plugin/plugin.rb +145 -0
- data/lib/manifests-vmc-plugin/version.rb +3 -0
- data/lib/manifests-vmc-plugin.rb +313 -0
- data/lib/mothership/base.rb +99 -0
- data/lib/mothership/callbacks.rb +85 -0
- data/lib/mothership/command.rb +146 -0
- data/lib/mothership/errors.rb +38 -0
- data/lib/mothership/help/commands.rb +53 -0
- data/lib/mothership/help/printer.rb +170 -0
- data/lib/mothership/help.rb +64 -0
- data/lib/mothership/inputs.rb +189 -0
- data/lib/mothership/parser.rb +182 -0
- data/lib/mothership/version.rb +3 -0
- data/lib/mothership.rb +64 -0
- data/lib/tunnel-vmc-plugin/plugin.rb +178 -0
- data/lib/tunnel-vmc-plugin/tunnel.rb +308 -0
- data/lib/tunnel-vmc-plugin/version.rb +3 -0
- data/lib/uaa/http.rb +168 -0
- data/lib/uaa/misc.rb +121 -0
- data/lib/uaa/scim.rb +292 -0
- data/lib/uaa/token_coder.rb +196 -0
- data/lib/uaa/token_issuer.rb +255 -0
- data/lib/uaa/util.rb +235 -0
- data/lib/uaa/version.rb +19 -0
- data/lib/uaa.rb +18 -0
- data/lib/vmc/cli/app/app.rb +45 -0
- data/lib/vmc/cli/app/apps.rb +99 -0
- data/lib/vmc/cli/app/base.rb +90 -0
- data/lib/vmc/cli/app/crashes.rb +42 -0
- data/lib/vmc/cli/app/delete.rb +95 -0
- data/lib/vmc/cli/app/deprecated.rb +11 -0
- data/lib/vmc/cli/app/env.rb +78 -0
- data/lib/vmc/cli/app/files.rb +137 -0
- data/lib/vmc/cli/app/health.rb +26 -0
- data/lib/vmc/cli/app/instances.rb +53 -0
- data/lib/vmc/cli/app/logs.rb +76 -0
- data/lib/vmc/cli/app/push/create.rb +165 -0
- data/lib/vmc/cli/app/push/interactions.rb +94 -0
- data/lib/vmc/cli/app/push/sync.rb +64 -0
- data/lib/vmc/cli/app/push.rb +109 -0
- data/lib/vmc/cli/app/rename.rb +35 -0
- data/lib/vmc/cli/app/restart.rb +20 -0
- data/lib/vmc/cli/app/scale.rb +71 -0
- data/lib/vmc/cli/app/start.rb +143 -0
- data/lib/vmc/cli/app/stats.rb +67 -0
- data/lib/vmc/cli/app/stop.rb +27 -0
- data/lib/vmc/cli/help.rb +11 -0
- data/lib/vmc/cli/interactive.rb +105 -0
- data/lib/vmc/cli/route/base.rb +12 -0
- data/lib/vmc/cli/route/map.rb +82 -0
- data/lib/vmc/cli/route/routes.rb +25 -0
- data/lib/vmc/cli/route/unmap.rb +94 -0
- data/lib/vmc/cli/service/base.rb +8 -0
- data/lib/vmc/cli/service/bind.rb +44 -0
- data/lib/vmc/cli/service/create.rb +126 -0
- data/lib/vmc/cli/service/delete.rb +86 -0
- data/lib/vmc/cli/service/rename.rb +35 -0
- data/lib/vmc/cli/service/service.rb +42 -0
- data/lib/vmc/cli/service/services.rb +114 -0
- data/lib/vmc/cli/service/unbind.rb +38 -0
- data/lib/vmc/cli/start/base.rb +94 -0
- data/lib/vmc/cli/start/colors.rb +13 -0
- data/lib/vmc/cli/start/info.rb +126 -0
- data/lib/vmc/cli/start/login.rb +97 -0
- data/lib/vmc/cli/start/logout.rb +17 -0
- data/lib/vmc/cli/start/target.rb +60 -0
- data/lib/vmc/cli/start/target_interactions.rb +37 -0
- data/lib/vmc/cli/start/targets.rb +16 -0
- data/lib/vmc/cli/user/base.rb +29 -0
- data/lib/vmc/cli/user/create.rb +39 -0
- data/lib/vmc/cli/user/delete.rb +27 -0
- data/lib/vmc/cli/user/passwd.rb +50 -0
- data/lib/vmc/cli/user/register.rb +42 -0
- data/lib/vmc/cli/user/users.rb +32 -0
- data/lib/vmc/cli/v2_check_cli.rb +16 -0
- data/lib/vmc/cli.rb +474 -0
- data/lib/vmc/constants.rb +13 -0
- data/lib/vmc/detect.rb +129 -0
- data/lib/vmc/errors.rb +19 -0
- data/lib/vmc/plugin.rb +56 -0
- data/lib/vmc/spacing.rb +89 -0
- data/lib/vmc/spec_helper.rb +1 -0
- data/lib/vmc/test_support.rb +6 -0
- data/lib/vmc/version.rb +3 -0
- data/lib/vmc.rb +8 -0
- data/vendor/errors/v1.yml +189 -0
- data/vendor/errors/v2.yml +360 -0
- metadata +303 -190
|
@@ -0,0 +1,196 @@
|
|
|
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 "openssl"
|
|
15
|
+
require "uaa/util"
|
|
16
|
+
|
|
17
|
+
module CF::UAA
|
|
18
|
+
|
|
19
|
+
# this code does not support the given token signature algorithim
|
|
20
|
+
class SignatureNotSupported < DecodeError; end
|
|
21
|
+
|
|
22
|
+
# this instance policy does not accept the given token signature algorithim
|
|
23
|
+
class SignatureNotAccepted < DecodeError; end
|
|
24
|
+
|
|
25
|
+
class InvalidSignature < DecodeError; end
|
|
26
|
+
class InvalidTokenFormat < DecodeError; end
|
|
27
|
+
class TokenExpired < AuthError; end
|
|
28
|
+
class InvalidAudience < AuthError; end
|
|
29
|
+
|
|
30
|
+
# This class is for OAuth Resource Servers.
|
|
31
|
+
# Resource Servers get tokens and need to validate and decode them,
|
|
32
|
+
# but they do not obtain them from the Authorization Server. This
|
|
33
|
+
# class is for resource servers which accept bearer JWT tokens.
|
|
34
|
+
#
|
|
35
|
+
# For more on JWT, see the JSON Web Token RFC here:
|
|
36
|
+
# {http://tools.ietf.org/id/draft-ietf-oauth-json-web-token-05.html}
|
|
37
|
+
#
|
|
38
|
+
# An instance of this class can be used to decode and verify the contents
|
|
39
|
+
# of a bearer token. Methods of this class can validate token signatures
|
|
40
|
+
# with a secret or public key, and they can also enforce that the token
|
|
41
|
+
# is for a particular audience.
|
|
42
|
+
class TokenCoder
|
|
43
|
+
|
|
44
|
+
def self.init_digest(algo) # @private
|
|
45
|
+
OpenSSL::Digest::Digest.new(algo.sub('HS', 'sha').sub('RS', 'sha'))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.normalize_options(opts) # @private
|
|
49
|
+
opts = opts.dup
|
|
50
|
+
pk = opts[:pkey]
|
|
51
|
+
opts[:pkey] = OpenSSL::PKey::RSA.new(pk) if pk && !pk.is_a?(OpenSSL::PKey::PKey)
|
|
52
|
+
opts[:audience_ids] = Util.arglist(opts[:audience_ids])
|
|
53
|
+
opts[:algorithm] = 'HS256' unless opts[:algorithm]
|
|
54
|
+
opts[:verify] = true unless opts.key?(:verify)
|
|
55
|
+
opts[:accept_algorithms] = Util.arglist(opts[:accept_algorithms],
|
|
56
|
+
["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"])
|
|
57
|
+
opts
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Constructs a signed JWT.
|
|
61
|
+
# @param token_body Contents of the token in any object that can be converted to JSON.
|
|
62
|
+
# @param options (see #initialize)
|
|
63
|
+
# @return [String] a signed JWT token string in the form "xxxx.xxxxx.xxxx".
|
|
64
|
+
def self.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil)
|
|
65
|
+
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
|
66
|
+
# deprecated: def self.encode(token_body, skey, pkey = nil, algo = 'HS256')
|
|
67
|
+
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
|
68
|
+
options = {:skey => options }
|
|
69
|
+
options[:pkey], options[:algorithm] = obsolete1, obsolete2
|
|
70
|
+
end
|
|
71
|
+
options = normalize_options(options)
|
|
72
|
+
algo = options[:algorithm]
|
|
73
|
+
segments = [Util.json_encode64("typ" => "JWT", "alg" => algo)]
|
|
74
|
+
segments << Util.json_encode64(token_body)
|
|
75
|
+
if ["HS256", "HS384", "HS512"].include?(algo)
|
|
76
|
+
sig = OpenSSL::HMAC.digest(init_digest(algo), options[:skey], segments.join('.'))
|
|
77
|
+
elsif ["RS256", "RS384", "RS512"].include?(algo)
|
|
78
|
+
sig = options[:pkey].sign(init_digest(algo), segments.join('.'))
|
|
79
|
+
elsif algo == "none"
|
|
80
|
+
sig = ""
|
|
81
|
+
else
|
|
82
|
+
raise SignatureNotSupported, "unsupported signing method"
|
|
83
|
+
end
|
|
84
|
+
segments << Util.encode64(sig)
|
|
85
|
+
segments.join('.')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Decodes a JWT token and optionally verifies the signature. Both a
|
|
89
|
+
# symmetrical key and a public key can be provided for signature verification.
|
|
90
|
+
# The JWT header indicates what signature algorithm was used and the
|
|
91
|
+
# corresponding key is used to verify the signature (if +verify+ is true).
|
|
92
|
+
# @param [String] token A JWT token as returned by {TokenCoder.encode}
|
|
93
|
+
# @param options (see #initialize)
|
|
94
|
+
# @return [Hash] the token contents
|
|
95
|
+
def self.decode(token, options = {}, obsolete1 = nil, obsolete2 = nil)
|
|
96
|
+
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
|
97
|
+
# deprecated: def self.decode(token, skey = nil, pkey = nil, verify = true)
|
|
98
|
+
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
|
99
|
+
options = {:skey => options }
|
|
100
|
+
options[:pkey], options[:verify] = obsolete1, obsolete2
|
|
101
|
+
end
|
|
102
|
+
options = normalize_options(options)
|
|
103
|
+
segments = token.split('.')
|
|
104
|
+
raise InvalidTokenFormat, "Not enough or too many segments" unless [2,3].include? segments.length
|
|
105
|
+
header_segment, payload_segment, crypto_segment = segments
|
|
106
|
+
signing_input = [header_segment, payload_segment].join('.')
|
|
107
|
+
header = Util.json_decode64(header_segment)
|
|
108
|
+
payload = Util.json_decode64(payload_segment, (:sym if options[:symbolize_keys]))
|
|
109
|
+
return payload unless options[:verify]
|
|
110
|
+
raise SignatureNotAccepted, "Signature algorithm not accepted" unless
|
|
111
|
+
options[:accept_algorithms].include?(algo = header["alg"])
|
|
112
|
+
return payload if algo == 'none'
|
|
113
|
+
signature = Util.decode64(crypto_segment)
|
|
114
|
+
if ["HS256", "HS384", "HS512"].include?(algo)
|
|
115
|
+
raise InvalidSignature, "Signature verification failed" unless
|
|
116
|
+
options[:skey] && signature == OpenSSL::HMAC.digest(init_digest(algo), options[:skey], signing_input)
|
|
117
|
+
elsif ["RS256", "RS384", "RS512"].include?(algo)
|
|
118
|
+
raise InvalidSignature, "Signature verification failed" unless
|
|
119
|
+
options[:pkey] && options[:pkey].verify(init_digest(algo), signature, signing_input)
|
|
120
|
+
else
|
|
121
|
+
raise SignatureNotSupported, "Algorithm not supported"
|
|
122
|
+
end
|
|
123
|
+
payload
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Creates a new token en/decoder for a service that is associated with
|
|
127
|
+
# the the audience_ids, the symmetrical token validation key, and the
|
|
128
|
+
# public and/or private keys.
|
|
129
|
+
# @param [Hash] options Supported options:
|
|
130
|
+
# * :audience_ids [Array<String>, String] -- An array or space separated
|
|
131
|
+
# string of values which indicate the token is intended for this service
|
|
132
|
+
# instance. It will be compared with tokens as they are decoded to ensure
|
|
133
|
+
# that the token was intended for this audience.
|
|
134
|
+
# * :skey [String] -- used to sign and validate tokens using symmetrical
|
|
135
|
+
# key algoruthms
|
|
136
|
+
# * :pkey [String, File, OpenSSL::PKey::PKey] -- may be a String or File in
|
|
137
|
+
# PEM or DER formats. May include public and/or private key data. The
|
|
138
|
+
# private key is used to sign tokens and the public key is used to
|
|
139
|
+
# validate tokens.
|
|
140
|
+
# * :algorithm [String] -- Sets default used for encoding. May be HS256,
|
|
141
|
+
# HS384, HS512, RS256, RS384, RS512, or none.
|
|
142
|
+
# * :verify [String] -- Verifies signatures when decoding tokens. Defaults
|
|
143
|
+
# to +true+.
|
|
144
|
+
# * :accept_algorithms [String, Array<String>] -- An Array or space separated
|
|
145
|
+
# string of values which list what algorthms are accepted for token
|
|
146
|
+
# signatures. Defaults to all possible values of :algorithm except 'none'.
|
|
147
|
+
# @note the TokenCoder instance must be configured with the appropriate
|
|
148
|
+
# key material to support particular algorithm families and operations
|
|
149
|
+
# -- i.e. :pkey must include a private key in order to sign tokens with
|
|
150
|
+
# the RS algorithms.
|
|
151
|
+
def initialize(options = {}, obsolete1 = nil, obsolete2 = nil)
|
|
152
|
+
unless options.is_a?(Hash) && obsolete1.nil? && obsolete2.nil?
|
|
153
|
+
# deprecated: def initialize(audience_ids, skey, pkey = nil)
|
|
154
|
+
warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash."
|
|
155
|
+
options = {:audience_ids => options }
|
|
156
|
+
options[:skey], options[:pkey] = obsolete1, obsolete2
|
|
157
|
+
end
|
|
158
|
+
@options = self.class.normalize_options(options)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Encode a JWT token. Takes a hash of values to use as the token body.
|
|
162
|
+
# Returns a signed token in JWT format (header, body, signature).
|
|
163
|
+
# @param token_body (see TokenCoder.encode)
|
|
164
|
+
# @param [String] algorithm -- overrides default. See {#initialize} for possible values.
|
|
165
|
+
# @return (see TokenCoder.encode)
|
|
166
|
+
def encode(token_body = {}, algorithm = nil)
|
|
167
|
+
token_body[:aud] = @options[:audience_ids] if @options[:audience_ids] && !token_body[:aud] && !token_body['aud']
|
|
168
|
+
token_body[:exp] = Time.now.to_i + 7 * 24 * 60 * 60 unless token_body[:exp] || token_body['exp']
|
|
169
|
+
self.class.encode(token_body, algorithm ? @options.merge(:algorithm => algorithm) : @options)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns hash of values decoded from the token contents. If the
|
|
173
|
+
# audience_ids were specified in the options to this instance (see #initialize)
|
|
174
|
+
# and the token does not contain one or more of those audience_ids, an
|
|
175
|
+
# AuthError will be raised. AuthError is raised if the token has expired.
|
|
176
|
+
# @param [String] auth_header (see Scim.initialize#auth_header)
|
|
177
|
+
# @return (see TokenCoder.decode)
|
|
178
|
+
def decode(auth_header)
|
|
179
|
+
unless auth_header && (tkn = auth_header.split(' ')).length == 2 && tkn[0] =~ /^bearer$/i
|
|
180
|
+
raise InvalidTokenFormat, "invalid authentication header: #{auth_header}"
|
|
181
|
+
end
|
|
182
|
+
reply = self.class.decode(tkn[1], @options)
|
|
183
|
+
auds = Util.arglist(reply[:aud] || reply['aud'])
|
|
184
|
+
if @options[:audience_ids] && (!auds || (auds & @options[:audience_ids]).empty?)
|
|
185
|
+
raise InvalidAudience, "invalid audience: #{auds}"
|
|
186
|
+
end
|
|
187
|
+
exp = reply[:exp] || reply['exp']
|
|
188
|
+
unless exp.is_a?(Integer) && exp > Time.now.to_i
|
|
189
|
+
raise TokenExpired, "token expired"
|
|
190
|
+
end
|
|
191
|
+
reply
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
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 'securerandom'
|
|
15
|
+
require 'uaa/http'
|
|
16
|
+
|
|
17
|
+
module CF::UAA
|
|
18
|
+
|
|
19
|
+
# The TokenInfo class is returned by various TokenIssuer methods. It holds access
|
|
20
|
+
# and refresh tokens as well as token meta-data such as token type and
|
|
21
|
+
# expiration time. See {TokenInfo#info} for contents.
|
|
22
|
+
class TokenInfo
|
|
23
|
+
|
|
24
|
+
# Information about the current token. The info hash MUST include
|
|
25
|
+
# access_token, token_type and scope (if granted scope differs from requested
|
|
26
|
+
# scope). It should include expires_in. It may include refresh_token, scope,
|
|
27
|
+
# and other values from the auth server.
|
|
28
|
+
# @return [Hash]
|
|
29
|
+
attr_reader :info
|
|
30
|
+
|
|
31
|
+
# Normally instantiated by {TokenIssuer}.
|
|
32
|
+
def initialize(info) @info = info end
|
|
33
|
+
|
|
34
|
+
# Constructs a string for use in an authorization header from the contents of
|
|
35
|
+
# the TokenInfo.
|
|
36
|
+
# @return [String] Typically a string such as "bearer xxxx.xxxx.xxxx".
|
|
37
|
+
def auth_header
|
|
38
|
+
"#{@info[:token_type] || @info['token_type']} #{@info[:access_token] || @info['access_token']}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Client Apps that want to get access to resource servers on behalf of their
|
|
44
|
+
# users need to get tokens via authcode and implicit flows,
|
|
45
|
+
# request scopes, etc., but they don't need to process tokens. This
|
|
46
|
+
# class is for these use cases.
|
|
47
|
+
#
|
|
48
|
+
# In general most of this class is an implementation of the client pieces of
|
|
49
|
+
# the OAuth2 protocol. See {http://tools.ietf.org/html/rfc6749}
|
|
50
|
+
class TokenIssuer
|
|
51
|
+
|
|
52
|
+
include Http
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def random_state; SecureRandom.hex end
|
|
57
|
+
|
|
58
|
+
def parse_implicit_params(encoded_params, state)
|
|
59
|
+
params = Util.decode_form(encoded_params)
|
|
60
|
+
raise BadResponse, "mismatched state" unless state && params.delete('state') == state
|
|
61
|
+
raise TargetError.new(params), "error response from #{@target}" if params['error']
|
|
62
|
+
raise BadResponse, "no type and token" unless params['token_type'] && params['access_token']
|
|
63
|
+
exp = params['expires_in'].to_i
|
|
64
|
+
params['expires_in'] = exp if exp.to_s == params['expires_in']
|
|
65
|
+
TokenInfo.new(Util.hash_keys!(params, @key_style))
|
|
66
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
67
|
+
raise BadResponse, "received invalid response from target #{@target}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# returns a CF::UAA::TokenInfo object which includes the access token and metadata.
|
|
71
|
+
def request_token(params)
|
|
72
|
+
if scope = Util.arglist(params.delete(:scope))
|
|
73
|
+
params[:scope] = Util.strlist(scope)
|
|
74
|
+
end
|
|
75
|
+
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8,
|
|
76
|
+
'authorization' => Http.basic_auth(@client_id, @client_secret) }
|
|
77
|
+
reply = json_parse_reply(@key_style, *request(@token_target, :post,
|
|
78
|
+
'/oauth/token', Util.encode_form(params), headers))
|
|
79
|
+
raise BadResponse unless reply[jkey :token_type] && reply[jkey :access_token]
|
|
80
|
+
TokenInfo.new(reply)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def authorize_path_args(response_type, redirect_uri, scope, state = random_state, args = {})
|
|
84
|
+
params = args.merge(:client_id => @client_id, :response_type => response_type,
|
|
85
|
+
:redirect_uri => redirect_uri, :state => state)
|
|
86
|
+
params[:scope] = scope = Util.strlist(scope) if scope = Util.arglist(scope)
|
|
87
|
+
params[:nonce], params[:response_type] = state, "#{response_type} id_token" if scope && scope.include?('openid')
|
|
88
|
+
"/oauth/authorize?#{Util.encode_form(params)}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def jkey(k) @key_style ? k : k.to_s end
|
|
92
|
+
|
|
93
|
+
public
|
|
94
|
+
|
|
95
|
+
# @param [String] target The base URL of a UAA's oauth authorize endpoint.
|
|
96
|
+
# For example the target would be {https://login.cloudfoundry.com} if the
|
|
97
|
+
# endpoint is {https://login.cloudfoundry.com/oauth/authorize}.
|
|
98
|
+
# The target would be {http://localhost:8080/uaa} if the endpoint
|
|
99
|
+
# is {http://localhost:8080/uaa/oauth/authorize}.
|
|
100
|
+
# @param [String] client_id The oauth2 client id, see
|
|
101
|
+
# {http://tools.ietf.org/html/rfc6749#section-2.2}
|
|
102
|
+
# @param [String] client_secret Needed to authenticate the client for all
|
|
103
|
+
# grant types except implicit.
|
|
104
|
+
# @param [Hash] options can be
|
|
105
|
+
# * +:token_target+, the base URL of the oauth token endpoint -- if
|
|
106
|
+
# not specified, +target+ is used.
|
|
107
|
+
# * +:symbolize_keys+, if true, returned hash keys are symbols.
|
|
108
|
+
def initialize(target, client_id, client_secret = nil, options = {})
|
|
109
|
+
@target, @client_id, @client_secret = target, client_id, client_secret
|
|
110
|
+
@token_target = options[:token_target] || target
|
|
111
|
+
@key_style = options[:symbolize_keys] ? :sym : nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Allows an app to discover what credentials are required for
|
|
115
|
+
# {#implicit_grant_with_creds}.
|
|
116
|
+
# @return [Hash] of credential names with type and suggested prompt value,
|
|
117
|
+
# e.g. !{"username":["text","Email"],"password":["password","Password"]}
|
|
118
|
+
def prompts
|
|
119
|
+
reply = json_get(@target, '/login')
|
|
120
|
+
return reply[jkey :prompts] if reply && reply[jkey :prompts]
|
|
121
|
+
raise BadResponse, "No prompts in response from target #{@target}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Gets an access token in a single call to the UAA with the user
|
|
125
|
+
# credentials used for authentication.
|
|
126
|
+
# @param credentials should be an object such as a hash that can be converted
|
|
127
|
+
# to a json representation of the credential name/value pairs corresponding to
|
|
128
|
+
# the keys retrieved by {#prompts}.
|
|
129
|
+
# @return [TokenInfo]
|
|
130
|
+
def implicit_grant_with_creds(credentials, scope = nil)
|
|
131
|
+
# this manufactured redirect_uri is a convention here, not part of OAuth2
|
|
132
|
+
redir_uri = "https://uaa.cloudfoundry.com/redirect/#{@client_id}"
|
|
133
|
+
uri = authorize_path_args("token", redir_uri, scope, state = random_state)
|
|
134
|
+
|
|
135
|
+
# the accept header is only here so the uaa will issue error replies in json to aid debugging
|
|
136
|
+
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8 }
|
|
137
|
+
body = Util.encode_form(credentials.merge(:source => 'credentials'))
|
|
138
|
+
status, body, headers = request(@target, :post, uri, body, headers)
|
|
139
|
+
raise BadResponse, "status #{status}" unless status == 302
|
|
140
|
+
req_uri, reply_uri = URI.parse(redir_uri), URI.parse(headers['location'])
|
|
141
|
+
fragment, reply_uri.fragment = reply_uri.fragment, nil
|
|
142
|
+
raise BadResponse, "bad location header" unless req_uri == reply_uri
|
|
143
|
+
parse_implicit_params(fragment, state)
|
|
144
|
+
rescue URI::Error => e
|
|
145
|
+
raise BadResponse, "bad location header in reply: #{e.message}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Constructs a uri that the client is to return to the browser to direct
|
|
149
|
+
# the user to the authorization server to get an authcode.
|
|
150
|
+
# @param [String] redirect_uri (see #authcode_uri)
|
|
151
|
+
# @return [String]
|
|
152
|
+
def implicit_uri(redirect_uri, scope = nil)
|
|
153
|
+
@target + authorize_path_args("token", redirect_uri, scope)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Gets a token via an implicit grant.
|
|
157
|
+
# @param [String] implicit_uri must be from a previous call to
|
|
158
|
+
# {#implicit_uri}, contains state used to validate the contents of the
|
|
159
|
+
# reply from the server.
|
|
160
|
+
# @param [String] callback_fragment must be the fragment portion of the URL
|
|
161
|
+
# received by the user's browser after the server redirects back to the
|
|
162
|
+
# +redirect_uri+ that was given to {#implicit_uri}. How the application
|
|
163
|
+
# gets the contents of the fragment is application specific -- usually
|
|
164
|
+
# some javascript in the page at the +redirect_uri+.
|
|
165
|
+
# @see http://tools.ietf.org/html/rfc6749#section-4.2
|
|
166
|
+
# @return [TokenInfo]
|
|
167
|
+
def implicit_grant(implicit_uri, callback_fragment)
|
|
168
|
+
in_params = Util.decode_form(URI.parse(implicit_uri).query)
|
|
169
|
+
unless in_params['state'] && in_params['redirect_uri']
|
|
170
|
+
raise ArgumentError, "redirect must happen before implicit grant"
|
|
171
|
+
end
|
|
172
|
+
parse_implicit_params(callback_fragment, in_params['state'])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# A UAA extension to OAuth2 that allows a client to pre-authenticate a
|
|
176
|
+
# user at the start of an authorization code flow. By passing in the
|
|
177
|
+
# user's credentials the server can establish a session with the user's
|
|
178
|
+
# browser without reprompting for authentication. This is useful for
|
|
179
|
+
# user account management apps so that they can create a user account,
|
|
180
|
+
# or reset a password for the user, without requiring the user to type
|
|
181
|
+
# in their credentials again.
|
|
182
|
+
# @param [String] credentials (see #implicit_grant_with_creds)
|
|
183
|
+
# @param [String] redirect_uri (see #authcode_uri)
|
|
184
|
+
# @return (see #authcode_uri)
|
|
185
|
+
def autologin_uri(redirect_uri, credentials, scope = nil)
|
|
186
|
+
headers = {'content-type' => FORM_UTF8, 'accept' => JSON_UTF8,
|
|
187
|
+
'authorization' => Http.basic_auth(@client_id, @client_secret) }
|
|
188
|
+
body = Util.encode_form(credentials)
|
|
189
|
+
reply = json_parse_reply(nil, *request(@target, :post, "/autologin", body, headers))
|
|
190
|
+
raise BadResponse, "no autologin code in reply" unless reply['code']
|
|
191
|
+
@target + authorize_path_args('code', redirect_uri, scope,
|
|
192
|
+
random_state, :code => reply['code'])
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Constructs a uri that the client is to return to the browser to direct
|
|
196
|
+
# the user to the authorization server to get an authcode.
|
|
197
|
+
# @param [String] redirect_uri is embedded in the returned uri so the server
|
|
198
|
+
# can redirect the user back to the caller's endpoint.
|
|
199
|
+
# @return [String] uri which
|
|
200
|
+
def authcode_uri(redirect_uri, scope = nil)
|
|
201
|
+
@target + authorize_path_args('code', redirect_uri, scope)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Uses the instance client credentials in addition to +callback_query+
|
|
205
|
+
# to get a token via the authorization code grant.
|
|
206
|
+
# @param [String] authcode_uri must be from a previous call to {#authcode_uri}
|
|
207
|
+
# and contains state used to validate the contents of the reply from the
|
|
208
|
+
# server.
|
|
209
|
+
# @param [String] callback_query must be the query portion of the URL
|
|
210
|
+
# received by the client after the user's browser is redirected back from
|
|
211
|
+
# the server. It contains the authorization code.
|
|
212
|
+
# @see http://tools.ietf.org/html/rfc6749#section-4.1
|
|
213
|
+
# @return [TokenInfo]
|
|
214
|
+
def authcode_grant(authcode_uri, callback_query)
|
|
215
|
+
ac_params = Util.decode_form(URI.parse(authcode_uri).query)
|
|
216
|
+
unless ac_params['state'] && ac_params['redirect_uri']
|
|
217
|
+
raise ArgumentError, "authcode redirect must happen before authcode grant"
|
|
218
|
+
end
|
|
219
|
+
begin
|
|
220
|
+
params = Util.decode_form(callback_query)
|
|
221
|
+
authcode = params['code']
|
|
222
|
+
raise BadResponse unless params['state'] == ac_params['state'] && authcode
|
|
223
|
+
rescue URI::InvalidURIError, ArgumentError, BadResponse
|
|
224
|
+
raise BadResponse, "received invalid response from target #{@target}"
|
|
225
|
+
end
|
|
226
|
+
request_token(:grant_type => 'authorization_code', :code => authcode,
|
|
227
|
+
:redirect_uri => ac_params['redirect_uri'])
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Uses the instance client credentials in addition to the +username+
|
|
231
|
+
# and +password+ to get a token via the owner password grant.
|
|
232
|
+
# See {http://tools.ietf.org/html/rfc6749#section-4.3}.
|
|
233
|
+
# @return [TokenInfo]
|
|
234
|
+
def owner_password_grant(username, password, scope = nil)
|
|
235
|
+
request_token(:grant_type => 'password', :username => username,
|
|
236
|
+
:password => password, :scope => scope)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Uses the instance client credentials to get a token with a client
|
|
240
|
+
# credentials grant. See http://tools.ietf.org/html/rfc6749#section-4.4
|
|
241
|
+
# @return [TokenInfo]
|
|
242
|
+
def client_credentials_grant(scope = nil)
|
|
243
|
+
request_token(:grant_type => 'client_credentials', :scope => scope)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Uses the instance client credentials and the given +refresh_token+ to get
|
|
247
|
+
# a new access token. See http://tools.ietf.org/html/rfc6749#section-6
|
|
248
|
+
# @return [TokenInfo] which may include a new refresh token as well as an access token.
|
|
249
|
+
def refresh_token_grant(refresh_token, scope = nil)
|
|
250
|
+
request_token(:grant_type => 'refresh_token', :refresh_token => refresh_token, :scope => scope)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
end
|
data/lib/uaa/util.rb
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
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 'multi_json'
|
|
15
|
+
require "base64"
|
|
16
|
+
require 'logger'
|
|
17
|
+
require 'uri'
|
|
18
|
+
|
|
19
|
+
# Cloud Foundry namespace
|
|
20
|
+
module CF
|
|
21
|
+
# Namespace for User Account and Authentication service
|
|
22
|
+
module UAA end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Logger # @private
|
|
26
|
+
Severity::TRACE = Severity::DEBUG - 1
|
|
27
|
+
def trace(progname, &blk); add(Logger::Severity::TRACE, nil, progname, &blk) end
|
|
28
|
+
def trace? ; @level <= Logger::Severity::TRACE end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module CF::UAA
|
|
32
|
+
|
|
33
|
+
# Useful parent class. All CF::UAA exceptions are derived from this.
|
|
34
|
+
class UAAError < RuntimeError; end
|
|
35
|
+
|
|
36
|
+
# Indicates an authentication error.
|
|
37
|
+
class AuthError < UAAError; end
|
|
38
|
+
|
|
39
|
+
# Indicates an error occurred decoding a token, base64 decoding, or JSON.
|
|
40
|
+
class DecodeError < UAAError; end
|
|
41
|
+
|
|
42
|
+
# Helper functions useful to the UAA client APIs
|
|
43
|
+
class Util
|
|
44
|
+
|
|
45
|
+
# General method to transform a hash key to a given style. Useful when
|
|
46
|
+
# dealing with HTTP headers and various protocol tags that tend to contain
|
|
47
|
+
# '-' characters and are case-insensitive and want to use them as keys in
|
|
48
|
+
# ruby hashes. Useful for dealing with {http://www.simplecloud.info/ SCIM}
|
|
49
|
+
# case-insensitive attribute names to normalize all attribute names (downcase).
|
|
50
|
+
#
|
|
51
|
+
# @param [String, Symbol] key current key
|
|
52
|
+
# @param [Symbol] style can be sym, downsym, down, str, [un]dash, [un]camel, nil, none
|
|
53
|
+
# @return [String, Symbol] new key
|
|
54
|
+
def self.hash_key(key, style)
|
|
55
|
+
case style
|
|
56
|
+
when nil, :none then key
|
|
57
|
+
when :downsym then key.to_s.downcase.to_sym
|
|
58
|
+
when :sym then key.to_sym
|
|
59
|
+
when :str then key.to_s
|
|
60
|
+
when :down then key.to_s.downcase
|
|
61
|
+
when :dash then key.to_s.downcase.tr('_', '-')
|
|
62
|
+
when :undash then key.to_s.downcase.tr('-', '_').to_sym
|
|
63
|
+
when :uncamel then key.to_s.gsub(/([A-Z])([^A-Z]*)/,'_\1\2').downcase.to_sym
|
|
64
|
+
when :camel then key.to_s.gsub(/(_[a-z])([^_]*)/) { $1[1].upcase + $2 }
|
|
65
|
+
else raise ArgumentError, "unknown hash key style: #{style}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Modifies obj in place changing any hash keys to style. Recursively modifies
|
|
70
|
+
# subordinate hashes.
|
|
71
|
+
# @param style (see Util.hash_key)
|
|
72
|
+
# @return modified obj
|
|
73
|
+
def self.hash_keys!(obj, style = nil)
|
|
74
|
+
return obj if style == :none
|
|
75
|
+
return obj.each {|o| hash_keys!(o, style)} if obj.is_a? Array
|
|
76
|
+
return obj unless obj.is_a? Hash
|
|
77
|
+
newkeys, nk = {}, nil
|
|
78
|
+
obj.delete_if { |k, v|
|
|
79
|
+
hash_keys!(v, style)
|
|
80
|
+
newkeys[nk] = v unless (nk = hash_key(k, style)) == k
|
|
81
|
+
nk != k
|
|
82
|
+
}
|
|
83
|
+
obj.merge!(newkeys)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Makes a new copy of obj with hash keys to style. Recursively modifies
|
|
87
|
+
# subordinate hashes.
|
|
88
|
+
# @param style (see Util.hash_key)
|
|
89
|
+
# @return obj or new object if hash keys were changed
|
|
90
|
+
def self.hash_keys(obj, style = nil)
|
|
91
|
+
return obj.collect {|o| hash_keys(o, style)} if obj.is_a? Array
|
|
92
|
+
return obj unless obj.is_a? Hash
|
|
93
|
+
obj.each_with_object({}) {|(k, v), h|
|
|
94
|
+
h[hash_key(k, style)] = hash_keys(v, style)
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# handle ruby 1.8.7 compatibility for form encoding
|
|
99
|
+
if URI.respond_to?(:encode_www_form_component)
|
|
100
|
+
def self.encode_component(str) URI.encode_www_form_component(str) end #@private
|
|
101
|
+
def self.decode_component(str) URI.decode_www_form_component(str) end #@private
|
|
102
|
+
else
|
|
103
|
+
def self.encode_component(str) # @private
|
|
104
|
+
str.to_s.gsub(/([^ a-zA-Z0-9*_.-]+)/) {
|
|
105
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
|
106
|
+
}.tr(' ', '+')
|
|
107
|
+
end
|
|
108
|
+
def self.decode_component(str) # @private
|
|
109
|
+
str.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {[$1.delete('%')].pack('H*')}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Takes an x-www-form-urlencoded string and returns a hash of utf-8 key/value
|
|
114
|
+
# pairs. Useful for OAuth parameters. Raises ArgumentError if a key occurs
|
|
115
|
+
# more than once, which is a restriction of OAuth query strings.
|
|
116
|
+
# OAuth parameters are case sensitive, scim parameters are case-insensitive.
|
|
117
|
+
# @see http://tools.ietf.org/html/rfc6749#section-3.1
|
|
118
|
+
# @param [String] url_encoded_pairs in an x-www-form-urlencoded string
|
|
119
|
+
# @param style (see Util.hash_key)
|
|
120
|
+
# @return [Hash] of key value pairs
|
|
121
|
+
def self.decode_form(url_encoded_pairs, style = nil)
|
|
122
|
+
pairs = {}
|
|
123
|
+
url_encoded_pairs.split(/[&;]/).each do |pair|
|
|
124
|
+
k, v = pair.split('=', 2).collect { |v| decode_component(v) }
|
|
125
|
+
raise "duplicate keys in form parameters" if pairs.key?(k = hash_key(k, style))
|
|
126
|
+
pairs[k] = v
|
|
127
|
+
end
|
|
128
|
+
pairs
|
|
129
|
+
rescue Exception => e
|
|
130
|
+
raise ArgumentError, e.message
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Encode an object into x-www-form-urlencoded string suitable for oauth2.
|
|
134
|
+
# @note that ruby 1.9.3 converts form components to utf-8. Ruby 1.8.7
|
|
135
|
+
# users must ensure all data is in utf-8 format before passing to form encode.
|
|
136
|
+
# @param [Hash] obj a hash of key/value pairs to be encoded.
|
|
137
|
+
# @see http://tools.ietf.org/html/rfc6749#section-3.1
|
|
138
|
+
def self.encode_form(obj)
|
|
139
|
+
obj.map {|k, v| encode_component(k) << '=' << encode_component(v)}.join('&')
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Converts +obj+ to JSON
|
|
143
|
+
# @return [String] obj in JSON form.
|
|
144
|
+
def self.json(obj) MultiJson.dump(obj) end
|
|
145
|
+
|
|
146
|
+
# Converts +obj+ to nicely formatted JSON
|
|
147
|
+
# @return [String] obj in formatted json
|
|
148
|
+
def self.json_pretty(obj) MultiJson.dump(obj, :pretty => true) end
|
|
149
|
+
|
|
150
|
+
# Converts +obj+ to a URL-safe base 64 encoded string
|
|
151
|
+
# @return [String]
|
|
152
|
+
def self.json_encode64(obj = {}) encode64(json(obj)) end
|
|
153
|
+
|
|
154
|
+
# Decodes base64 encoding of JSON data.
|
|
155
|
+
# @param [String] str
|
|
156
|
+
# @param style (see Util.hash_key)
|
|
157
|
+
# @return [Hash]
|
|
158
|
+
def self.json_decode64(str, style = nil) json_parse(decode64(str), style) end
|
|
159
|
+
|
|
160
|
+
# Encodes +obj+ as a URL-safe base 64 encoded string, with trailing padding removed.
|
|
161
|
+
# @return [String]
|
|
162
|
+
def self.encode64(obj)
|
|
163
|
+
str = Base64.respond_to?(:urlsafe_encode64)? Base64.urlsafe_encode64(obj):
|
|
164
|
+
[obj].pack("m").tr("+/", "-_")
|
|
165
|
+
str.gsub!(/(\n|=*$)/, '')
|
|
166
|
+
str
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Decodes a URL-safe base 64 encoded string. Adds padding if necessary.
|
|
170
|
+
# @return [String] decoded string
|
|
171
|
+
def self.decode64(str)
|
|
172
|
+
return unless str
|
|
173
|
+
pad = str.length % 4
|
|
174
|
+
str = str + '=' * (4 - pad) if pad > 0
|
|
175
|
+
Base64.respond_to?(:urlsafe_decode64) ?
|
|
176
|
+
Base64.urlsafe_decode64(str) : Base64.decode64(str.tr('-_', '+/'))
|
|
177
|
+
rescue ArgumentError
|
|
178
|
+
raise DecodeError, "invalid base64 encoding"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Parses a JSON string.
|
|
182
|
+
# @param style (see Util.hash_key)
|
|
183
|
+
# @return [Hash] parsed data
|
|
184
|
+
def self.json_parse(str, style = nil)
|
|
185
|
+
hash_keys!(MultiJson.load(str), style) if str && !str.empty?
|
|
186
|
+
rescue MultiJson::DecodeError
|
|
187
|
+
raise DecodeError, "json decoding error"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Converts obj to a string and truncates if over limit.
|
|
191
|
+
# @return [String]
|
|
192
|
+
def self.truncate(obj, limit = 50)
|
|
193
|
+
return obj.to_s if limit == 0
|
|
194
|
+
limit = limit < 5 ? 1 : limit - 4
|
|
195
|
+
str = obj.to_s[0..limit]
|
|
196
|
+
str.length > limit ? str + '...': str
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Converts common input formats into array of strings.
|
|
200
|
+
# Many parameters in these classes can be given as arrays, or as a list of
|
|
201
|
+
# arguments separated by spaces or commas. This method handles the possible
|
|
202
|
+
# inputs and returns an array of strings.
|
|
203
|
+
# @return [Array<String>]
|
|
204
|
+
def self.arglist(arg, default_arg = nil)
|
|
205
|
+
arg = default_arg unless arg
|
|
206
|
+
return arg if arg.nil? || arg.respond_to?(:join)
|
|
207
|
+
raise ArgumentError, "arg must be Array or space|comma delimited strings" unless arg.respond_to?(:split)
|
|
208
|
+
arg.split(/[\s\,]+/).reject { |e| e.empty? }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Joins arrays of strings into a single string. Reverse of {Util.arglist}.
|
|
212
|
+
# @param [Object, #join] arg
|
|
213
|
+
# @param [String] delim delimiter to put between strings.
|
|
214
|
+
# @return [String]
|
|
215
|
+
def self.strlist(arg, delim = ' ')
|
|
216
|
+
arg.respond_to?(:join) ? arg.join(delim) : arg.to_s if arg
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Set the default logger used by the higher level classes.
|
|
220
|
+
# @param [String, Symbol] level such as info, debug trace.
|
|
221
|
+
# @param [IO] sink output for log messages, defaults to $stdout
|
|
222
|
+
# @return [Logger]
|
|
223
|
+
def self.default_logger(level = nil, sink = nil)
|
|
224
|
+
if sink || !@default_logger
|
|
225
|
+
@default_logger = Logger.new(sink || $stdout)
|
|
226
|
+
level = :info unless level
|
|
227
|
+
@default_logger.formatter = Proc.new { |severity, time, pname, msg| puts msg }
|
|
228
|
+
end
|
|
229
|
+
@default_logger.level = Logger::Severity.const_get(level.to_s.upcase) if level
|
|
230
|
+
@default_logger
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
end
|