oauth2 0.4.1 → 0.5.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/.autotest +1 -0
- data/.gitignore +4 -1
- data/.travis.yml +1 -3
- data/Gemfile +1 -1
- data/README.md +62 -57
- data/Rakefile +3 -1
- data/lib/oauth2.rb +3 -9
- data/lib/oauth2/access_token.rb +122 -24
- data/lib/oauth2/client.rb +115 -74
- data/lib/oauth2/error.rb +17 -0
- data/lib/oauth2/response.rb +90 -0
- data/lib/oauth2/strategy/auth_code.rb +32 -0
- data/lib/oauth2/strategy/base.rb +7 -29
- data/lib/oauth2/strategy/password.rb +16 -27
- data/lib/oauth2/version.rb +1 -1
- data/oauth2.gemspec +23 -21
- data/spec/helper.rb +13 -0
- data/spec/oauth2/access_token_spec.rb +99 -45
- data/spec/oauth2/client_spec.rb +81 -69
- data/spec/oauth2/response_spec.rb +90 -0
- data/spec/oauth2/strategy/auth_code_spec.rb +88 -0
- data/spec/oauth2/strategy/base_spec.rb +1 -1
- data/spec/oauth2/strategy/password_spec.rb +7 -7
- metadata +114 -90
- data/CHANGELOG.md +0 -21
- data/lib/oauth2/response_object.rb +0 -58
- data/lib/oauth2/strategy/web_server.rb +0 -58
- data/spec/oauth2/strategy/web_server_spec.rb +0 -138
- data/spec/spec_helper.rb +0 -11
data/spec/oauth2/client_spec.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
-
require '
|
1
|
+
require 'helper'
|
2
2
|
|
3
3
|
describe OAuth2::Client do
|
4
|
+
let!(:error_value) {'invalid_token'}
|
5
|
+
let!(:error_description_value) {'bad bad token'}
|
6
|
+
|
4
7
|
subject do
|
5
|
-
|
6
|
-
|
7
|
-
b.adapter :test do |stub|
|
8
|
+
OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
|
9
|
+
builder.adapter :test do |stub|
|
8
10
|
stub.get('/success') {|env| [200, {'Content-Type' => 'text/awesome'}, 'yay']}
|
9
|
-
stub.get('/
|
10
|
-
stub.
|
11
|
+
stub.get('/reflect') {|env| [200, {}, env[:body]]}
|
12
|
+
stub.post('/reflect') {|env| [200, {}, env[:body]]}
|
13
|
+
stub.get('/unauthorized') {|env| [401, {'Content-Type' => 'application/json'}, MultiJson.encode(:error => error_value, :error_description => error_description_value)]}
|
14
|
+
stub.get('/conflict') {|env| [409, {'Content-Type' => 'text/plain'}, 'not authorized']}
|
11
15
|
stub.get('/redirect') {|env| [302, {'Content-Type' => 'text/plain', 'location' => '/success' }, '']}
|
16
|
+
stub.post('/redirect') {|env| [303, {'Content-Type' => 'text/plain', 'location' => '/reflect' }, '']}
|
12
17
|
stub.get('/error') {|env| [500, {}, '']}
|
13
|
-
stub.get('/
|
18
|
+
stub.get('/empty_get') {|env| [204, {}, nil]}
|
14
19
|
end
|
15
20
|
end
|
16
|
-
cli
|
17
21
|
end
|
18
22
|
|
19
23
|
describe '#initialize' do
|
@@ -34,122 +38,130 @@ describe OAuth2::Client do
|
|
34
38
|
subject.connection.ssl.should == {}
|
35
39
|
end
|
36
40
|
|
37
|
-
it "should be able to pass
|
41
|
+
it "should be able to pass a block to configure the connection" do
|
38
42
|
connection = stub('connection')
|
39
|
-
Faraday::Connection.stub(:new => connection)
|
40
43
|
session = stub('session', :to_ary => nil)
|
41
44
|
builder = stub('builder')
|
42
45
|
connection.stub(:build).and_yield(builder)
|
46
|
+
Faraday::Connection.stub(:new => connection)
|
43
47
|
|
44
|
-
builder.should_receive(:adapter).with(:
|
48
|
+
builder.should_receive(:adapter).with(:test)
|
45
49
|
|
46
|
-
OAuth2::Client.new('abc', 'def'
|
50
|
+
OAuth2::Client.new('abc', 'def') do |builder|
|
51
|
+
builder.adapter :test
|
52
|
+
end.connection
|
47
53
|
end
|
48
54
|
|
49
55
|
it "defaults raise_errors to true" do
|
50
|
-
subject.raise_errors.should be_true
|
56
|
+
subject.options[:raise_errors].should be_true
|
51
57
|
end
|
52
58
|
|
53
59
|
it "allows true/false for raise_errors option" do
|
54
60
|
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => false)
|
55
|
-
client.raise_errors.should be_false
|
61
|
+
client.options[:raise_errors].should be_false
|
56
62
|
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => true)
|
57
|
-
client.raise_errors.should be_true
|
63
|
+
client.options[:raise_errors].should be_true
|
58
64
|
end
|
59
65
|
|
60
66
|
it "allows get/post for access_token_method option" do
|
61
67
|
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :get)
|
62
|
-
client.
|
68
|
+
client.options[:access_token_method].should == :get
|
63
69
|
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :post)
|
64
|
-
client.
|
70
|
+
client.options[:access_token_method].should == :post
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
68
|
-
%w(authorize
|
69
|
-
describe "
|
70
|
-
it "should default to a path of /oauth/#{
|
71
|
-
subject.send("#{
|
74
|
+
%w(authorize token).each do |url_type|
|
75
|
+
describe ":#{url_type}_url option" do
|
76
|
+
it "should default to a path of /oauth/#{url_type}" do
|
77
|
+
subject.send("#{url_type}_url").should == "https://api.example.com/oauth/#{url_type}"
|
72
78
|
end
|
73
79
|
|
74
|
-
it "should be settable via the :#{
|
75
|
-
subject.options[:"#{
|
76
|
-
subject.send("#{
|
80
|
+
it "should be settable via the :#{url_type}_url option" do
|
81
|
+
subject.options[:"#{url_type}_url"] = '/oauth/custom'
|
82
|
+
subject.send("#{url_type}_url").should == 'https://api.example.com/oauth/custom'
|
77
83
|
end
|
78
84
|
|
79
|
-
it "
|
80
|
-
subject.options[:"#{
|
81
|
-
subject.send("#{
|
85
|
+
it "allows a different host than the site" do
|
86
|
+
subject.options[:"#{url_type}_url"] = 'https://api.foo.com/oauth/custom'
|
87
|
+
subject.send("#{url_type}_url").should == 'https://api.foo.com/oauth/custom'
|
82
88
|
end
|
83
89
|
end
|
84
90
|
end
|
85
91
|
|
86
92
|
describe "#request" do
|
87
|
-
it "
|
88
|
-
|
89
|
-
response.should == 'yay'
|
90
|
-
response.status.should == 200
|
91
|
-
response.headers.should == {'Content-Type' => 'text/awesome'}
|
93
|
+
it "works with a null response body" do
|
94
|
+
subject.request(:get, 'empty_get').body.should == ''
|
92
95
|
end
|
93
96
|
|
94
|
-
it "
|
95
|
-
response = subject.request(:get, '/
|
96
|
-
response.should == 'yay'
|
97
|
+
it "returns on a successful response" do
|
98
|
+
response = subject.request(:get, '/success')
|
99
|
+
response.body.should == 'yay'
|
97
100
|
response.status.should == 200
|
98
101
|
response.headers.should == {'Content-Type' => 'text/awesome'}
|
99
102
|
end
|
100
103
|
|
101
|
-
it "
|
102
|
-
subject.
|
103
|
-
response
|
104
|
-
|
105
|
-
response.should == 'not authorized'
|
106
|
-
response.status.should == 401
|
107
|
-
response.headers.should == {'Content-Type' => 'text/plain'}
|
104
|
+
it "posts a body" do
|
105
|
+
response = subject.request(:post, '/reflect', :body => 'foo=bar')
|
106
|
+
response.body.should == 'foo=bar'
|
108
107
|
end
|
109
108
|
|
110
|
-
it "
|
111
|
-
|
109
|
+
it "follows redirects properly" do
|
110
|
+
response = subject.request(:get, '/redirect')
|
111
|
+
response.body.should == 'yay'
|
112
|
+
response.status.should == 200
|
113
|
+
response.headers.should == {'Content-Type' => 'text/awesome'}
|
112
114
|
end
|
113
115
|
|
114
|
-
it "
|
115
|
-
|
116
|
+
it "redirects using GET on a 303" do
|
117
|
+
response = subject.request(:post, '/redirect', :body => 'foo=bar')
|
118
|
+
response.body.should be_empty
|
119
|
+
response.status.should == 200
|
116
120
|
end
|
117
121
|
|
118
|
-
it "
|
119
|
-
|
122
|
+
it "obeys the :max_redirects option" do
|
123
|
+
max_redirects = subject.options[:max_redirects]
|
124
|
+
subject.options[:max_redirects] = 0
|
125
|
+
response = subject.request(:get, '/redirect')
|
126
|
+
response.status.should == 302
|
127
|
+
subject.options[:max_redirects] = max_redirects
|
120
128
|
end
|
121
|
-
end
|
122
129
|
|
123
|
-
|
124
|
-
|
125
|
-
|
130
|
+
it "returns if raise_errors is false" do
|
131
|
+
subject.options[:raise_errors] = false
|
132
|
+
response = subject.request(:get, '/unauthorized')
|
126
133
|
|
127
|
-
|
128
|
-
|
129
|
-
|
134
|
+
response.status.should == 401
|
135
|
+
response.headers.should == {'Content-Type' => 'application/json'}
|
136
|
+
response.error.should_not be_nil
|
130
137
|
end
|
131
138
|
|
132
|
-
|
133
|
-
it
|
134
|
-
|
135
|
-
puts response.inspect
|
136
|
-
response.should be_kind_of(OAuth2::ResponseHash)
|
137
|
-
response['abc'].should == 'def'
|
139
|
+
%w(/unauthorized /conflict /error).each do |error_path|
|
140
|
+
it "raises OAuth2::Error on error response to path #{error_path}" do
|
141
|
+
lambda {subject.request(:get, error_path)}.should raise_error(OAuth2::Error)
|
138
142
|
end
|
143
|
+
end
|
139
144
|
|
140
|
-
|
141
|
-
|
145
|
+
it 'parses OAuth2 standard error response' do
|
146
|
+
begin
|
147
|
+
subject.request(:get, '/unauthorized')
|
148
|
+
rescue Exception => e
|
149
|
+
e.code.should == error_value
|
150
|
+
e.description.should == error_description_value
|
142
151
|
end
|
143
152
|
end
|
144
153
|
|
145
|
-
it
|
146
|
-
|
147
|
-
|
154
|
+
it "provides the response in the Exception" do
|
155
|
+
begin
|
156
|
+
subject.request(:get, '/error')
|
157
|
+
rescue Exception => e
|
158
|
+
e.response.should_not be_nil
|
159
|
+
end
|
148
160
|
end
|
161
|
+
end
|
149
162
|
|
150
|
-
|
151
|
-
|
152
|
-
end
|
163
|
+
it '#auth_code should instantiate a AuthCode strategy with this client' do
|
164
|
+
subject.auth_code.should be_kind_of(OAuth2::Strategy::AuthCode)
|
153
165
|
end
|
154
166
|
|
155
167
|
context 'with SSL options' do
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe OAuth2::Response do
|
4
|
+
describe '#initialize' do
|
5
|
+
let(:status) {200}
|
6
|
+
let(:headers) {{'foo' => 'bar'}}
|
7
|
+
let(:body) {'foo'}
|
8
|
+
|
9
|
+
it 'returns the status, headers and body' do
|
10
|
+
response = double('response', :headers => headers,
|
11
|
+
:status => status,
|
12
|
+
:body => body)
|
13
|
+
subject = Response.new(response)
|
14
|
+
subject.headers.should == headers
|
15
|
+
subject.status.should == status
|
16
|
+
subject.body.should == body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.register_parser' do
|
21
|
+
let(:response) {
|
22
|
+
double('response', :headers => {'Content-Type' => 'application/foo-bar'},
|
23
|
+
:status => 200,
|
24
|
+
:body => 'baz')
|
25
|
+
}
|
26
|
+
before do
|
27
|
+
OAuth2::Response.register_parser(:foobar, 'application/foo-bar') do |body|
|
28
|
+
"foobar #{body}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should add to the content types and parsers' do
|
33
|
+
OAuth2::Response::PARSERS.keys.should be_include(:foobar)
|
34
|
+
OAuth2::Response::CONTENT_TYPES.keys.should be_include('application/foo-bar')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should be able to parse that content type automatically' do
|
38
|
+
OAuth2::Response.new(response).parsed.should == 'foobar baz'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#parsed' do
|
43
|
+
it "parses application/x-www-form-urlencoded body" do
|
44
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
45
|
+
body = 'foo=bar&answer=42'
|
46
|
+
response = double('response', :headers => headers, :body => body)
|
47
|
+
subject = Response.new(response)
|
48
|
+
subject.parsed.keys.size.should == 2
|
49
|
+
subject.parsed['foo'].should == 'bar'
|
50
|
+
subject.parsed['answer'].should == '42'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "parses application/json body" do
|
54
|
+
headers = {'Content-Type' => 'application/json'}
|
55
|
+
body = MultiJson.encode(:foo => 'bar', :answer => 42)
|
56
|
+
response = double('response', :headers => headers, :body => body)
|
57
|
+
subject = Response.new(response)
|
58
|
+
subject.parsed.keys.size.should == 2
|
59
|
+
subject.parsed['foo'].should == 'bar'
|
60
|
+
subject.parsed['answer'].should == 42
|
61
|
+
end
|
62
|
+
|
63
|
+
it "doesn't try to parse other content-types" do
|
64
|
+
headers = {'Content-Type' => 'text/html'}
|
65
|
+
body = '<!DOCTYPE html><html><head></head><body></body></html>'
|
66
|
+
|
67
|
+
response = double('response', :headers => headers, :body => body)
|
68
|
+
|
69
|
+
MultiJson.should_not_receive(:decode)
|
70
|
+
Rack::Utils.should_not_receive(:parse_query)
|
71
|
+
|
72
|
+
subject = Response.new(response)
|
73
|
+
subject.parsed.should be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'xml parser registration' do
|
78
|
+
it 'should try to load multi_xml and use it' do
|
79
|
+
OAuth2::Response::PARSERS[:xml].should_not be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should be able to parse xml' do
|
83
|
+
headers = {'Content-Type' => 'text/xml'}
|
84
|
+
body = '<?xml version="1.0" standalone="yes" ?><foo><bar>baz</bar></foo>'
|
85
|
+
|
86
|
+
response = double('response', :headers => headers, :body => body)
|
87
|
+
OAuth2::Response.new(response).parsed.should == {"foo" => {"bar" => "baz"}}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe OAuth2::Strategy::AuthCode do
|
4
|
+
let(:code) {'sushi'}
|
5
|
+
let(:kvform_token) {'expires_in=600&access_token=salmon&refresh_token=trout&extra_param=steve'}
|
6
|
+
let(:facebook_token) {kvform_token.gsub('_in', '')}
|
7
|
+
let(:json_token) {MultiJson.encode(:expires_in => 600, :access_token => 'salmon', :refresh_token => 'trout', :extra_param => 'steve')}
|
8
|
+
|
9
|
+
let(:client) do
|
10
|
+
OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') do |builder|
|
11
|
+
builder.adapter :test do |stub|
|
12
|
+
stub.get("/oauth/token?client_id=abc&client_secret=def&code=#{code}&grant_type=authorization_code") do |env|
|
13
|
+
case @mode
|
14
|
+
when "formencoded"
|
15
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
|
16
|
+
when "json"
|
17
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
18
|
+
when "from_facebook"
|
19
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
stub.post('/oauth/token', {'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'sushi', 'grant_type' => 'authorization_code'}) do |env|
|
23
|
+
case @mode
|
24
|
+
when "formencoded"
|
25
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
|
26
|
+
when "json"
|
27
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
28
|
+
when "from_facebook"
|
29
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
subject {client.auth_code}
|
37
|
+
|
38
|
+
describe '#authorize_url' do
|
39
|
+
it 'should include the client_id' do
|
40
|
+
subject.authorize_url.should be_include('client_id=abc')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should include the type' do
|
44
|
+
subject.authorize_url.should be_include('response_type=code')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should include passed in options' do
|
48
|
+
cb = 'http://myserver.local/oauth/callback'
|
49
|
+
subject.authorize_url(:redirect_uri => cb).should be_include("redirect_uri=#{Rack::Utils.escape(cb)}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
%w(json formencoded from_facebook).each do |mode|
|
54
|
+
[:get, :post].each do |verb|
|
55
|
+
describe "#get_token (#{mode}, access_token_method=#{verb}" do
|
56
|
+
before :each do
|
57
|
+
@mode = mode
|
58
|
+
client.options[:token_method] = verb
|
59
|
+
@access = subject.get_token(code)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns AccessToken with same Client' do
|
63
|
+
@access.client.should == client
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns AccessToken with #token' do
|
67
|
+
@access.token.should == 'salmon'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'returns AccessToken with #refresh_token' do
|
71
|
+
@access.refresh_token.should == 'trout'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns AccessToken with #expires_in' do
|
75
|
+
@access.expires_in.should == 600
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns AccessToken with #expires_at' do
|
79
|
+
@access.expires_at.should be_kind_of(Integer)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns AccessToken with params accessible via []' do
|
83
|
+
@access['extra_param'].should == 'steve'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
|
-
require '
|
1
|
+
require 'helper'
|
2
2
|
|
3
3
|
describe OAuth2::Strategy::Password do
|
4
4
|
let(:client) do
|
5
5
|
cli = OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com')
|
6
6
|
cli.connection.build do |b|
|
7
7
|
b.adapter :test do |stub|
|
8
|
-
stub.post('/oauth/
|
8
|
+
stub.post('/oauth/token') do |env|
|
9
9
|
case @mode
|
10
10
|
when "formencoded"
|
11
|
-
[200, {}, 'expires_in=600&access_token=salmon&refresh_token=trout']
|
11
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout']
|
12
12
|
when "json"
|
13
|
-
[200, {}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
|
13
|
+
[200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -26,10 +26,10 @@ describe OAuth2::Strategy::Password do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
%w(json formencoded).each do |mode|
|
29
|
-
describe "#
|
29
|
+
describe "#get_token (#{mode})" do
|
30
30
|
before do
|
31
31
|
@mode = mode
|
32
|
-
@access = subject.
|
32
|
+
@access = subject.get_token('username', 'password')
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'returns AccessToken with same Client' do
|
@@ -49,7 +49,7 @@ describe OAuth2::Strategy::Password do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'returns AccessToken with #expires_at' do
|
52
|
-
@access.expires_at.
|
52
|
+
@access.expires_at.should_not be_nil
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|