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