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