oauth2 1.3.1 → 1.4.7
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +170 -0
- data/CODE_OF_CONDUCT.md +133 -0
- data/LICENSE +22 -0
- data/README.md +137 -23
- data/lib/oauth2/access_token.rb +12 -4
- data/lib/oauth2/authenticator.rb +10 -0
- data/lib/oauth2/client.rb +63 -16
- data/lib/oauth2/mac_token.rb +11 -3
- data/lib/oauth2/response.rb +5 -3
- data/lib/oauth2/strategy/assertion.rb +3 -3
- data/lib/oauth2/strategy/password.rb +2 -2
- data/lib/oauth2/version.rb +10 -4
- data/spec/helper.rb +37 -0
- data/spec/oauth2/access_token_spec.rb +216 -0
- data/spec/oauth2/authenticator_spec.rb +84 -0
- data/spec/oauth2/client_spec.rb +506 -0
- data/spec/oauth2/mac_token_spec.rb +117 -0
- data/spec/oauth2/response_spec.rb +90 -0
- data/spec/oauth2/strategy/assertion_spec.rb +58 -0
- data/spec/oauth2/strategy/auth_code_spec.rb +107 -0
- data/spec/oauth2/strategy/base_spec.rb +5 -0
- data/spec/oauth2/strategy/client_credentials_spec.rb +69 -0
- data/spec/oauth2/strategy/implicit_spec.rb +26 -0
- data/spec/oauth2/strategy/password_spec.rb +55 -0
- data/spec/oauth2/version_spec.rb +23 -0
- metadata +219 -24
- data/.document +0 -5
- data/CONTRIBUTING.md +0 -18
- data/LICENSE.md +0 -20
- data/oauth2.gemspec +0 -24
@@ -0,0 +1,90 @@
|
|
1
|
+
describe OAuth2::Response do
|
2
|
+
describe '#initialize' do
|
3
|
+
let(:status) { 200 }
|
4
|
+
let(:headers) { {'foo' => 'bar'} }
|
5
|
+
let(:body) { 'foo' }
|
6
|
+
|
7
|
+
it 'returns the status, headers and body' do
|
8
|
+
response = double('response', :headers => headers,
|
9
|
+
:status => status,
|
10
|
+
:body => body)
|
11
|
+
subject = described_class.new(response)
|
12
|
+
expect(subject.headers).to eq(headers)
|
13
|
+
expect(subject.status).to eq(status)
|
14
|
+
expect(subject.body).to eq(body)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.register_parser' do
|
19
|
+
let(:response) do
|
20
|
+
double('response', :headers => {'Content-Type' => 'application/foo-bar'},
|
21
|
+
:status => 200,
|
22
|
+
:body => 'baz')
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
described_class.register_parser(:foobar, 'application/foo-bar') do |body|
|
27
|
+
"foobar #{body}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'adds to the content types and parsers' do
|
32
|
+
expect(described_class.send(:class_variable_get, :@@parsers).keys).to include(:foobar)
|
33
|
+
expect(described_class.send(:class_variable_get, :@@content_types).keys).to include('application/foo-bar')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'is able to parse that content type automatically' do
|
37
|
+
expect(described_class.new(response).parsed).to eq('foobar baz')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#parsed' do
|
42
|
+
it 'parses application/x-www-form-urlencoded body' do
|
43
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
44
|
+
body = 'foo=bar&answer=42'
|
45
|
+
response = double('response', :headers => headers, :body => body)
|
46
|
+
subject = described_class.new(response)
|
47
|
+
expect(subject.parsed.keys.size).to eq(2)
|
48
|
+
expect(subject.parsed['foo']).to eq('bar')
|
49
|
+
expect(subject.parsed['answer']).to eq('42')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'parses application/json body' do
|
53
|
+
headers = {'Content-Type' => 'application/json'}
|
54
|
+
body = MultiJson.encode(:foo => 'bar', :answer => 42)
|
55
|
+
response = double('response', :headers => headers, :body => body)
|
56
|
+
subject = described_class.new(response)
|
57
|
+
expect(subject.parsed.keys.size).to eq(2)
|
58
|
+
expect(subject.parsed['foo']).to eq('bar')
|
59
|
+
expect(subject.parsed['answer']).to eq(42)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "doesn't try to parse other content-types" do
|
63
|
+
headers = {'Content-Type' => 'text/html'}
|
64
|
+
body = '<!DOCTYPE html><html><head></head><body></body></html>'
|
65
|
+
|
66
|
+
response = double('response', :headers => headers, :body => body)
|
67
|
+
|
68
|
+
expect(MultiJson).not_to receive(:decode)
|
69
|
+
expect(MultiJson).not_to receive(:load)
|
70
|
+
expect(Rack::Utils).not_to receive(:parse_query)
|
71
|
+
|
72
|
+
subject = described_class.new(response)
|
73
|
+
expect(subject.parsed).to be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with xml parser registration' do
|
78
|
+
it 'tries to load multi_xml and use it' do
|
79
|
+
expect(described_class.send(:class_variable_get, :@@parsers)[:xml]).not_to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'is 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
|
+
expect(described_class.new(response).parsed).to eq('foo' => {'bar' => 'baz'})
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
describe OAuth2::Strategy::Assertion do
|
2
|
+
subject { client.assertion }
|
3
|
+
|
4
|
+
let(:client) do
|
5
|
+
cli = OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com')
|
6
|
+
cli.connection.build do |b|
|
7
|
+
b.adapter :test do |stub|
|
8
|
+
stub.post('/oauth/token') do |env|
|
9
|
+
case @mode
|
10
|
+
when 'formencoded'
|
11
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout']
|
12
|
+
when 'json'
|
13
|
+
[200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
cli
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:params) do
|
22
|
+
{
|
23
|
+
:hmac_secret => 'foo',
|
24
|
+
:exp => Time.now.utc.to_i + 3600,
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#authorize_url' do
|
29
|
+
it 'raises NotImplementedError' do
|
30
|
+
expect { subject.authorize_url }.to raise_error(NotImplementedError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%w[json formencoded].each do |mode|
|
35
|
+
describe "#get_token (#{mode})" do
|
36
|
+
before do
|
37
|
+
@mode = mode
|
38
|
+
@access = subject.get_token(params)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns AccessToken with same Client' do
|
42
|
+
expect(@access.client).to eq(client)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns AccessToken with #token' do
|
46
|
+
expect(@access.token).to eq('salmon')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns AccessToken with #expires_in' do
|
50
|
+
expect(@access.expires_in).to eq(600)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns AccessToken with #expires_at' do
|
54
|
+
expect(@access.expires_at).not_to be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe OAuth2::Strategy::AuthCode do
|
4
|
+
subject { client.auth_code }
|
5
|
+
|
6
|
+
let(:code) { 'sushi' }
|
7
|
+
let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout&extra_param=steve' }
|
8
|
+
let(:facebook_token) { kvform_token.gsub('_in', '') }
|
9
|
+
let(:json_token) { MultiJson.encode(:expires_in => 600, :access_token => 'salmon', :refresh_token => 'trout', :extra_param => 'steve') }
|
10
|
+
|
11
|
+
let(:client) do
|
12
|
+
OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') do |builder|
|
13
|
+
builder.adapter :test do |stub|
|
14
|
+
stub.get("/oauth/token?client_id=abc&client_secret=def&code=#{code}&grant_type=authorization_code") do |env|
|
15
|
+
case @mode
|
16
|
+
when 'formencoded'
|
17
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
|
18
|
+
when 'json'
|
19
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
20
|
+
when 'from_facebook'
|
21
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'sushi', 'grant_type' => 'authorization_code') do |env|
|
25
|
+
case @mode
|
26
|
+
when 'formencoded'
|
27
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
|
28
|
+
when 'json'
|
29
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
30
|
+
when 'from_facebook'
|
31
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#authorize_url' do
|
39
|
+
it 'includes the client_id' do
|
40
|
+
expect(subject.authorize_url).to include('client_id=abc')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'includes the type' do
|
44
|
+
expect(subject.authorize_url).to include('response_type=code')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'includes passed in options' do
|
48
|
+
cb = 'http://myserver.local/oauth/callback'
|
49
|
+
expect(subject.authorize_url(:redirect_uri => cb)).to include("redirect_uri=#{Rack::Utils.escape(cb)}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#get_token (handling utf-8 data)' do
|
54
|
+
let(:json_token) { MultiJson.encode(:expires_in => 600, :access_token => 'salmon', :refresh_token => 'trout', :extra_param => 'André') }
|
55
|
+
|
56
|
+
before do
|
57
|
+
@mode = 'json'
|
58
|
+
client.options[:token_method] = :post
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'does not raise an error' do
|
62
|
+
expect { subject.get_token(code) }.not_to raise_error
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'does not create an error instance' do
|
66
|
+
expect(OAuth2::Error).not_to receive(:new)
|
67
|
+
|
68
|
+
subject.get_token(code)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
%w[json formencoded from_facebook].each do |mode|
|
73
|
+
[:get, :post].each do |verb|
|
74
|
+
describe "#get_token (#{mode}, access_token_method=#{verb}" do
|
75
|
+
before do
|
76
|
+
@mode = mode
|
77
|
+
client.options[:token_method] = verb
|
78
|
+
@access = subject.get_token(code)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns AccessToken with same Client' do
|
82
|
+
expect(@access.client).to eq(client)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'returns AccessToken with #token' do
|
86
|
+
expect(@access.token).to eq('salmon')
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns AccessToken with #refresh_token' do
|
90
|
+
expect(@access.refresh_token).to eq('trout')
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns AccessToken with #expires_in' do
|
94
|
+
expect(@access.expires_in).to eq(600)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns AccessToken with #expires_at' do
|
98
|
+
expect(@access.expires_at).to be_kind_of(Integer)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns AccessToken with params accessible via []' do
|
102
|
+
expect(@access['extra_param']).to eq('steve')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
describe OAuth2::Strategy::ClientCredentials do
|
2
|
+
subject { client.client_credentials }
|
3
|
+
|
4
|
+
let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout' }
|
5
|
+
let(:json_token) { '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}' }
|
6
|
+
|
7
|
+
let(:client) do
|
8
|
+
OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') do |builder|
|
9
|
+
builder.adapter :test do |stub|
|
10
|
+
stub.post('/oauth/token', 'grant_type' => 'client_credentials') do |env|
|
11
|
+
client_id, client_secret = Base64.decode64(env[:request_headers]['Authorization'].split(' ', 2)[1]).split(':', 2)
|
12
|
+
client_id == 'abc' && client_secret == 'def' || raise(Faraday::Adapter::Test::Stubs::NotFound)
|
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
|
+
end
|
19
|
+
end
|
20
|
+
stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'grant_type' => 'client_credentials') do |env|
|
21
|
+
case @mode
|
22
|
+
when 'formencoded'
|
23
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
|
24
|
+
when 'json'
|
25
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#authorize_url' do
|
33
|
+
it 'raises NotImplementedError' do
|
34
|
+
expect { subject.authorize_url }.to raise_error(NotImplementedError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
%w[json formencoded].each do |mode|
|
39
|
+
[:basic_auth, :request_body].each do |auth_scheme|
|
40
|
+
describe "#get_token (#{mode}) (#{auth_scheme})" do
|
41
|
+
before do
|
42
|
+
@mode = mode
|
43
|
+
client.options[:auth_scheme] = auth_scheme
|
44
|
+
@access = subject.get_token
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns AccessToken with same Client' do
|
48
|
+
expect(@access.client).to eq(client)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns AccessToken with #token' do
|
52
|
+
expect(@access.token).to eq('salmon')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns AccessToken without #refresh_token' do
|
56
|
+
expect(@access.refresh_token).to be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns AccessToken with #expires_in' do
|
60
|
+
expect(@access.expires_in).to eq(600)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns AccessToken with #expires_at' do
|
64
|
+
expect(@access.expires_at).not_to be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
describe OAuth2::Strategy::Implicit do
|
2
|
+
subject { client.implicit }
|
3
|
+
|
4
|
+
let(:client) { OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') }
|
5
|
+
|
6
|
+
describe '#authorize_url' do
|
7
|
+
it 'includes the client_id' do
|
8
|
+
expect(subject.authorize_url).to include('client_id=abc')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'includes the type' do
|
12
|
+
expect(subject.authorize_url).to include('response_type=token')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'includes passed in options' do
|
16
|
+
cb = 'http://myserver.local/oauth/callback'
|
17
|
+
expect(subject.authorize_url(:redirect_uri => cb)).to include("redirect_uri=#{Rack::Utils.escape(cb)}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#get_token' do
|
22
|
+
it 'raises NotImplementedError' do
|
23
|
+
expect { subject.get_token }.to raise_error(NotImplementedError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
describe OAuth2::Strategy::Password do
|
2
|
+
subject { client.password }
|
3
|
+
|
4
|
+
let(:client) do
|
5
|
+
cli = OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com')
|
6
|
+
cli.connection.build do |b|
|
7
|
+
b.adapter :test do |stub|
|
8
|
+
stub.post('/oauth/token') do |env|
|
9
|
+
case @mode
|
10
|
+
when 'formencoded'
|
11
|
+
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout']
|
12
|
+
when 'json'
|
13
|
+
[200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
cli
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#authorize_url' do
|
22
|
+
it 'raises NotImplementedError' do
|
23
|
+
expect { subject.authorize_url }.to raise_error(NotImplementedError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
%w[json formencoded].each do |mode|
|
28
|
+
describe "#get_token (#{mode})" do
|
29
|
+
before do
|
30
|
+
@mode = mode
|
31
|
+
@access = subject.get_token('username', 'password')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns AccessToken with same Client' do
|
35
|
+
expect(@access.client).to eq(client)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns AccessToken with #token' do
|
39
|
+
expect(@access.token).to eq('salmon')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns AccessToken with #refresh_token' do
|
43
|
+
expect(@access.refresh_token).to eq('trout')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns AccessToken with #expires_in' do
|
47
|
+
expect(@access.expires_in).to eq(600)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns AccessToken with #expires_at' do
|
51
|
+
expect(@access.expires_at).not_to be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe OAuth2::Version do
|
4
|
+
it 'has a version number' do
|
5
|
+
expect(described_class).not_to be nil
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can be a string' do
|
9
|
+
expect(described_class.to_s).to be_a(String)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'allows Constant access' do
|
13
|
+
expect(described_class::VERSION).to be_a(String)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'is greater than 0.1.0' do
|
17
|
+
expect(Gem::Version.new(described_class) > Gem::Version.new('0.1.0')).to be(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is not a pre-release' do
|
21
|
+
expect(Gem::Version.new(described_class).prerelease?).to be(false)
|
22
|
+
end
|
23
|
+
end
|