cloulu 0.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/bin/{cloulu → cl} +0 -0
  2. data/lib/cc_api_stub/applications.rb +53 -0
  3. data/lib/cc_api_stub/domains.rb +16 -0
  4. data/lib/cc_api_stub/frameworks.rb +22 -0
  5. data/lib/cc_api_stub/helper.rb +131 -0
  6. data/lib/cc_api_stub/login.rb +21 -0
  7. data/lib/cc_api_stub/organization_users.rb +21 -0
  8. data/lib/cc_api_stub/organizations.rb +70 -0
  9. data/lib/cc_api_stub/routes.rb +26 -0
  10. data/lib/cc_api_stub/runtimes.rb +22 -0
  11. data/lib/cc_api_stub/service_bindings.rb +22 -0
  12. data/lib/cc_api_stub/service_instances.rb +22 -0
  13. data/lib/cc_api_stub/services.rb +25 -0
  14. data/lib/cc_api_stub/spaces.rb +49 -0
  15. data/lib/cc_api_stub/users.rb +84 -0
  16. data/lib/cc_api_stub.rb +17 -0
  17. data/lib/cfoundry/auth_token.rb +63 -0
  18. data/lib/cfoundry/baseclient.rb +201 -0
  19. data/lib/cfoundry/chatty_hash.rb +46 -0
  20. data/lib/cfoundry/client.rb +46 -0
  21. data/lib/cfoundry/concerns/login_helpers.rb +13 -0
  22. data/lib/cfoundry/errors.rb +160 -0
  23. data/lib/cfoundry/rest_client.rb +299 -0
  24. data/lib/cfoundry/test_support.rb +3 -0
  25. data/lib/cfoundry/trace_helpers.rb +40 -0
  26. data/lib/cfoundry/uaaclient.rb +112 -0
  27. data/lib/cfoundry/upload_helpers.rb +187 -0
  28. data/lib/cfoundry/v1/app.rb +363 -0
  29. data/lib/cfoundry/v1/base.rb +72 -0
  30. data/lib/cfoundry/v1/client.rb +193 -0
  31. data/lib/cfoundry/v1/framework.rb +21 -0
  32. data/lib/cfoundry/v1/model.rb +178 -0
  33. data/lib/cfoundry/v1/model_magic.rb +129 -0
  34. data/lib/cfoundry/v1/runtime.rb +24 -0
  35. data/lib/cfoundry/v1/service.rb +39 -0
  36. data/lib/cfoundry/v1/service_instance.rb +32 -0
  37. data/lib/cfoundry/v1/service_plan.rb +19 -0
  38. data/lib/cfoundry/v1/user.rb +22 -0
  39. data/lib/cfoundry/v2/app.rb +392 -0
  40. data/lib/cfoundry/v2/base.rb +83 -0
  41. data/lib/cfoundry/v2/client.rb +138 -0
  42. data/lib/cfoundry/v2/domain.rb +11 -0
  43. data/lib/cfoundry/v2/framework.rb +14 -0
  44. data/lib/cfoundry/v2/model.rb +148 -0
  45. data/lib/cfoundry/v2/model_magic.rb +449 -0
  46. data/lib/cfoundry/v2/organization.rb +16 -0
  47. data/lib/cfoundry/v2/route.rb +15 -0
  48. data/lib/cfoundry/v2/runtime.rb +12 -0
  49. data/lib/cfoundry/v2/service.rb +19 -0
  50. data/lib/cfoundry/v2/service_auth_token.rb +9 -0
  51. data/lib/cfoundry/v2/service_binding.rb +10 -0
  52. data/lib/cfoundry/v2/service_instance.rb +14 -0
  53. data/lib/cfoundry/v2/service_plan.rb +12 -0
  54. data/lib/cfoundry/v2/space.rb +18 -0
  55. data/lib/cfoundry/v2/user.rb +64 -0
  56. data/lib/cfoundry/validator.rb +39 -0
  57. data/lib/cfoundry/version.rb +4 -0
  58. data/lib/cfoundry/zip.rb +56 -0
  59. data/lib/cfoundry.rb +2 -0
  60. data/lib/manifests-vmc-plugin/errors.rb +21 -0
  61. data/lib/manifests-vmc-plugin/loader/builder.rb +34 -0
  62. data/lib/manifests-vmc-plugin/loader/normalizer.rb +149 -0
  63. data/lib/manifests-vmc-plugin/loader/resolver.rb +79 -0
  64. data/lib/manifests-vmc-plugin/loader.rb +31 -0
  65. data/lib/manifests-vmc-plugin/plugin.rb +145 -0
  66. data/lib/manifests-vmc-plugin/version.rb +3 -0
  67. data/lib/manifests-vmc-plugin.rb +313 -0
  68. data/lib/mothership/base.rb +99 -0
  69. data/lib/mothership/callbacks.rb +85 -0
  70. data/lib/mothership/command.rb +146 -0
  71. data/lib/mothership/errors.rb +38 -0
  72. data/lib/mothership/help/commands.rb +53 -0
  73. data/lib/mothership/help/printer.rb +170 -0
  74. data/lib/mothership/help.rb +64 -0
  75. data/lib/mothership/inputs.rb +189 -0
  76. data/lib/mothership/parser.rb +182 -0
  77. data/lib/mothership/version.rb +3 -0
  78. data/lib/mothership.rb +64 -0
  79. data/lib/tunnel-vmc-plugin/plugin.rb +178 -0
  80. data/lib/tunnel-vmc-plugin/tunnel.rb +308 -0
  81. data/lib/tunnel-vmc-plugin/version.rb +3 -0
  82. data/lib/uaa/http.rb +168 -0
  83. data/lib/uaa/misc.rb +121 -0
  84. data/lib/uaa/scim.rb +292 -0
  85. data/lib/uaa/token_coder.rb +196 -0
  86. data/lib/uaa/token_issuer.rb +255 -0
  87. data/lib/uaa/util.rb +235 -0
  88. data/lib/uaa/version.rb +19 -0
  89. data/lib/uaa.rb +18 -0
  90. data/lib/vmc/cli/app/app.rb +45 -0
  91. data/lib/vmc/cli/app/apps.rb +99 -0
  92. data/lib/vmc/cli/app/base.rb +90 -0
  93. data/lib/vmc/cli/app/crashes.rb +42 -0
  94. data/lib/vmc/cli/app/delete.rb +95 -0
  95. data/lib/vmc/cli/app/deprecated.rb +11 -0
  96. data/lib/vmc/cli/app/env.rb +78 -0
  97. data/lib/vmc/cli/app/files.rb +137 -0
  98. data/lib/vmc/cli/app/health.rb +26 -0
  99. data/lib/vmc/cli/app/instances.rb +53 -0
  100. data/lib/vmc/cli/app/logs.rb +76 -0
  101. data/lib/vmc/cli/app/push/create.rb +165 -0
  102. data/lib/vmc/cli/app/push/interactions.rb +94 -0
  103. data/lib/vmc/cli/app/push/sync.rb +64 -0
  104. data/lib/vmc/cli/app/push.rb +109 -0
  105. data/lib/vmc/cli/app/rename.rb +35 -0
  106. data/lib/vmc/cli/app/restart.rb +20 -0
  107. data/lib/vmc/cli/app/scale.rb +71 -0
  108. data/lib/vmc/cli/app/start.rb +143 -0
  109. data/lib/vmc/cli/app/stats.rb +67 -0
  110. data/lib/vmc/cli/app/stop.rb +27 -0
  111. data/lib/vmc/cli/help.rb +11 -0
  112. data/lib/vmc/cli/interactive.rb +105 -0
  113. data/lib/vmc/cli/route/base.rb +12 -0
  114. data/lib/vmc/cli/route/map.rb +82 -0
  115. data/lib/vmc/cli/route/routes.rb +25 -0
  116. data/lib/vmc/cli/route/unmap.rb +94 -0
  117. data/lib/vmc/cli/service/base.rb +8 -0
  118. data/lib/vmc/cli/service/bind.rb +44 -0
  119. data/lib/vmc/cli/service/create.rb +126 -0
  120. data/lib/vmc/cli/service/delete.rb +86 -0
  121. data/lib/vmc/cli/service/rename.rb +35 -0
  122. data/lib/vmc/cli/service/service.rb +42 -0
  123. data/lib/vmc/cli/service/services.rb +114 -0
  124. data/lib/vmc/cli/service/unbind.rb +38 -0
  125. data/lib/vmc/cli/start/base.rb +94 -0
  126. data/lib/vmc/cli/start/colors.rb +13 -0
  127. data/lib/vmc/cli/start/info.rb +126 -0
  128. data/lib/vmc/cli/start/login.rb +97 -0
  129. data/lib/vmc/cli/start/logout.rb +17 -0
  130. data/lib/vmc/cli/start/target.rb +60 -0
  131. data/lib/vmc/cli/start/target_interactions.rb +37 -0
  132. data/lib/vmc/cli/start/targets.rb +16 -0
  133. data/lib/vmc/cli/user/base.rb +29 -0
  134. data/lib/vmc/cli/user/create.rb +39 -0
  135. data/lib/vmc/cli/user/delete.rb +27 -0
  136. data/lib/vmc/cli/user/passwd.rb +50 -0
  137. data/lib/vmc/cli/user/register.rb +42 -0
  138. data/lib/vmc/cli/user/users.rb +32 -0
  139. data/lib/vmc/cli/v2_check_cli.rb +16 -0
  140. data/lib/vmc/cli.rb +474 -0
  141. data/lib/vmc/constants.rb +13 -0
  142. data/lib/vmc/detect.rb +129 -0
  143. data/lib/vmc/errors.rb +19 -0
  144. data/lib/vmc/plugin.rb +56 -0
  145. data/lib/vmc/spacing.rb +89 -0
  146. data/lib/vmc/spec_helper.rb +1 -0
  147. data/lib/vmc/test_support.rb +6 -0
  148. data/lib/vmc/version.rb +3 -0
  149. data/lib/vmc.rb +8 -0
  150. data/vendor/errors/v1.yml +189 -0
  151. data/vendor/errors/v2.yml +360 -0
  152. 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