cf-uaa-lib 3.6.0 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/cf-uaa-lib.gemspec +1 -0
- data/lib/uaa/http.rb +45 -42
- data/lib/uaa/info.rb +0 -2
- data/lib/uaa/scim.rb +0 -2
- data/lib/uaa/token_coder.rb +19 -1
- data/lib/uaa/token_issuer.rb +0 -2
- data/lib/uaa/version.rb +1 -1
- data/spec/http_spec.rb +74 -55
- data/spec/info_spec.rb +36 -38
- data/spec/integration_spec.rb +173 -110
- data/spec/scim_spec.rb +75 -80
- data/spec/token_issuer_spec.rb +130 -135
- metadata +16 -3
- data/lib/uaa/proxy_options.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41d1d29707fa6dd64486407764cfaa01dddf0b39
|
4
|
+
data.tar.gz: 41a335fbc12c82462b3ca18ffbdd73613f89a7e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 171ff7b4dabbfe88d3939204fe5ea7d6142a9f5912d191979c78920d56b697d43810df85d80a6798702f3684c3414f1b0c0637d41fd772e08ab83a1f4cd50b56
|
7
|
+
data.tar.gz: 076de82f0fc91e1cb8388a310324fb66c86a017d24dbab52313167742400941b4b4865ccdb09f91844750f88edcbc8d2b88991cea7fbdbd7c92a75d5c8c68733
|
data/cf-uaa-lib.gemspec
CHANGED
data/lib/uaa/http.rb
CHANGED
@@ -12,9 +12,8 @@
|
|
12
12
|
#++
|
13
13
|
|
14
14
|
require 'base64'
|
15
|
-
require 'net/http'
|
16
15
|
require 'uaa/util'
|
17
|
-
require '
|
16
|
+
require 'httpclient'
|
18
17
|
|
19
18
|
module CF::UAA
|
20
19
|
|
@@ -46,11 +45,10 @@ class InvalidToken < TargetError; end
|
|
46
45
|
|
47
46
|
# Utility accessors and methods for objects that want to access JSON web APIs.
|
48
47
|
module Http
|
49
|
-
include ProxyOptions
|
50
48
|
|
51
49
|
def self.included(base)
|
52
50
|
base.class_eval do
|
53
|
-
attr_accessor :
|
51
|
+
attr_accessor :skip_ssl_validation, :ssl_ca_file, :ssl_cert_store, :zone
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
@@ -78,30 +76,30 @@ module Http
|
|
78
76
|
# @return [String]
|
79
77
|
def self.basic_auth(name, password)
|
80
78
|
str = "#{name}:#{password}"
|
81
|
-
|
82
|
-
Base64.strict_encode64(str): [str].pack(
|
79
|
+
'Basic ' + (Base64.respond_to?(:strict_encode64)?
|
80
|
+
Base64.strict_encode64(str): [str].pack('m').gsub(/\n/, ''))
|
83
81
|
end
|
84
82
|
|
85
|
-
JSON_UTF8 =
|
86
|
-
FORM_UTF8 =
|
83
|
+
JSON_UTF8 = 'application/json;charset=utf-8'
|
84
|
+
FORM_UTF8 = 'application/x-www-form-urlencoded;charset=utf-8'
|
87
85
|
|
88
86
|
private
|
89
87
|
|
90
88
|
def json_get(target, path = nil, style = nil, headers = {})
|
91
89
|
raise ArgumentError unless style.nil? || style.is_a?(Symbol)
|
92
|
-
json_parse_reply(style, *http_get(target, path, headers.merge(
|
90
|
+
json_parse_reply(style, *http_get(target, path, headers.merge('accept' => JSON_UTF8)))
|
93
91
|
end
|
94
92
|
|
95
93
|
def json_post(target, path, body, headers = {})
|
96
|
-
http_post(target, path, Util.json(body), headers.merge(
|
94
|
+
http_post(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
97
95
|
end
|
98
96
|
|
99
97
|
def json_put(target, path, body, headers = {})
|
100
|
-
http_put(target, path, Util.json(body), headers.merge(
|
98
|
+
http_put(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
101
99
|
end
|
102
100
|
|
103
101
|
def json_patch(target, path, body, headers = {})
|
104
|
-
http_patch(target, path, Util.json(body), headers.merge(
|
102
|
+
http_patch(target, path, Util.json(body), headers.merge('content-type' => JSON_UTF8))
|
105
103
|
end
|
106
104
|
|
107
105
|
def json_parse_reply(style, status, body, headers)
|
@@ -110,17 +108,17 @@ module Http
|
|
110
108
|
raise (status == 404 ? NotFound : BadResponse), "invalid status response: #{status}"
|
111
109
|
end
|
112
110
|
if body && !body.empty? && (status == 204 || headers.nil? ||
|
113
|
-
headers[
|
114
|
-
raise BadResponse,
|
111
|
+
headers['content-type'] !~ /application\/json/i)
|
112
|
+
raise BadResponse, 'received invalid response content or type'
|
115
113
|
end
|
116
114
|
parsed_reply = Util.json_parse(body, style)
|
117
115
|
if status >= 400
|
118
|
-
raise parsed_reply && parsed_reply[
|
119
|
-
InvalidToken.new(parsed_reply) : TargetError.new(parsed_reply),
|
116
|
+
raise parsed_reply && parsed_reply['error'] == 'invalid_token' ?
|
117
|
+
InvalidToken.new(parsed_reply) : TargetError.new(parsed_reply), 'error response'
|
120
118
|
end
|
121
119
|
parsed_reply
|
122
120
|
rescue DecodeError
|
123
|
-
raise BadResponse,
|
121
|
+
raise BadResponse, 'invalid JSON response'
|
124
122
|
end
|
125
123
|
|
126
124
|
def http_get(target, path = nil, headers = {}) request(target, :get, path, nil, headers) end
|
@@ -129,7 +127,7 @@ module Http
|
|
129
127
|
def http_patch(target, path, body, headers = {}) request(target, :patch, path, body, headers) end
|
130
128
|
|
131
129
|
def http_delete(target, path, authorization, zone = nil)
|
132
|
-
hdrs = {
|
130
|
+
hdrs = { 'authorization' => authorization }
|
133
131
|
hdrs['X-Identity-Zone-Subdomain'] = zone if zone
|
134
132
|
status = request(target, :delete, path, nil, hdrs)[0]
|
135
133
|
unless [200, 204].include?(status)
|
@@ -138,7 +136,7 @@ module Http
|
|
138
136
|
end
|
139
137
|
|
140
138
|
def request(target, method, path, body = nil, headers = {})
|
141
|
-
headers[
|
139
|
+
headers['accept'] = headers['content-type'] if headers['content-type'] && !headers['accept']
|
142
140
|
url = "#{target}#{path}"
|
143
141
|
|
144
142
|
logger.debug { "--->\nrequest: #{method} #{url}\n" +
|
@@ -156,44 +154,49 @@ module Http
|
|
156
154
|
end
|
157
155
|
|
158
156
|
def net_http_request(url, method, body, headers)
|
159
|
-
raise ArgumentError unless reqtype = {:delete => Net::HTTP::Delete,
|
160
|
-
:get => Net::HTTP::Get, :post => Net::HTTP::Post, :put => Net::HTTP::Put, :patch => Net::HTTP::Patch}[method]
|
161
|
-
headers["content-length"] = body.length if body
|
162
157
|
uri = URI.parse(url)
|
163
|
-
req = reqtype.new(uri.request_uri)
|
164
|
-
headers.each { |k, v| req[k] = v }
|
165
158
|
http = http_request(uri)
|
166
|
-
|
167
|
-
|
168
|
-
|
159
|
+
headers['content-length'] = body.length.to_s if body
|
160
|
+
case method
|
161
|
+
when :get, :delete
|
162
|
+
response = http.send(method, uri, nil, headers)
|
163
|
+
when :post, :put, :patch
|
164
|
+
response = http.send(method, uri, body, headers)
|
165
|
+
else
|
166
|
+
raise ArgumentError
|
167
|
+
end
|
169
168
|
|
169
|
+
unless response.status
|
170
|
+
raise HTTPException.new "Can't parse response from the server #{response.content}"
|
171
|
+
end
|
172
|
+
response_headers = {}
|
173
|
+
response.header.all.each { |k, v| response_headers[k.downcase] = v }
|
174
|
+
return [response.status.to_i, response.content, response_headers]
|
170
175
|
rescue OpenSSL::SSL::SSLError => e
|
171
176
|
raise SSLException, "Invalid SSL Cert for #{url}. Use '--skip-ssl-validation' to continue with an insecure target"
|
172
177
|
rescue URI::Error, SocketError, SystemCallError => e
|
173
178
|
raise BadTarget, "error: #{e.message}"
|
174
|
-
rescue Net::HTTPBadResponse => e
|
175
|
-
raise HTTPException, "HTTP exception: #{e.class}: #{e}"
|
176
179
|
end
|
177
180
|
|
178
181
|
def http_request(uri)
|
179
|
-
cache_key = URI.join(uri.to_s,
|
182
|
+
cache_key = URI.join(uri.to_s, '/')
|
180
183
|
@http_cache ||= {}
|
181
184
|
return @http_cache[cache_key] if @http_cache[cache_key]
|
182
185
|
|
183
|
-
http = Net::HTTP.new(uri.host, uri.port, *proxy_options_for(uri))
|
184
|
-
|
185
186
|
if uri.is_a?(URI::HTTPS)
|
186
|
-
http
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
187
|
+
http = HTTPClient.new.tap do |c|
|
188
|
+
if skip_ssl_validation
|
189
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
190
|
+
elsif ssl_ca_file
|
191
|
+
c.ssl_config.set_trust_ca File.expand_path(ssl_ca_file)
|
192
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
193
|
+
elsif ssl_cert_store
|
194
|
+
c.ssl_config.cert_store = ssl_cert_store
|
195
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
196
|
+
end
|
196
197
|
end
|
198
|
+
else
|
199
|
+
http = HTTPClient.new
|
197
200
|
end
|
198
201
|
|
199
202
|
@http_cache[cache_key] = http
|
data/lib/uaa/info.rb
CHANGED
@@ -35,8 +35,6 @@ class Info
|
|
35
35
|
self.ssl_ca_file = options[:ssl_ca_file]
|
36
36
|
self.ssl_cert_store = options[:ssl_cert_store]
|
37
37
|
self.symbolize_keys = options[:symbolize_keys]
|
38
|
-
self.http_proxy = options[:http_proxy]
|
39
|
-
self.https_proxy = options[:https_proxy]
|
40
38
|
end
|
41
39
|
|
42
40
|
# sets whether the keys in returned hashes should be symbols.
|
data/lib/uaa/scim.rb
CHANGED
@@ -109,8 +109,6 @@ class Scim
|
|
109
109
|
self.skip_ssl_validation = options[:skip_ssl_validation]
|
110
110
|
self.ssl_ca_file = options[:ssl_ca_file]
|
111
111
|
self.ssl_cert_store = options[:ssl_cert_store]
|
112
|
-
self.http_proxy = options[:http_proxy]
|
113
|
-
self.https_proxy = options[:https_proxy]
|
114
112
|
@zone = options[:zone]
|
115
113
|
end
|
116
114
|
|
data/lib/uaa/token_coder.rb
CHANGED
@@ -113,7 +113,7 @@ class TokenCoder
|
|
113
113
|
signature = Util.decode64(crypto_segment)
|
114
114
|
if ["HS256", "HS384", "HS512"].include?(algo)
|
115
115
|
raise InvalidSignature, "Signature verification failed" unless
|
116
|
-
options[:skey] && signature
|
116
|
+
options[:skey] && constant_time_compare(signature, OpenSSL::HMAC.digest(init_digest(algo), options[:skey], signing_input))
|
117
117
|
elsif ["RS256", "RS384", "RS512"].include?(algo)
|
118
118
|
raise InvalidSignature, "Signature verification failed" unless
|
119
119
|
options[:pkey] && options[:pkey].verify(init_digest(algo), signature, signing_input)
|
@@ -123,6 +123,24 @@ class TokenCoder
|
|
123
123
|
payload
|
124
124
|
end
|
125
125
|
|
126
|
+
# Takes constant time to compare 2 strings (HMAC digests in this case)
|
127
|
+
# to avoid timing attacks while comparing the HMAC digests
|
128
|
+
# @param [String] a: the first digest to compare
|
129
|
+
# @param [String] b: the second digest to compare
|
130
|
+
# @return [boolean] true if they are equal, false otherwise
|
131
|
+
def self.constant_time_compare(a, b)
|
132
|
+
if a.length != b.length
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
result = 0
|
137
|
+
a.chars.zip(b.chars).each do |x, y|
|
138
|
+
result |= x.ord ^ y.ord
|
139
|
+
end
|
140
|
+
|
141
|
+
result == 0
|
142
|
+
end
|
143
|
+
|
126
144
|
# Creates a new token en/decoder for a service that is associated with
|
127
145
|
# the the audience_ids, the symmetrical token validation key, and the
|
128
146
|
# public and/or private keys.
|
data/lib/uaa/token_issuer.rb
CHANGED
@@ -112,8 +112,6 @@ class TokenIssuer
|
|
112
112
|
self.skip_ssl_validation = options[:skip_ssl_validation]
|
113
113
|
self.ssl_ca_file = options[:ssl_ca_file]
|
114
114
|
self.ssl_cert_store = options[:ssl_cert_store]
|
115
|
-
self.http_proxy = options[:http_proxy]
|
116
|
-
self.https_proxy = options[:https_proxy]
|
117
115
|
end
|
118
116
|
|
119
117
|
# Allows an app to discover what credentials are required for
|
data/lib/uaa/version.rb
CHANGED
data/spec/http_spec.rb
CHANGED
@@ -15,83 +15,102 @@ require 'spec_helper'
|
|
15
15
|
require 'uaa/http'
|
16
16
|
require 'uaa/version'
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
describe Http do
|
18
|
+
describe CF::UAA::Http do
|
21
19
|
|
22
20
|
class HttpTest
|
23
|
-
include Http
|
21
|
+
include CF::UAA::Http
|
24
22
|
|
25
23
|
public :http_get
|
26
24
|
end
|
27
25
|
|
28
26
|
let(:http_instance) { HttpTest.new }
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
let(:http_double) do
|
29
|
+
http_double = double('http').as_null_object
|
30
|
+
expect(HTTPClient).to receive(:new).and_return(http_double)
|
31
|
+
http_double
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:cert_store) { double('OpenSSL::X509::Store') }
|
35
|
+
|
36
|
+
describe 'set_request_handler' do
|
37
|
+
it 'sets a request handler' do
|
38
|
+
http_instance.set_request_handler do |url, method, body, headers|
|
39
|
+
[200, 'body', {'content-type' => 'text/plain'}]
|
40
|
+
end
|
41
|
+
status, body, resp_headers = http_instance.http_get('http://example.com')
|
42
|
+
status.should == 200
|
43
|
+
body.should == 'body'
|
44
|
+
resp_headers['content-type'].should == 'text/plain'
|
33
45
|
end
|
34
|
-
status, body, resp_headers = http_instance.http_get("http://example.com")
|
35
|
-
status.should == 200
|
36
|
-
body.should == "body"
|
37
|
-
resp_headers["content-type"].should == "text/plain"
|
38
46
|
end
|
39
47
|
|
40
|
-
|
41
|
-
reply_double = double('http reply', each_header: {}).as_null_object
|
42
|
-
http_double = double('http', request: reply_double, new: nil)
|
43
|
-
Net::HTTP.stub(:new).and_return(http_double)
|
44
|
-
http_instance.http_proxy = 'user:password@http-proxy.example.com:1234'
|
45
|
-
http_instance.https_proxy = 'user:password@https-proxy.example.com:1234'
|
48
|
+
describe 'http_get' do
|
46
49
|
|
47
|
-
|
50
|
+
context 'when response has no status' do
|
51
|
+
let(:response) { double('http::message') }
|
52
|
+
it 'raises an HTTPException error' do
|
53
|
+
expect(response).to receive(:status)
|
54
|
+
expect(response).to receive(:content).and_return('TEST')
|
55
|
+
expect(http_double).to receive(:get).and_return(response)
|
56
|
+
expect { http_instance.http_get('https://example.com') }.to raise_error(CF::UAA::HTTPException, "Can't parse response from the server TEST")
|
57
|
+
end
|
58
|
+
end
|
48
59
|
|
49
|
-
|
50
|
-
|
60
|
+
context 'when certificate is not valid' do
|
61
|
+
it 'raises an SSLException' do
|
62
|
+
expect(http_double).to receive(:get).and_raise(OpenSSL::SSL::SSLError)
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
http_double.stub(:request).and_raise(OpenSSL::SSL::SSLError)
|
64
|
+
expect { http_instance.http_get('https://example.com') }.to raise_error(CF::UAA::SSLException)
|
65
|
+
end
|
66
|
+
end
|
56
67
|
|
57
|
-
|
58
|
-
|
68
|
+
context 'when skipping ssl validation' do
|
69
|
+
let(:ssl_config) { double('ssl_config') }
|
59
70
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
71
|
+
it 'sets verify mode to VERIFY_NONE' do
|
72
|
+
http_instance.skip_ssl_validation = true
|
73
|
+
|
74
|
+
expect(http_double).to receive(:ssl_config).and_return(ssl_config)
|
75
|
+
expect(ssl_config).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
76
|
+
http_instance.http_get('https://uncached.example.com')
|
77
|
+
end
|
78
|
+
end
|
64
79
|
|
65
|
-
|
66
|
-
|
80
|
+
context 'when validating ssl' do
|
81
|
+
it 'does not set verify mode' do
|
82
|
+
expect(http_double).not_to receive(:ssl_config)
|
83
|
+
http_instance.http_get('https://example.com')
|
84
|
+
end
|
85
|
+
end
|
67
86
|
|
68
|
-
|
69
|
-
http_instance.http_get("https://uncached.example.com")
|
70
|
-
expect(http_double).to have_received(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
71
|
-
end
|
87
|
+
context 'when ssl certificate is provided' do
|
72
88
|
|
73
|
-
|
74
|
-
http_double = double('http').as_null_object
|
75
|
-
Net::HTTP.stub(:new).and_return(http_double)
|
89
|
+
let(:ssl_config) { double('ssl_config') }
|
76
90
|
|
77
|
-
|
78
|
-
|
91
|
+
it 'passes it' do
|
92
|
+
http_instance.ssl_ca_file = '/fake-ca-file'
|
79
93
|
|
80
|
-
|
81
|
-
|
82
|
-
|
94
|
+
expect(http_double).to receive(:ssl_config).and_return(ssl_config).twice
|
95
|
+
expect(ssl_config).to receive(:set_trust_ca).with('/fake-ca-file')
|
96
|
+
expect(ssl_config).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
|
83
97
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
Net::HTTP.stub(:new).and_return(http_double)
|
98
|
+
http_instance.http_get('https://uncached.example.com')
|
99
|
+
end
|
100
|
+
end
|
88
101
|
|
89
|
-
|
90
|
-
|
102
|
+
context 'when ssl cert store is provided' do
|
103
|
+
let(:ssl_config) { double('ssl_config') }
|
91
104
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
105
|
+
it 'passes it' do
|
106
|
+
http_instance.ssl_cert_store = cert_store
|
107
|
+
|
108
|
+
expect(http_double).to receive(:ssl_config).and_return(ssl_config).twice
|
109
|
+
expect(ssl_config).to receive(:cert_store=).with(cert_store)
|
110
|
+
expect(ssl_config).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
|
96
111
|
|
97
|
-
|
112
|
+
http_instance.http_get('https://uncached.example.com')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/spec/info_spec.rb
CHANGED
@@ -19,44 +19,42 @@ module CF::UAA
|
|
19
19
|
describe Info do
|
20
20
|
let(:options) { {} }
|
21
21
|
let(:uaa_info) { Info.new(target, options) }
|
22
|
-
let(:target) {
|
22
|
+
let(:target) { 'https://login.cloudfoundry.com' }
|
23
23
|
let(:authorization) { nil }
|
24
24
|
|
25
25
|
before do
|
26
26
|
uaa_info.set_request_handler do |url, method, body, headers|
|
27
27
|
url.should == target_url
|
28
28
|
method.should == :get
|
29
|
-
headers[
|
30
|
-
headers[
|
31
|
-
headers[
|
32
|
-
[200, response_body, {
|
29
|
+
headers['content-type'].should be_nil
|
30
|
+
headers['accept'].gsub(/\s/, '').should =~ /application\/json;charset=utf-8/i
|
31
|
+
headers['authorization'].should == authorization
|
32
|
+
[200, response_body, {'content-type' => 'application/json'}]
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
describe
|
37
|
-
let(:options) { {:
|
36
|
+
describe 'initialize' do
|
37
|
+
let(:options) { {:skip_ssl_validation => true} }
|
38
38
|
|
39
|
-
it
|
40
|
-
uaa_info.http_proxy.should == 'http-proxy.com'
|
41
|
-
uaa_info.https_proxy.should == 'https-proxy.com'
|
39
|
+
it 'sets proxy information' do
|
42
40
|
uaa_info.skip_ssl_validation == true
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
46
|
-
describe
|
47
|
-
let(:target_url) {
|
44
|
+
describe 'getting server info' do
|
45
|
+
let(:target_url) { 'https://login.cloudfoundry.com/login' }
|
48
46
|
let(:response_body) { '{"commit_id":"12345","prompts":["one","two"]}' }
|
49
47
|
|
50
|
-
it
|
48
|
+
it 'gets server info' do
|
51
49
|
result = uaa_info.server
|
52
|
-
result[
|
53
|
-
result[
|
50
|
+
result['prompts'].should_not be_nil
|
51
|
+
result['commit_id'].should_not be_nil
|
54
52
|
end
|
55
53
|
|
56
|
-
context
|
54
|
+
context 'with symbolize_keys keys true' do
|
57
55
|
let(:options) { {:symbolize_keys => true} }
|
58
56
|
|
59
|
-
it
|
57
|
+
it 'gets server info' do
|
60
58
|
result = uaa_info.server
|
61
59
|
result[:prompts].should_not be_nil
|
62
60
|
result[:commit_id].should_not be_nil
|
@@ -64,63 +62,63 @@ module CF::UAA
|
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
67
|
-
describe
|
68
|
-
let(:target) {
|
69
|
-
let(:target_url) {
|
65
|
+
describe 'getting UAA target' do
|
66
|
+
let(:target) { 'https://login.cloudfoundry.com' }
|
67
|
+
let(:target_url) { 'https://login.cloudfoundry.com/login' }
|
70
68
|
let(:response_body) { '{"links":{"uaa":"https://uaa.cloudfoundry.com"},"prompts":["one","two"]}' }
|
71
69
|
|
72
|
-
it
|
70
|
+
it 'gets UAA target' do
|
73
71
|
result = uaa_info.discover_uaa
|
74
|
-
result.should ==
|
72
|
+
result.should == 'https://uaa.cloudfoundry.com'
|
75
73
|
end
|
76
74
|
|
77
75
|
context "when there is no 'links' key present" do
|
78
|
-
let(:target) {
|
79
|
-
let(:target_url) {
|
76
|
+
let(:target) { 'https://uaa.cloudfoundry.com' }
|
77
|
+
let(:target_url) { 'https://uaa.cloudfoundry.com/login' }
|
80
78
|
let(:response_body) { '{ "prompts" : ["one","two"]} ' }
|
81
79
|
|
82
|
-
it
|
80
|
+
it 'returns the target url' do
|
83
81
|
result = uaa_info.discover_uaa
|
84
|
-
result.should ==
|
82
|
+
result.should == 'https://uaa.cloudfoundry.com'
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
88
|
-
context
|
86
|
+
context 'with symbolize_keys keys true' do
|
89
87
|
let(:options) { {:symbolize_keys => true} }
|
90
88
|
|
91
|
-
it
|
89
|
+
it 'gets UAA target' do
|
92
90
|
result = uaa_info.discover_uaa
|
93
|
-
result.should ==
|
91
|
+
result.should == 'https://uaa.cloudfoundry.com'
|
94
92
|
end
|
95
93
|
end
|
96
94
|
end
|
97
95
|
|
98
|
-
describe
|
99
|
-
let(:target_url) {
|
96
|
+
describe 'whoami' do
|
97
|
+
let(:target_url) { 'https://login.cloudfoundry.com/userinfo?schema=openid' }
|
100
98
|
let(:response_body) { '{"user_id":"1111-1111-1111-1111","user_name":"user","given_name":"first","family_name":"last","name":"first last","email":"email@example.com"}' }
|
101
99
|
let(:authorization) { 'authentication_token' }
|
102
100
|
|
103
|
-
it
|
101
|
+
it 'returns the user info' do
|
104
102
|
result = uaa_info.whoami(authorization)
|
105
103
|
result['email'].should == 'email@example.com'
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
109
|
-
describe
|
110
|
-
let(:target_url) {
|
107
|
+
describe 'validation_key' do
|
108
|
+
let(:target_url) { 'https://login.cloudfoundry.com/token_key' }
|
111
109
|
let(:response_body) { '{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nabc123\n-----END PUBLIC KEY-----\n"}' }
|
112
110
|
|
113
|
-
it
|
111
|
+
it 'returns the key data' do
|
114
112
|
result = uaa_info.validation_key(authorization)
|
115
113
|
result['alg'].should == 'SHA256withRSA'
|
116
114
|
end
|
117
115
|
end
|
118
116
|
|
119
|
-
describe
|
120
|
-
let(:target_url) {
|
117
|
+
describe 'validation keys' do
|
118
|
+
let(:target_url) { 'https://login.cloudfoundry.com/token_keys' }
|
121
119
|
let(:response_body) { '{ "keys": [ { "kid": "the_key", "alg": "SHA256withRSA", "value": "-----BEGIN PUBLIC KEY-----\nabc123\n-----END PUBLIC KEY-----\n", "kty": "RSA", "use": "sig", "n": "Ufn7Qc", "e": "EEXZ" }, { "kid": "the_other_key", "alg": "SHA256withRSA", "value": "-----BEGIN PUBLIC KEY-----\ndef456\n-----END PUBLIC KEY-----\n", "kty": "RSA", "use": "sig", "n": "AMcW9/P", "e": "AQAB" } ] }' }
|
122
120
|
|
123
|
-
it
|
121
|
+
it 'returns a hash of keys' do
|
124
122
|
result = uaa_info.validation_keys_hash(authorization)
|
125
123
|
|
126
124
|
the_key = result['the_key']
|