cf-uaa-lib 1.3.0

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