cf-uaa-lib 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/Gemfile +16 -0
- data/README.md +43 -0
- data/Rakefile +50 -0
- data/cf-uaa-lib.gemspec +45 -0
- data/lib/uaa.rb +18 -0
- data/lib/uaa/http.rb +147 -0
- data/lib/uaa/misc.rb +67 -0
- data/lib/uaa/scim.rb +206 -0
- data/lib/uaa/token_coder.rb +124 -0
- data/lib/uaa/token_issuer.rb +173 -0
- data/lib/uaa/util.rb +159 -0
- data/lib/uaa/version.rb +18 -0
- data/spec/http_spec.rb +37 -0
- data/spec/misc_spec.rb +42 -0
- data/spec/scim_spec.rb +117 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/token_coder_spec.rb +128 -0
- data/spec/token_issuer_spec.rb +190 -0
- metadata +210 -0
data/lib/uaa/version.rb
ADDED
@@ -0,0 +1,18 @@
|
|
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
|
+
module CF
|
15
|
+
module UAA
|
16
|
+
VERSION = "1.3.0"
|
17
|
+
end
|
18
|
+
end
|
data/spec/http_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
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 'spec_helper'
|
15
|
+
require 'uaa/http'
|
16
|
+
require 'uaa/version'
|
17
|
+
|
18
|
+
module CF::UAA
|
19
|
+
|
20
|
+
describe Http do
|
21
|
+
|
22
|
+
include Http
|
23
|
+
include SpecHelper
|
24
|
+
|
25
|
+
it "sets a request handler" do
|
26
|
+
set_request_handler do |req|
|
27
|
+
[200, "body", {"content-type" => "text/plain"}]
|
28
|
+
end
|
29
|
+
status, body, resp_headers = http_get("http://example.com")
|
30
|
+
status.should == 200
|
31
|
+
body.should == "body"
|
32
|
+
resp_headers["content-type"].should == "text/plain"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/misc_spec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
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 'spec_helper'
|
15
|
+
require 'uaa/misc'
|
16
|
+
|
17
|
+
module CF::UAA
|
18
|
+
|
19
|
+
describe Misc do
|
20
|
+
|
21
|
+
include SpecHelper
|
22
|
+
|
23
|
+
before :all do
|
24
|
+
#Util.default_logger(:trace)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "gets server info" do
|
28
|
+
Misc.set_request_handler do |url, method, body, headers|
|
29
|
+
url.should == "https://uaa.cloudfoundry.com/login"
|
30
|
+
method.should == :get
|
31
|
+
headers["content-type"].should be_nil
|
32
|
+
headers["accept"].should =~ /application\/json/
|
33
|
+
[200, '{"commit_id":"12345","prompts":["one","two"]}', {"content-type" => "application/json"}]
|
34
|
+
end
|
35
|
+
result = Misc.server("https://uaa.cloudfoundry.com")
|
36
|
+
result["prompts"].should_not be_nil
|
37
|
+
result["commit_id"].should_not be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/scim_spec.rb
ADDED
@@ -0,0 +1,117 @@
|
|
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 'spec_helper'
|
15
|
+
require 'uaa/scim'
|
16
|
+
|
17
|
+
module CF::UAA
|
18
|
+
|
19
|
+
describe Scim do
|
20
|
+
|
21
|
+
include SpecHelper
|
22
|
+
|
23
|
+
before :all do
|
24
|
+
#Util.default_logger(:trace)
|
25
|
+
@authheader, @target = "bEareR xyz", "https://test.target"
|
26
|
+
@scim = Scim.new(@target, @authheader)
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { @scim }
|
30
|
+
|
31
|
+
def check_headers(headers, content, accept)
|
32
|
+
headers["content-type"].should =~ /application\/json/ if content == :json
|
33
|
+
headers["content-type"].should be_nil unless content
|
34
|
+
headers["accept"].should =~ /application\/json/ if accept == :json
|
35
|
+
headers["accept"].should be_nil unless accept
|
36
|
+
headers["authorization"].should =~ /^(?i:bearer)\s+xyz$/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "adds an object" do
|
40
|
+
subject.set_request_handler do |url, method, body, headers|
|
41
|
+
url.should == "#{@target}/Users"
|
42
|
+
method.should == :post
|
43
|
+
check_headers(headers, :json, :json)
|
44
|
+
[200, '{"ID":"id12345"}', {"content-type" => "application/json"}]
|
45
|
+
end
|
46
|
+
result = subject.add(:user, hair: "brown", shoe_size: "large",
|
47
|
+
eye_color: ["blue", "green"], name: "fred")
|
48
|
+
result["id"].should == "id12345"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "replaces an object" do
|
52
|
+
obj = {hair: "black", shoe_size: "medium", eye_color: ["hazel", "brown"],
|
53
|
+
name: "fredrick", meta: {version: 'v567'}, id: "id12345"}
|
54
|
+
subject.set_request_handler do |url, method, body, headers|
|
55
|
+
url.should == "#{@target}/Users/id12345"
|
56
|
+
method.should == :put
|
57
|
+
check_headers(headers, :json, :json)
|
58
|
+
headers["if-match"].should == "v567"
|
59
|
+
[200, '{"ID":"id12345"}', {"content-type" => "application/json"}]
|
60
|
+
end
|
61
|
+
result = subject.put(:user, obj)
|
62
|
+
result["id"].should == "id12345"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "gets an object" do
|
66
|
+
subject.set_request_handler do |url, method, body, headers|
|
67
|
+
url.should == "#{@target}/Users/id12345"
|
68
|
+
method.should == :get
|
69
|
+
check_headers(headers, nil, :json)
|
70
|
+
[200, '{"id":"id12345"}', {"content-type" => "application/json"}]
|
71
|
+
end
|
72
|
+
result = subject.get(:user, "id12345")
|
73
|
+
result['id'].should == "id12345"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "pages through all objects" do
|
77
|
+
subject.set_request_handler do |url, method, body, headers|
|
78
|
+
url.should =~ %r{^#{@target}/Users\?attributes=id&startIndex=[12]$}
|
79
|
+
method.should == :get
|
80
|
+
check_headers(headers, nil, :json)
|
81
|
+
reply = url =~ /1$/ ?
|
82
|
+
'{"TotalResults":2,"ItemsPerPage":1,"StartIndex":1,"RESOURCES":[{"id":"id12345"}]}' :
|
83
|
+
'{"TotalResults":2,"ItemsPerPage":1,"StartIndex":2,"RESOURCES":[{"id":"id67890"}]}'
|
84
|
+
[200, reply, {"content-type" => "application/json"}]
|
85
|
+
end
|
86
|
+
result = subject.all_pages(:user, attributes: 'id')
|
87
|
+
result[0]['id'].should == "id12345"
|
88
|
+
result[1]['id'].should == "id67890"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "changes a user's password" do
|
92
|
+
subject.set_request_handler do |url, method, body, headers|
|
93
|
+
url.should == "#{@target}/Users/id12345/password"
|
94
|
+
method.should == :put
|
95
|
+
check_headers(headers, :json, :json)
|
96
|
+
body.should == '{"password":"newpwd","oldPassword":"oldpwd"}'
|
97
|
+
[200, '{"id":"id12345"}', {"content-type" => "application/json"}]
|
98
|
+
end
|
99
|
+
result = subject.change_password("id12345", "newpwd", "oldpwd")
|
100
|
+
result['id'].should == "id12345"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "changes a client's secret" do
|
104
|
+
subject.set_request_handler do |url, method, body, headers|
|
105
|
+
url.should == "#{@target}/oauth/clients/id12345/secret"
|
106
|
+
method.should == :put
|
107
|
+
check_headers(headers, :json, :json)
|
108
|
+
body.should == '{"secret":"newpwd","oldSecret":"oldpwd"}'
|
109
|
+
[200, '{"id":"id12345"}', {"content-type" => "application/json"}]
|
110
|
+
end
|
111
|
+
result = subject.change_secret("id12345", "newpwd", "oldpwd")
|
112
|
+
result['id'].should == "id12345"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
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
|
+
if ENV['COVERAGE']
|
15
|
+
require "simplecov"
|
16
|
+
if ENV['COVERAGE'] =~ /rcov/
|
17
|
+
require "simplecov-rcov"
|
18
|
+
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
|
19
|
+
end
|
20
|
+
SimpleCov.add_filter "^#{File.dirname(__FILE__)}" if ENV['COVERAGE'] =~ /exclude-spec/
|
21
|
+
SimpleCov.add_filter "^#{File.expand_path(File.join(File.dirname(__FILE__), "..", "vendor"))}" if ENV['COVERAGE'] =~ /exclude-vendor/
|
22
|
+
SimpleCov.start
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'rspec'
|
26
|
+
|
27
|
+
module SpecHelper
|
28
|
+
end
|
@@ -0,0 +1,128 @@
|
|
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 'spec_helper'
|
15
|
+
require 'uaa/token_coder'
|
16
|
+
|
17
|
+
module CF::UAA
|
18
|
+
|
19
|
+
describe TokenCoder do
|
20
|
+
|
21
|
+
subject { TokenCoder.new("test_resource", "test_secret", OpenSSL::PKey::RSA.generate(512) ) }
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
@tkn_body = {'foo' => "bar"}
|
25
|
+
@tkn_secret = "test_secret"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises a decode error if the given auth header is bad" do
|
29
|
+
expect { subject.decode(nil) }.to raise_exception(DecodeError)
|
30
|
+
expect { subject.decode("one two three") }.to raise_exception(DecodeError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "encodes/decodes a token using a symmetrical key" do
|
34
|
+
tkn = subject.encode(@tkn_body, 'HS512')
|
35
|
+
result = subject.decode("bEaReR #{tkn}")
|
36
|
+
result.should_not be_nil
|
37
|
+
result["foo"].should == "bar"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "encodes/decodes a token using pub/priv key" do
|
41
|
+
tkn = subject.encode(@tkn_body, 'RS256')
|
42
|
+
result = subject.decode("bEaReR #{tkn}")
|
43
|
+
result.should_not be_nil
|
44
|
+
result["foo"].should == "bar"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "encodes/decodes a token using pub/priv key from PEM" do
|
48
|
+
pem = <<-DATA.gsub(/^ +/, '')
|
49
|
+
-----BEGIN RSA PRIVATE KEY-----
|
50
|
+
MIIBOwIBAAJBAN+5O6n85LSs/fj46Ht1jNbc5e+3QX+suxVPJqICvuV6sIukJXXE
|
51
|
+
zfblneN2GeEVqgeNvglAU9tnm3OIKzlwM5UCAwEAAQJAEhJ2fV7OYsHuqiQBM6fl
|
52
|
+
Pp4NfPXCtruPSUNhjYjHPuYpnqo6cpuUNAzRvqAdDkJJsPCPt1E5AWOYUYOmLE+d
|
53
|
+
AQIhAO/XxMb9GrTDyqJDvS8T1EcJpLCaUIReae0jSg1RnBrhAiEA7st6WLmOyTxX
|
54
|
+
JgLcO6LUfW6RsE3pgi9NGL25P3eOAzUCIQDUFKi1CJR36XWh/GIqYc9grX9KhnnS
|
55
|
+
QqZKAd12X4a5IQIhAMTOJKaNP/Xwai7kupfX6mL6Rs5UWDg4PcU/UDbTlNJlAiBv
|
56
|
+
2yrlT5h164jGCxqe7++1kIl4ollFCgz6QJ8lcmb/2Q==
|
57
|
+
-----END RSA PRIVATE KEY-----
|
58
|
+
DATA
|
59
|
+
coder = TokenCoder.new("test_resource", nil, pem)
|
60
|
+
tkn = coder.encode(@tkn_body, 'RS256')
|
61
|
+
result = coder.decode("bEaReR #{tkn}")
|
62
|
+
result.should_not be_nil
|
63
|
+
result["foo"].should == "bar"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "encodes/decodes with 'none' signature" do
|
67
|
+
tkn = subject.encode(@tkn_body, 'none')
|
68
|
+
result = subject.decode("bEaReR #{tkn}")
|
69
|
+
result.should_not be_nil
|
70
|
+
result["foo"].should == "bar"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "raises an error if the signing algorithm is not supported" do
|
74
|
+
expect { subject.encode(@tkn_body, 'baz') }.to raise_exception(ArgumentError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "raises an auth error if the token is for another resource server" do
|
78
|
+
tkn = subject.encode({'aud' => ["other_resource"], 'foo' => "bar"})
|
79
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(AuthError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "raises a decode error if the token is signed by an unknown signing key" do
|
83
|
+
other = TokenCoder.new("test_resource", "other_secret", nil)
|
84
|
+
tkn = other.encode(@tkn_body)
|
85
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(DecodeError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raises a decode error if the token is an unknown signing algorithm" do
|
89
|
+
segments = [Util.json_encode64(typ: "JWT", alg:"BADALGO")]
|
90
|
+
segments << Util.json_encode64(@tkn_body)
|
91
|
+
segments << Util.encode64("BADSIG")
|
92
|
+
tkn = segments.join('.')
|
93
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(DecodeError)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "raises a decode error if the token is malformed" do
|
97
|
+
tkn = "one.two.three.four"
|
98
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(DecodeError)
|
99
|
+
tkn = "onlyone"
|
100
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(DecodeError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "raises a decode error if a token segment is malformed" do
|
104
|
+
segments = [Util.encode64("this is not json")]
|
105
|
+
segments << Util.encode64("n/a")
|
106
|
+
segments << Util.encode64("n/a")
|
107
|
+
tkn = segments.join('.')
|
108
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(DecodeError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "raises an auth error if the token has expired" do
|
112
|
+
tkn = subject.encode({'foo' => "bar", 'exp' => Time.now.to_i - 60 })
|
113
|
+
expect { subject.decode("bEaReR #{tkn}") }.to raise_exception(AuthError)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "decodes a token without validation" do
|
117
|
+
token = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImY1MTgwMjExLWVkYjItNGQ4OS1hNmQwLThmNGVjMTE0NTE4YSIsInJlc291cmNlX2lkcyI6WyJjbG91ZF9jb250cm9sbGVyIiwicGFzc3dvcmQiXSwiZXhwaXJlc19hdCI6MTMzNjU1MTc2Niwic2NvcGUiOlsicmVhZCJdLCJlbWFpbCI6Im9sZHNAdm13YXJlLmNvbSIsImNsaWVudF9hdXRob3JpdGllcyI6WyJST0xFX1VOVFJVU1RFRCJdLCJleHBpcmVzX2luIjo0MzIwMCwidXNlcl9hdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwidXNlcl9pZCI6Im9sZHNAdm13YXJlLmNvbSIsImNsaWVudF9pZCI6InZtYyIsInRva2VuX2lkIjoiZWRlYmYzMTctNWU2Yi00YmYwLWFmM2ItMTA0OWRjNmFlYjc1In0.XoirrePfEujnZ9Vm7SRRnj3vZEfRp2tkjkS_OCVz5Bs"
|
118
|
+
info = TokenCoder.decode(token, nil, nil, false)
|
119
|
+
info["id"].should_not be_nil
|
120
|
+
info["email"].should == "olds@vmware.com"
|
121
|
+
#puts Time.at(info[:exp].to_i)
|
122
|
+
#BaseCli.pp info
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,190 @@
|
|
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 'set'
|
15
|
+
require 'spec_helper'
|
16
|
+
require 'uaa/token_issuer'
|
17
|
+
|
18
|
+
module CF::UAA
|
19
|
+
|
20
|
+
describe TokenIssuer do
|
21
|
+
|
22
|
+
include SpecHelper
|
23
|
+
|
24
|
+
before :all do
|
25
|
+
#Util.default_logger(:trace)
|
26
|
+
@issuer = TokenIssuer.new("http://test.uaa.target", "test_client", "test_secret")
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { @issuer }
|
30
|
+
|
31
|
+
context "with client credentials grant" do
|
32
|
+
|
33
|
+
it "gets a token with client credentials" do
|
34
|
+
subject.set_request_handler do |url, method, body, headers|
|
35
|
+
headers["content-type"].should =~ /application\/x-www-form-urlencoded/
|
36
|
+
headers["accept"].should =~ /application\/json/
|
37
|
+
# TODO check basic auth header
|
38
|
+
url.should == "http://test.uaa.target/oauth/token"
|
39
|
+
method.should == :post
|
40
|
+
reply = {access_token: "test_access_token", token_type: "BEARER", scope: "logs.read", expires_in: 98765}
|
41
|
+
[200, Util.json(reply), {"content-type" => "application/json"}]
|
42
|
+
end
|
43
|
+
token = subject.client_credentials_grant("logs.read")
|
44
|
+
token.should be_an_instance_of Token
|
45
|
+
token.info["access_token"].should == "test_access_token"
|
46
|
+
token.info["token_type"].should =~ /^bearer$/i
|
47
|
+
token.info["scope"].should == "logs.read"
|
48
|
+
token.info["expires_in"].should == 98765
|
49
|
+
end
|
50
|
+
|
51
|
+
it "gets all granted scopes if none specified" do
|
52
|
+
subject.set_request_handler do |url, method, body, headers|
|
53
|
+
reply = {access_token: "test_access_token", token_type: "BEARER", scope: "openid logs.read", expires_in: 98765}
|
54
|
+
[200, Util.json(reply), {"content-type" => "application/json"}]
|
55
|
+
end
|
56
|
+
token = subject.client_credentials_grant
|
57
|
+
Util.arglist(token.info["scope"]).to_set.should == Util.arglist("openid logs.read").to_set
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises a bad response error if response content type is not json" do
|
61
|
+
subject.set_request_handler { [200, "not json", {"content-type" => "text/html"}] }
|
62
|
+
expect {subject.client_credentials_grant}.to raise_exception BadResponse
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises a bad response error if the response is not proper json" do
|
66
|
+
subject.set_request_handler { [200, "bad json", {"content-type" => "application/json"}] }
|
67
|
+
expect {subject.client_credentials_grant}.to raise_exception BadResponse
|
68
|
+
end
|
69
|
+
|
70
|
+
it "raises a target error if the response is 400 with valid oauth json error" do
|
71
|
+
subject.set_request_handler { [400, '{"error":"invalid scope"}', {"content-type" => "application/json"}] }
|
72
|
+
expect {subject.client_credentials_grant("bad.scope")}.to raise_exception TargetError
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with owner password grant" do
|
78
|
+
|
79
|
+
it "gets a token with owner password" do
|
80
|
+
subject.set_request_handler do |url, method, body, headers|
|
81
|
+
headers["content-type"].should =~ /application\/x-www-form-urlencoded/
|
82
|
+
headers["accept"].should =~ /application\/json/
|
83
|
+
# TODO check basic auth header
|
84
|
+
url.should == "http://test.uaa.target/oauth/token"
|
85
|
+
method.should == :post
|
86
|
+
reply = {access_token: "test_access_token", token_type: "BEARER", scope: "openid", expires_in: 98765}
|
87
|
+
[200, Util.json(reply), {"content-type" => "application/json"}]
|
88
|
+
end
|
89
|
+
token = subject.owner_password_grant("joe+admin", "?joe's%password$@ ", "openid")
|
90
|
+
token.should be_an_instance_of Token
|
91
|
+
token.info["access_token"].should == "test_access_token"
|
92
|
+
token.info["token_type"].should =~ /^bearer$/i
|
93
|
+
token.info["scope"].should == "openid"
|
94
|
+
token.info["expires_in"].should == 98765
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with implicit grant" do
|
100
|
+
|
101
|
+
it "gets the prompts for credentials used to authenticate implicit grant" do
|
102
|
+
subject.set_request_handler do |url, method, body, headers|
|
103
|
+
info = { prompts: {username: ["text", "Username"], password: ["password","Password"]} }
|
104
|
+
[200, Util.json(info), {"content-type" => "application/json"}]
|
105
|
+
end
|
106
|
+
result = subject.prompts
|
107
|
+
result.should_not be_empty
|
108
|
+
end
|
109
|
+
|
110
|
+
it "raises a bad target error if no prompts are received" do
|
111
|
+
subject.set_request_handler do |url, method, body, headers|
|
112
|
+
[200, Util.json({}), {"content-type" => "application/json"}]
|
113
|
+
end
|
114
|
+
expect { subject.prompts }.to raise_exception BadResponse
|
115
|
+
end
|
116
|
+
|
117
|
+
it "gets an access token" do
|
118
|
+
subject.set_request_handler do |url, method, body, headers|
|
119
|
+
headers["content-type"].should =~ /application\/x-www-form-urlencoded/
|
120
|
+
headers["accept"].should =~ /application\/json/
|
121
|
+
url.should match "http://test.uaa.target/oauth/authorize"
|
122
|
+
(state = /state=([^&]+)/.match(url)[1]).should_not be_nil
|
123
|
+
method.should == :post
|
124
|
+
location = "https://uaa.cloudfoundry.com/redirect/test_client#" +
|
125
|
+
"access_token=test_access_token&token_type=bearer&" +
|
126
|
+
"expires_in=98765&scope=openid+logs.read&state=#{state}"
|
127
|
+
[302, nil, {"content-type" => "application/json", "location" => location}]
|
128
|
+
end
|
129
|
+
token = subject.implicit_grant_with_creds(username: "joe+admin", password: "?joe's%password$@ ")
|
130
|
+
token.should be_an_instance_of Token
|
131
|
+
token.info["access_token"].should == "test_access_token"
|
132
|
+
token.info["token_type"].should =~ /^bearer$/i
|
133
|
+
Util.arglist(token.info["scope"]).to_set.should == Util.arglist("openid logs.read").to_set
|
134
|
+
token.info["expires_in"].should == 98765
|
135
|
+
end
|
136
|
+
|
137
|
+
it "rejects an access token with wrong state" do
|
138
|
+
subject.set_request_handler do |url, method, body, headers|
|
139
|
+
location = "https://uaa.cloudfoundry.com/redirect/test_client#" +
|
140
|
+
"access_token=test_access_token&token_type=bearer&" +
|
141
|
+
"expires_in=98765&scope=openid+logs.read&state=bad_state"
|
142
|
+
[302, nil, {"content-type" => "application/json", "location" => location}]
|
143
|
+
end
|
144
|
+
expect {token = subject.implicit_grant_with_creds(username: "joe+admin",
|
145
|
+
password: "?joe's%password$@ ")}.to raise_exception BadResponse
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with auth code grant" do
|
151
|
+
|
152
|
+
it "gets the authcode uri to be sent to the user agent for an authcode" do
|
153
|
+
redir_uri = "http://call.back/uri_path"
|
154
|
+
uri_parts = subject.authcode_uri(redir_uri).split('?')
|
155
|
+
uri_parts[0].should == "http://test.uaa.target/oauth/authorize"
|
156
|
+
params = Util.decode_form_to_hash(uri_parts[1])
|
157
|
+
params["response_type"].should == "code"
|
158
|
+
params["client_id"].should == "test_client"
|
159
|
+
params["scope"].should be_nil
|
160
|
+
params["redirect_uri"].should == redir_uri
|
161
|
+
params["state"].should_not be_nil
|
162
|
+
end
|
163
|
+
|
164
|
+
it "gets an access token with an authorization code" do
|
165
|
+
subject.set_request_handler do |url, method, body, headers|
|
166
|
+
headers["content-type"].should =~ /application\/x-www-form-urlencoded/
|
167
|
+
headers["accept"].should =~ /application\/json/
|
168
|
+
# TODO check basic auth header
|
169
|
+
url.should match "http://test.uaa.target/oauth/token"
|
170
|
+
method.should == :post
|
171
|
+
reply = {access_token: "test_access_token", token_type: "BEARER", scope: "openid", expires_in: 98765}
|
172
|
+
[200, Util.json(reply), {"content-type" => "application/json"}]
|
173
|
+
end
|
174
|
+
cburi = "http://call.back/uri_path"
|
175
|
+
redir_uri = subject.authcode_uri(cburi)
|
176
|
+
state = /state=([^&]+)/.match(redir_uri)[1]
|
177
|
+
reply_query = "state=#{state}&code=kz8%2F5gQZ2pc%3D"
|
178
|
+
token = subject.authcode_grant(redir_uri, reply_query)
|
179
|
+
token.should be_an_instance_of Token
|
180
|
+
token.info["access_token"].should == "test_access_token"
|
181
|
+
token.info["token_type"].should =~ /^bearer$/i
|
182
|
+
token.info["scope"].should == "openid"
|
183
|
+
token.info["expires_in"].should == 98765
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|