cf-uaa-lib 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|