oauth2-client 1.0.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/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +11 -0
- data/TODO +10 -0
- data/examples/google_client.rb +159 -0
- data/lib/oauth2.rb +7 -0
- data/lib/oauth2/client.rb +63 -0
- data/lib/oauth2/connection.rb +188 -0
- data/lib/oauth2/error.rb +3 -0
- data/lib/oauth2/grant.rb +7 -0
- data/lib/oauth2/grant/authorization_code.rb +71 -0
- data/lib/oauth2/grant/base.rb +41 -0
- data/lib/oauth2/grant/client_credentials.rb +27 -0
- data/lib/oauth2/grant/device.rb +37 -0
- data/lib/oauth2/grant/implicit.rb +46 -0
- data/lib/oauth2/grant/password.rb +28 -0
- data/lib/oauth2/grant/refresh_token.rb +26 -0
- data/lib/oauth2/helper.rb +46 -0
- data/lib/oauth2/version.rb +11 -0
- data/oauth2-client.gemspec +13 -0
- data/spec/.DS_Store +0 -0
- data/spec/examples/google_client_spec.rb +223 -0
- data/spec/mocks/oauth_client.yml +60 -0
- data/spec/oauth2/client_spec.rb +157 -0
- data/spec/oauth2/connection_spec.rb +273 -0
- data/spec/oauth2/grant/authorization_code_spec.rb +89 -0
- data/spec/oauth2/grant/base_spec.rb +57 -0
- data/spec/oauth2/grant/client_credentials_spec.rb +28 -0
- data/spec/oauth2/grant/device_spec.rb +35 -0
- data/spec/oauth2/grant/implicit_spec.rb +36 -0
- data/spec/oauth2/grant/password_spec.rb +28 -0
- data/spec/oauth2/grant/refresh_token_spec.rb +27 -0
- data/spec/spec_helper.rb +15 -0
- metadata +83 -0
@@ -0,0 +1,273 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe OAuth2::HttpConnection do
|
5
|
+
|
6
|
+
subject do
|
7
|
+
@conn = OAuth2::HttpConnection.new('https://yammer.com')
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with user options" do
|
11
|
+
before do
|
12
|
+
@conn = OAuth2::HttpConnection.new('https://microsoft.com', {
|
13
|
+
:accept => 'application/xml',
|
14
|
+
:user_agent => "OAuth2 Test Client",
|
15
|
+
:ssl => {:verify => false},
|
16
|
+
:max_redirects => 2
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "connection options" do
|
21
|
+
# it " " do
|
22
|
+
# options = OAuth2::HttpConnection.default_options
|
23
|
+
# options.keys.each do |key|
|
24
|
+
# expect(@conn.instance_variable_get(:"@#{key}")).to eq options[key]
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#default_headers" do
|
31
|
+
it "returns user_agent and response format" do
|
32
|
+
expect(subject.default_headers).to eq ({
|
33
|
+
"Accept" => "application/json",
|
34
|
+
"User-Agent" => "OAuth2 Ruby Gem #{OAuth2::Version}"
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#scheme" do
|
40
|
+
it "returns the http scheme" do
|
41
|
+
expect(subject.scheme).to eq 'https'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#scheme" do
|
46
|
+
context "scheme is unsupported" do
|
47
|
+
it "raises an error" do
|
48
|
+
expect { subject.scheme = 'ftp'}.to raise_error(OAuth2::HttpConnection::UnsupportedSchemeError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "scheme is http" do
|
53
|
+
it "sets the scheme" do
|
54
|
+
subject.scheme = 'http'
|
55
|
+
expect(subject.scheme).to eq 'http'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "scheme is https" do
|
60
|
+
it "sets the scheme" do
|
61
|
+
subject.scheme = 'https'
|
62
|
+
expect(subject.scheme).to eq 'https'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#host" do
|
68
|
+
it "returns the host server" do
|
69
|
+
expect(subject.host).to eq 'yammer.com'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#port" do
|
74
|
+
it "returns the port" do
|
75
|
+
expect(subject.port).to eq 443
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#ssl?" do
|
80
|
+
context "scheme is https" do
|
81
|
+
it "returns true" do
|
82
|
+
subject.scheme = 'https'
|
83
|
+
expect(subject.ssl?).to eq true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "scheme is http" do
|
88
|
+
it "returns false" do
|
89
|
+
subject.scheme = 'http'
|
90
|
+
expect(subject.ssl?).to eq false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#http_connection" do
|
96
|
+
it "behaves like HTTP client" do
|
97
|
+
expect(subject.http_connection).to respond_to(:get)
|
98
|
+
expect(subject.http_connection).to respond_to(:post)
|
99
|
+
expect(subject.http_connection).to respond_to(:put)
|
100
|
+
expect(subject.http_connection).to respond_to(:delete)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#absolute_url" do
|
105
|
+
context "with no parameters" do
|
106
|
+
it "returns a uri without path" do
|
107
|
+
expect(subject.absolute_url).to eq "https://yammer.com"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with parameters" do
|
112
|
+
it "returns a uri with path" do
|
113
|
+
expect(subject.absolute_url('/oauth/v2/authorize')).to eq "https://yammer.com/oauth/v2/authorize"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#configure_ssl" do
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#redirect_limit_reached?" do
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#ssl_verify_mode" do
|
125
|
+
context "ssl verify set to true" do
|
126
|
+
it "returns OpenSSL::SSL::VERIFY_PEER" do
|
127
|
+
subject.ssl = { :verify => true }
|
128
|
+
expect(subject.send(:ssl_verify_mode)).to eq OpenSSL::SSL::VERIFY_PEER
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "ssl verify set to false" do
|
133
|
+
it "returns OpenSSL::SSL::VERIFY_NONE" do
|
134
|
+
subject.ssl = { :verify => false }
|
135
|
+
expect(subject.send(:ssl_verify_mode)).to eq OpenSSL::SSL::VERIFY_NONE
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "ssl_cert_store" do
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#send_request" do
|
144
|
+
before do
|
145
|
+
@http_ok = OpenStruct.new(
|
146
|
+
:code => '200',
|
147
|
+
:body => 'success',
|
148
|
+
:header => {'Content-Type' => "application/json"}
|
149
|
+
)
|
150
|
+
@http_redirect = OpenStruct.new(
|
151
|
+
:code => '301',
|
152
|
+
:body => 'redirect',
|
153
|
+
:header => {'Location' => "http://yammer.com/members"}
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
context "when method is not supported" do
|
158
|
+
it "raises an error" do
|
159
|
+
expect {subject.send_request(:patch, '/')}.to raise_error(OAuth2::HttpConnection::UnhandledHTTPMethodError)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when method is get" do
|
164
|
+
it "returns an http response" do
|
165
|
+
path = '/oauth/authorize'
|
166
|
+
params = {:client_id => '001337', :client_secret => 'abcxyz'}
|
167
|
+
method = :get
|
168
|
+
|
169
|
+
normalized_path = '/oauth/authorize?client_id=001337&client_secret=abcxyz'
|
170
|
+
|
171
|
+
Net::HTTP.any_instance.should_receive(:get).with(normalized_path, subject.default_headers).and_return(@http_ok)
|
172
|
+
response = subject.send_request(method, path, :params => params)
|
173
|
+
|
174
|
+
expect(response.code).to eq '200'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when method is delete" do
|
179
|
+
it "returns an http response" do
|
180
|
+
path = '/users/1'
|
181
|
+
method = 'delete'
|
182
|
+
|
183
|
+
Net::HTTP.any_instance.should_receive(:delete).with(path, subject.default_headers).and_return(@http_ok)
|
184
|
+
response = subject.send_request(method, path)
|
185
|
+
|
186
|
+
expect(response.code).to eq '200'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "when method is post" do
|
191
|
+
it "returns an http response" do
|
192
|
+
path = '/users'
|
193
|
+
params = {:first_name => 'john', :last_name => 'smith'}
|
194
|
+
query = Addressable::URI.form_encode(params)
|
195
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded' }.merge(subject.default_headers)
|
196
|
+
|
197
|
+
Net::HTTP.any_instance.should_receive(:post).with(path, query, headers).and_return(@http_ok)
|
198
|
+
response =subject.send_request(:post, path, :params => params)
|
199
|
+
|
200
|
+
expect(response.code).to eq '200'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context "when method is put" do
|
205
|
+
it "returns an http response" do
|
206
|
+
path = '/users/1'
|
207
|
+
params = {:first_name => 'jane', :last_name => 'doe'}
|
208
|
+
query = Addressable::URI.form_encode(params)
|
209
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded' }.merge(subject.default_headers)
|
210
|
+
|
211
|
+
Net::HTTP.any_instance.should_receive(:put).with(path, query, headers).and_return(@http_ok)
|
212
|
+
|
213
|
+
response = subject.send_request(:put, path, :params => params)
|
214
|
+
|
215
|
+
expect(response.code).to eq '200'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
it "follows redirect" do
|
220
|
+
path = '/users'
|
221
|
+
params = {:first_name => 'jane', :last_name => 'doe'}
|
222
|
+
query = Addressable::URI.form_encode(params)
|
223
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded' }.merge(subject.default_headers)
|
224
|
+
client = double("client")
|
225
|
+
|
226
|
+
subject.should_receive(:http_connection).twice.and_return(client, client)
|
227
|
+
client.should_receive(:post).ordered.with(path, query, headers).and_return(@http_redirect)
|
228
|
+
client.should_receive(:post).ordered.with('/members', query, headers).and_return(@http_ok)
|
229
|
+
|
230
|
+
response = subject.send_request(:post, path, :params => params)
|
231
|
+
|
232
|
+
expect(response.code).to eq '200'
|
233
|
+
end
|
234
|
+
|
235
|
+
it "respects the redirect limit " do
|
236
|
+
subject.max_redirects = 1
|
237
|
+
path = '/users'
|
238
|
+
params = {:first_name => 'jane', :last_name => 'doe'}
|
239
|
+
query = Addressable::URI.form_encode(params)
|
240
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded' }.merge(subject.default_headers)
|
241
|
+
client = double("client")
|
242
|
+
|
243
|
+
subject.should_receive(:http_connection).twice.and_return(client, client)
|
244
|
+
client.should_receive(:post).ordered.with(path, query, headers).and_return(@http_redirect)
|
245
|
+
client.should_receive(:post).ordered.with('/members', query, headers).and_return(@http_redirect)
|
246
|
+
|
247
|
+
response = subject.send_request(:post, path, :params => params)
|
248
|
+
|
249
|
+
expect(response.code).to eq '301'
|
250
|
+
end
|
251
|
+
|
252
|
+
it "modifies http 303 redirect from POST to GET " do
|
253
|
+
http_303 = OpenStruct.new(
|
254
|
+
:code => '303',
|
255
|
+
:body => 'redirect',
|
256
|
+
:header => {'Location' => "http://yammer.com/members"}
|
257
|
+
)
|
258
|
+
path = '/users'
|
259
|
+
params = {:first_name => 'jane', :last_name => 'doe'}
|
260
|
+
query = Addressable::URI.form_encode(params)
|
261
|
+
headers = {'Content-Type' => 'application/x-www-form-urlencoded' }.merge(subject.default_headers)
|
262
|
+
client = double("client")
|
263
|
+
|
264
|
+
subject.should_receive(:http_connection).twice.and_return(client, client)
|
265
|
+
client.should_receive(:post).ordered.with(path, query, headers).and_return(http_303)
|
266
|
+
client.should_receive(:get).ordered.with('/members', subject.default_headers).and_return(@http_ok)
|
267
|
+
|
268
|
+
response = subject.send_request(:post, path, :params => params)
|
269
|
+
|
270
|
+
expect(response.code).to eq '200'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe OAuth2::Grant::AuthorizationCode do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@host = 'https://example.com'
|
7
|
+
@client_id = 's6BhdRkqt3'
|
8
|
+
@client_secret = 'SplxlOBeZQQYbYS6WxSbIA'
|
9
|
+
@client = OAuth2::Client.new(@host, @client_id, @client_secret)
|
10
|
+
OAuth2::Grant::AuthorizationCode.stub(:make_request)
|
11
|
+
end
|
12
|
+
|
13
|
+
subject do
|
14
|
+
OAuth2::Grant::AuthorizationCode.new(@client)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#authorization_params" do
|
18
|
+
it "returns client_id and response_type" do
|
19
|
+
expect(subject.send(:authorization_params)).to eq({ :client_id => "s6BhdRkqt3", :response_type => "code" })
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#response_type" do
|
24
|
+
it "returns response type" do
|
25
|
+
expect(subject.response_type).to eq 'code'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#grant_type" do
|
30
|
+
it "returns grant type" do
|
31
|
+
expect(subject.grant_type).to eq 'authorization_code'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#authorization_path" do
|
36
|
+
context "without parameters" do
|
37
|
+
it "returns authorization path with only response_type and client_id in query string" do
|
38
|
+
query_values = Addressable::URI.parse(subject.authorization_path).query_values
|
39
|
+
expect(query_values).to eq({"response_type"=>"code", "client_id"=>"s6BhdRkqt3"})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with parameters" do
|
44
|
+
it "returns authorization path with exra parameters in query string" do
|
45
|
+
path = subject.authorization_path({
|
46
|
+
:scope => 'abc xyz',
|
47
|
+
:state => 'state'
|
48
|
+
})
|
49
|
+
query_values = Addressable::URI.parse(path).query_values
|
50
|
+
expect(query_values).to eq({"response_type"=>"code", "client_id"=>"s6BhdRkqt3", "scope" => "abc xyz", "state" => "state"})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#token_path" do
|
56
|
+
context "with parameters" do
|
57
|
+
it "returns token path" do
|
58
|
+
query_values = Addressable::URI.parse(subject.token_path).query_values
|
59
|
+
expect(subject.token_path).to eq '/oauth2/token'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "without parameters" do
|
64
|
+
it "returns token path with provided query parameters" do
|
65
|
+
path = subject.token_path({
|
66
|
+
:response_type => 'token',
|
67
|
+
:state => '16WxSbIA',
|
68
|
+
:code => 's6BhdRkqt3'
|
69
|
+
})
|
70
|
+
query_values = Addressable::URI.parse(path).query_values
|
71
|
+
expect(query_values).to eq({"response_type"=>"token", "code"=>"s6BhdRkqt3", "state" =>"16WxSbIA"})
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#fetch_authorization_url" do
|
77
|
+
it "returns response authorization page from oauth server" do
|
78
|
+
subject.should_receive(:make_request).with(:get, "/oauth2/authorize", {:params=> {:response_type=>"code", :client_id=>"s6BhdRkqt3"}})
|
79
|
+
subject.fetch_authorization_url
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#get_token" do
|
84
|
+
it "exchanges authorization code for access token" do
|
85
|
+
subject.should_receive(:make_request).with(:post, "/oauth2/token", {:params=>{:scope=>"abc xyz", :state=>"state", :code=>"G3Y6jU3a"}, :authenticate=>:headers})
|
86
|
+
subject.get_token('G3Y6jU3a', :params => {:scope => 'abc xyz', :state => 'state'})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
require 'oauth2/helper'
|
3
|
+
|
4
|
+
describe OAuth2::Grant::Base do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
@client = OpenStruct.new(
|
8
|
+
:host => 'example.com',
|
9
|
+
:client_id => 's6BhdRkqt3',
|
10
|
+
:client_secret => 'SplxlOBeZQQYbYS6WxSbIA',
|
11
|
+
:authorize_path => '/oauth2/authorize',
|
12
|
+
:token_path => '/oauth2/token',
|
13
|
+
:device_path => '/oauth2/device',
|
14
|
+
:connection => OpenStruct.new
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
subject do
|
19
|
+
OAuth2::Grant::Base.new(@client)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#make_request" do
|
23
|
+
context "without authenticate option" do
|
24
|
+
it "does not send authorization credentials" do
|
25
|
+
@client.connection.should_receive(:send_request).with(:get, '/oauth2', {})
|
26
|
+
subject.make_request(:get, '/oauth2')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with authenticate option" do
|
31
|
+
context "option is headers" do
|
32
|
+
it "authorization credentials in headers" do
|
33
|
+
opts = {
|
34
|
+
:headers => {'Authorization' => OAuth2::UrlHelper::http_basic_encode(@client.client_id, @client.client_secret)},
|
35
|
+
:params => {:client_id => @client.client_id}
|
36
|
+
}
|
37
|
+
@client.connection.should_receive(:send_request).with(:get, '/oauth2', opts)
|
38
|
+
subject.make_request(:get, '/oauth2', :authenticate => :headers, :params => {:client_id => @client.client_id})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "option is body" do
|
43
|
+
it "authorization credentials in body" do
|
44
|
+
opts = {
|
45
|
+
:params => {
|
46
|
+
:code => 'abc123',
|
47
|
+
:client_id => @client.client_id,
|
48
|
+
:client_secret => @client.client_secret
|
49
|
+
},
|
50
|
+
}
|
51
|
+
@client.connection.should_receive(:send_request).with(:get, '/oauth2', opts)
|
52
|
+
subject.make_request(:get, '/oauth2', :params => {:code => 'abc123'}, :authenticate => :body)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe OAuth2::Grant::ClientCredentials do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@host = 'example.com'
|
7
|
+
@client_id = 's6BhdRkqt3'
|
8
|
+
@client_secret = 'SplxlOBeZQQYbYS6WxSbIA'
|
9
|
+
@client = OAuth2::Client.new(@host, @client_id, @client_secret)
|
10
|
+
end
|
11
|
+
|
12
|
+
subject do
|
13
|
+
OAuth2::Grant::ClientCredentials.new(@client)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#grant_type" do
|
17
|
+
it "returns grant type" do
|
18
|
+
expect(subject.grant_type).to eq 'client_credentials'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#get_token" do
|
23
|
+
it "exchanges authorization code for access token" do
|
24
|
+
subject.should_receive(:make_request).with(:post, "/oauth2/token", {:params=>{:grant_type=>"client_credentials"}, :authenticate=>:headers})
|
25
|
+
subject.get_token
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|