rockoauth 0.1.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.
- checksums.yaml +7 -0
- data/History.txt +5 -0
- data/README.rdoc +422 -0
- data/example/README.rdoc +11 -0
- data/example/application.rb +158 -0
- data/example/config.ru +3 -0
- data/example/environment.rb +11 -0
- data/example/models/connection.rb +9 -0
- data/example/models/note.rb +4 -0
- data/example/models/user.rb +5 -0
- data/example/public/style.css +78 -0
- data/example/schema.rb +22 -0
- data/example/views/authorize.erb +28 -0
- data/example/views/create_user.erb +3 -0
- data/example/views/error.erb +6 -0
- data/example/views/home.erb +24 -0
- data/example/views/layout.erb +24 -0
- data/example/views/login.erb +20 -0
- data/example/views/new_client.erb +25 -0
- data/example/views/new_user.erb +22 -0
- data/example/views/show_client.erb +15 -0
- data/lib/rockoauth/model/authorization.rb +132 -0
- data/lib/rockoauth/model/client.rb +54 -0
- data/lib/rockoauth/model/client_owner.rb +13 -0
- data/lib/rockoauth/model/hashing.rb +26 -0
- data/lib/rockoauth/model/helpers.rb +14 -0
- data/lib/rockoauth/model/resource_owner.rb +22 -0
- data/lib/rockoauth/model.rb +38 -0
- data/lib/rockoauth/provider/access_token.rb +70 -0
- data/lib/rockoauth/provider/authorization.rb +185 -0
- data/lib/rockoauth/provider/error.rb +19 -0
- data/lib/rockoauth/provider/exchange.rb +225 -0
- data/lib/rockoauth/provider.rb +133 -0
- data/lib/rockoauth/router.rb +75 -0
- data/lib/rockoauth/schema/20120828112156_rockoauth_schema_original_schema.rb +35 -0
- data/lib/rockoauth/schema/20121024180930_rockoauth_schema_add_authorization_index.rb +13 -0
- data/lib/rockoauth/schema/20121025180447_rockoauth_schema_add_unique_indexes.rb +31 -0
- data/lib/rockoauth/schema.rb +25 -0
- data/lib/rockoauth.rb +1 -0
- data/spec/factories.rb +20 -0
- data/spec/request_helpers.rb +62 -0
- data/spec/rockoauth/model/authorization_spec.rb +237 -0
- data/spec/rockoauth/model/client_spec.rb +44 -0
- data/spec/rockoauth/model/helpers_spec.rb +25 -0
- data/spec/rockoauth/model/resource_owner_spec.rb +87 -0
- data/spec/rockoauth/provider/access_token_spec.rb +138 -0
- data/spec/rockoauth/provider/authorization_spec.rb +356 -0
- data/spec/rockoauth/provider/exchange_spec.rb +361 -0
- data/spec/rockoauth/provider_spec.rb +560 -0
- data/spec/spec_helper.rb +80 -0
- data/spec/test_app/helper.rb +36 -0
- data/spec/test_app/provider/application.rb +67 -0
- data/spec/test_app/provider/views/authorize.erb +19 -0
- metadata +238 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
class RockoauthSchemaAddUniqueIndexes < ActiveRecord::Migration
|
2
|
+
FIELDS = [:code, :refresh_token_hash]
|
3
|
+
|
4
|
+
def self.up
|
5
|
+
FIELDS.each do |field|
|
6
|
+
remove_index :oauth2_authorizations, :name => "index_oauth2_client_id_and_#{field}"
|
7
|
+
add_index :oauth2_authorizations, [:client_id, field], :unique => true, :name => "index_oauth2_client_id_#{field}"
|
8
|
+
end
|
9
|
+
remove_index :oauth2_authorizations, [:access_token_hash]
|
10
|
+
add_index :oauth2_authorizations, [:access_token_hash], :unique => true
|
11
|
+
|
12
|
+
remove_index :oauth2_clients, [:client_id]
|
13
|
+
add_index :oauth2_clients, [:client_id], :unique => true
|
14
|
+
|
15
|
+
add_index :oauth2_clients, [:name], :unique => true
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
FIELDS.each do |field|
|
20
|
+
remove_index :oauth2_authorizations, [:client_id, field]
|
21
|
+
add_index :oauth2_authorizations, [:client_id, field]
|
22
|
+
end
|
23
|
+
remove_index :oauth2_authorizations, [:access_token_hash]
|
24
|
+
add_index :oauth2_authorizations, [:access_token_hash]
|
25
|
+
|
26
|
+
remove_index :oauth2_clients, [:client_id]
|
27
|
+
add_index :oauth2_clients, [:client_id]
|
28
|
+
|
29
|
+
remove_index :oauth2_clients, [:name]
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RockOAuth
|
2
|
+
|
3
|
+
class Schema
|
4
|
+
def self.migrate
|
5
|
+
ActiveRecord::Base.logger ||= Logger.new(StringIO.new)
|
6
|
+
ActiveRecord::Migrator.up(migrations_path)
|
7
|
+
end
|
8
|
+
class << self
|
9
|
+
alias :up :migrate
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.rollback
|
13
|
+
ActiveRecord::Base.logger ||= Logger.new(StringIO.new)
|
14
|
+
ActiveRecord::Migrator.down(migrations_path)
|
15
|
+
end
|
16
|
+
class << self
|
17
|
+
alias :down :rollback
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.migrations_path
|
21
|
+
File.expand_path('../schema', __FILE__)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/rockoauth.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rockoauth/provider'
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
Factory.sequence :client_name do |n|
|
4
|
+
"Client ##{n}"
|
5
|
+
end
|
6
|
+
|
7
|
+
Factory.sequence :user_name do |n|
|
8
|
+
"User ##{n}"
|
9
|
+
end
|
10
|
+
|
11
|
+
Factory.define :owner, :class => TestApp::User do |u|
|
12
|
+
u.name { Factory.next :user_name }
|
13
|
+
end
|
14
|
+
|
15
|
+
Factory.define :client, :class => RockOAuth::Model::Client do |c|
|
16
|
+
c.client_id { RockOAuth.random_string }
|
17
|
+
c.client_secret { RockOAuth.random_string }
|
18
|
+
c.name { Factory.next :client_name }
|
19
|
+
c.redirect_uri 'https://client.example.com/cb'
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module RequestHelpers
|
4
|
+
require "net/http"
|
5
|
+
|
6
|
+
SERVER_PORT = TCPServer.new(0).addr[1]
|
7
|
+
|
8
|
+
def querystring(params)
|
9
|
+
params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join("&")
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(query_params)
|
13
|
+
uri = URI.parse("http://localhost:#{SERVER_PORT}/authorize?" + querystring(query_params))
|
14
|
+
Net::HTTP.get_response(uri)
|
15
|
+
end
|
16
|
+
|
17
|
+
def allow_or_deny(query_params)
|
18
|
+
Net::HTTP.post_form(URI.parse("http://localhost:#{SERVER_PORT}/allow"), query_params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post_basic_auth(auth_params, query_params)
|
22
|
+
url = "http://#{ auth_params["client_id"] }:#{ auth_params["client_secret"] }@localhost:#{SERVER_PORT}/authorize"
|
23
|
+
Net::HTTP.post_form(URI.parse(url), query_params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def post(body_params, query_params = {})
|
27
|
+
uri = URI.parse("http://localhost:#{SERVER_PORT}/authorize?" + querystring(query_params))
|
28
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
29
|
+
http.post(uri.path + "?" + uri.query.to_s, querystring(body_params))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_response(response, status, body)
|
34
|
+
expect(response.code.to_i).to eq(status)
|
35
|
+
expect(response.body).to eq(body)
|
36
|
+
expect(response["Cache-Control"]).to eq("no-store")
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_json_response(response, status, body)
|
40
|
+
expect(response.code.to_i).to eq(status)
|
41
|
+
expect(JSON.parse(response.body)).to eq(body)
|
42
|
+
expect(response["Content-Type"]).to eq("application/json")
|
43
|
+
expect(response["Cache-Control"]).to eq("no-store")
|
44
|
+
end
|
45
|
+
|
46
|
+
def mock_request(request_class, stubs = {})
|
47
|
+
mock_request = double(request_class)
|
48
|
+
method_stubs = {
|
49
|
+
:redirect? => false,
|
50
|
+
:response_body => nil,
|
51
|
+
:response_headers => {},
|
52
|
+
:response_status => 200
|
53
|
+
}.merge(stubs)
|
54
|
+
|
55
|
+
method_stubs.each do |method, value|
|
56
|
+
expect(mock_request).to receive(method).and_return(value)
|
57
|
+
end
|
58
|
+
|
59
|
+
mock_request
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Model::Authorization do
|
4
|
+
let(:client) { Factory :client }
|
5
|
+
let(:impostor) { Factory :client }
|
6
|
+
let(:owner) { Factory :owner }
|
7
|
+
let(:user) { Factory :owner }
|
8
|
+
let(:tester) { Factory(:owner) }
|
9
|
+
|
10
|
+
let(:authorization) do
|
11
|
+
create_authorization(:owner => tester, :client => client)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is valid" do
|
15
|
+
expect(authorization).to be_valid
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is not valid without a client" do
|
19
|
+
authorization.client = nil
|
20
|
+
expect(authorization).not_to be_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is not valid without an owner" do
|
24
|
+
authorization.owner = nil
|
25
|
+
expect(authorization).not_to be_valid
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "when there are existing authorizations" do
|
29
|
+
before do
|
30
|
+
create_authorization(
|
31
|
+
:owner => user,
|
32
|
+
:client => impostor,
|
33
|
+
:access_token => 'existing_access_token')
|
34
|
+
|
35
|
+
create_authorization(
|
36
|
+
:owner => user,
|
37
|
+
:client => client,
|
38
|
+
:code => 'existing_code')
|
39
|
+
|
40
|
+
create_authorization(
|
41
|
+
:owner => owner,
|
42
|
+
:client => client,
|
43
|
+
:refresh_token => 'existing_refresh_token')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "is valid if its access_token is unique" do
|
47
|
+
expect(authorization).to be_valid
|
48
|
+
end
|
49
|
+
|
50
|
+
it "is valid if both access_tokens are nil" do
|
51
|
+
RockOAuth::Model::Authorization.first.update_attribute(:access_token, nil)
|
52
|
+
authorization.access_token = nil
|
53
|
+
expect(authorization).to be_valid
|
54
|
+
end
|
55
|
+
|
56
|
+
it "is not valid if its access_token is not unique" do
|
57
|
+
authorization.access_token = 'existing_access_token'
|
58
|
+
expect(authorization).not_to be_valid
|
59
|
+
end
|
60
|
+
|
61
|
+
it "is valid if it has a unique code for its client" do
|
62
|
+
authorization.client = impostor
|
63
|
+
authorization.code = 'existing_code'
|
64
|
+
expect(authorization).to be_valid
|
65
|
+
end
|
66
|
+
|
67
|
+
it "is not valid if it does not have a unique client and code" do
|
68
|
+
authorization.code = 'existing_code'
|
69
|
+
expect(authorization).not_to be_valid
|
70
|
+
end
|
71
|
+
|
72
|
+
it "is valid if it has a unique refresh_token for its client" do
|
73
|
+
authorization.client = impostor
|
74
|
+
authorization.refresh_token = 'existing_refresh_token'
|
75
|
+
expect(authorization).to be_valid
|
76
|
+
end
|
77
|
+
|
78
|
+
it "is not valid if it does not have a unique client and refresh_token" do
|
79
|
+
authorization.refresh_token = 'existing_refresh_token'
|
80
|
+
expect(authorization).not_to be_valid
|
81
|
+
end
|
82
|
+
|
83
|
+
describe ".create_code" do
|
84
|
+
before { allow(RockOAuth).to receive(:random_string).and_return('existing_code', 'new_code') }
|
85
|
+
|
86
|
+
it "returns the first code the client has not used" do
|
87
|
+
expect(RockOAuth::Model::Authorization.create_code(client)).to eq('new_code')
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns the first code another client has not used" do
|
91
|
+
expect(RockOAuth::Model::Authorization.create_code(impostor)).to eq('existing_code')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ".create_access_token" do
|
96
|
+
before { allow(RockOAuth).to receive(:random_string).and_return('existing_access_token', 'new_access_token') }
|
97
|
+
|
98
|
+
it "returns the first unused token it can find" do
|
99
|
+
expect(RockOAuth::Model::Authorization.create_access_token).to eq('new_access_token')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe ".create_refresh_token" do
|
104
|
+
before { allow(RockOAuth).to receive(:random_string).and_return('existing_refresh_token', 'new_refresh_token') }
|
105
|
+
|
106
|
+
it "returns the first refresh_token the client has not used" do
|
107
|
+
expect(RockOAuth::Model::Authorization.create_refresh_token(client)).to eq('new_refresh_token')
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns the first refresh_token another client has not used" do
|
111
|
+
expect(RockOAuth::Model::Authorization.create_refresh_token(impostor)).to eq('existing_refresh_token')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "duplicate records" do
|
116
|
+
it "raises an error if a duplicate authorization is created" do
|
117
|
+
expect {
|
118
|
+
authorization = RockOAuth::Model::Authorization.__send__(:new)
|
119
|
+
authorization.owner = user
|
120
|
+
authorization.client = client
|
121
|
+
authorization.save
|
122
|
+
}.to raise_error
|
123
|
+
end
|
124
|
+
|
125
|
+
it "finds an existing record after a race" do
|
126
|
+
allow(user).to receive(:oauth2_authorization_for) do
|
127
|
+
allow(user).to receive(:oauth2_authorization_for).and_call_original
|
128
|
+
raise TypeError, 'Mysql::Error: Duplicate entry'
|
129
|
+
end
|
130
|
+
authorization = RockOAuth::Model::Authorization.for(user, client)
|
131
|
+
expect(authorization.owner).to eq(user)
|
132
|
+
expect(authorization.client).to eq(client)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#exchange!" do
|
138
|
+
it "saves the record" do
|
139
|
+
expect(authorization).to receive(:save!)
|
140
|
+
authorization.exchange!
|
141
|
+
end
|
142
|
+
|
143
|
+
it "uses its helpers to find unique tokens" do
|
144
|
+
expect(RockOAuth::Model::Authorization).to receive(:create_access_token).and_return('access_token')
|
145
|
+
authorization.exchange!
|
146
|
+
expect(authorization.access_token).to eq('access_token')
|
147
|
+
end
|
148
|
+
|
149
|
+
it "updates the tokens correctly" do
|
150
|
+
authorization.exchange!
|
151
|
+
expect(authorization).to be_valid
|
152
|
+
expect(authorization.code).to be_nil
|
153
|
+
expect(authorization.refresh_token).to be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#expired?" do
|
158
|
+
it "returns false when not expiry is set" do
|
159
|
+
expect(authorization).not_to be_expired
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns false when expiry is in the future" do
|
163
|
+
authorization.expires_at = 2.days.from_now
|
164
|
+
expect(authorization).not_to be_expired
|
165
|
+
end
|
166
|
+
|
167
|
+
it "returns true when expiry is in the past" do
|
168
|
+
authorization.expires_at = 2.days.ago
|
169
|
+
expect(authorization).to be_expired
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "#grants_access?" do
|
174
|
+
it "returns true given the right user" do
|
175
|
+
expect(authorization.grants_access?(tester)).to eq(true)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "returns false given the wrong user" do
|
179
|
+
expect(authorization.grants_access?(user)).to eq(false)
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "when the authorization is expired" do
|
183
|
+
before { authorization.expires_at = 2.days.ago }
|
184
|
+
|
185
|
+
it "returns false in all cases" do
|
186
|
+
expect(authorization.grants_access?(tester)).to eq(false)
|
187
|
+
expect(authorization.grants_access?(user)).to eq(false)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "with a scope" do
|
193
|
+
before { authorization.scope = 'foo bar' }
|
194
|
+
|
195
|
+
describe "#in_scope?" do
|
196
|
+
it "returns true for authorized scopes" do
|
197
|
+
expect(authorization).to be_in_scope('foo')
|
198
|
+
expect(authorization).to be_in_scope('bar')
|
199
|
+
end
|
200
|
+
|
201
|
+
it "returns false for unauthorized scopes" do
|
202
|
+
expect(authorization).not_to be_in_scope('qux')
|
203
|
+
expect(authorization).not_to be_in_scope('fo')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "#grants_access?" do
|
208
|
+
it "returns true given the right user and all authorization scopes" do
|
209
|
+
expect(authorization.grants_access?(tester, 'foo', 'bar')).to eq(true)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "returns true given the right user and some authorization scopes" do
|
213
|
+
expect(authorization.grants_access?(tester, 'bar')).to eq(true)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "returns false given the right user and some unauthorization scopes" do
|
217
|
+
expect(authorization.grants_access?(tester, 'foo', 'bar', 'qux')).to eq(false)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "returns false given an unauthorized scope" do
|
221
|
+
expect(authorization.grants_access?(tester, 'qux')).to eq(false)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "returns true given the right user" do
|
225
|
+
expect(authorization.grants_access?(tester)).to eq(true)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "returns false given the wrong user" do
|
229
|
+
expect(authorization.grants_access?(user)).to eq(false)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "returns false given the wrong user and an authorized scope" do
|
233
|
+
expect(authorization.grants_access?(user, 'foo')).to eq(false)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Model::Client do
|
4
|
+
before do
|
5
|
+
@client = RockOAuth::Model::Client.create(:name => 'App', :redirect_uri => 'http://example.com/cb')
|
6
|
+
@owner = Factory(:owner)
|
7
|
+
RockOAuth::Model::Authorization.for(@owner, @client)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is valid" do
|
11
|
+
expect(@client).to be_valid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is invalid without a name" do
|
15
|
+
@client.name = nil
|
16
|
+
expect(@client).not_to be_valid
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is invalid without a redirect_uri" do
|
20
|
+
@client.redirect_uri = nil
|
21
|
+
expect(@client).not_to be_valid
|
22
|
+
end
|
23
|
+
|
24
|
+
it "is invalid with a non-URI redirect_uri" do
|
25
|
+
@client.redirect_uri = 'foo'
|
26
|
+
expect(@client).not_to be_valid
|
27
|
+
end
|
28
|
+
|
29
|
+
# http://en.wikipedia.org/wiki/HTTP_response_splitting
|
30
|
+
it "is invalid if the URI contains HTTP line breaks" do
|
31
|
+
@client.redirect_uri = "http://example.com/c\r\nb"
|
32
|
+
expect(@client).not_to be_valid
|
33
|
+
end
|
34
|
+
|
35
|
+
it "has client_id and client_secret filled in" do
|
36
|
+
expect(@client.client_id).not_to be_nil
|
37
|
+
expect(@client.client_secret).not_to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "destroys its authorizations on destroy" do
|
41
|
+
@client.destroy
|
42
|
+
expect(RockOAuth::Model::Authorization.count).to be_zero
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Model::Helpers do
|
4
|
+
subject { RockOAuth::Model::Helpers }
|
5
|
+
|
6
|
+
describe '.count' do
|
7
|
+
let(:owner) { Factory(:owner) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
3.times { Factory(:client, :owner => owner) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when conditions are not passed' do
|
14
|
+
it 'returns count of total rows' do
|
15
|
+
expect(subject.count(owner.oauth2_clients)).to eq(3)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when conditions are passed' do
|
20
|
+
it 'returns count of rows satisfying supplied conditions' do
|
21
|
+
expect(subject.count(RockOAuth::Model::Client, :client_id => RockOAuth::Model::Client.first.client_id)).to eq(1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Model::ResourceOwner do
|
4
|
+
before do
|
5
|
+
@owner = Factory(:owner)
|
6
|
+
@client = Factory(:client)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#grant_access!" do
|
10
|
+
it "raises an error when passed an invalid client argument" do
|
11
|
+
expect{ @owner.grant_access!('client') }.to raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "creates an authorization between the owner and the client" do
|
15
|
+
authorization = RockOAuth::Model::Authorization.__send__(:new)
|
16
|
+
expect(RockOAuth::Model::Authorization).to receive(:new).and_return(authorization)
|
17
|
+
@owner.grant_access!(@client)
|
18
|
+
end
|
19
|
+
|
20
|
+
# This is hacky, but doubleing ActiveRecord turns out to get messy
|
21
|
+
it "creates an Authorization" do
|
22
|
+
expect(RockOAuth::Model::Authorization.count).to eq(0)
|
23
|
+
@owner.grant_access!(@client)
|
24
|
+
expect(RockOAuth::Model::Authorization.count).to eq(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the authorization" do
|
28
|
+
expect(@owner.grant_access!(@client)).to be_kind_of(RockOAuth::Model::Authorization)
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method must return the same owner object, since the assertion
|
32
|
+
# handler may modify it -- either by changing its attributes or by extending
|
33
|
+
# it with new methods. These changes must be returned to the app calling the
|
34
|
+
# Provider interface.
|
35
|
+
it "sets the receiver as the authorization's owner" do
|
36
|
+
authorization = @owner.grant_access!(@client)
|
37
|
+
expect(authorization.owner).to be_equal(@owner)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets the duration of the authorization" do
|
41
|
+
authorization = @owner.grant_access!(@client, :duration => 5.hours)
|
42
|
+
expect(authorization.expires_at.to_i).to eq((Time.now + 5.hours.to_i).to_i)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when there is an existing authorization" do
|
47
|
+
before do
|
48
|
+
@authorization = create_authorization(:owner => @owner, :client => @client)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does not create a new one" do
|
52
|
+
expect(RockOAuth::Model::Authorization).not_to receive(:new)
|
53
|
+
@owner.grant_access!(@client)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "updates the authorization with scopes" do
|
57
|
+
@owner.grant_access!(@client, :scopes => ['foo', 'bar'])
|
58
|
+
@authorization.reload
|
59
|
+
expect(@authorization.scopes).to eq(Set.new(['foo', 'bar']))
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with scopes" do
|
63
|
+
before do
|
64
|
+
@authorization.update_attribute(:scope, 'foo bar')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "merges the new scopes with the existing ones" do
|
68
|
+
@owner.grant_access!(@client, :scopes => ['qux'])
|
69
|
+
@authorization.reload
|
70
|
+
expect(@authorization.scopes).to eq(Set.new(['foo', 'bar', 'qux']))
|
71
|
+
end
|
72
|
+
|
73
|
+
it "does not add duplicate scopes to the list" do
|
74
|
+
@owner.grant_access!(@client, :scopes => ['qux'])
|
75
|
+
@owner.grant_access!(@client, :scopes => ['qux'])
|
76
|
+
@authorization.reload
|
77
|
+
expect(@authorization.scopes).to eq(Set.new(['foo', 'bar', 'qux']))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "destroys its authorizations on destroy" do
|
83
|
+
RockOAuth::Model::Authorization.for(@owner, @client)
|
84
|
+
@owner.destroy
|
85
|
+
expect(RockOAuth::Model::Authorization.count).to be_zero
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Provider::AccessToken do
|
4
|
+
before do
|
5
|
+
@alice = TestApp::User['Alice']
|
6
|
+
@bob = TestApp::User['Bob']
|
7
|
+
|
8
|
+
create_authorization(
|
9
|
+
:owner => @alice,
|
10
|
+
:client => Factory(:client),
|
11
|
+
:scope => 'profile',
|
12
|
+
:access_token => 'sesame')
|
13
|
+
|
14
|
+
@authorization = create_authorization(
|
15
|
+
:owner => @bob,
|
16
|
+
:client => Factory(:client),
|
17
|
+
:scope => 'profile',
|
18
|
+
:access_token => 'magic-key')
|
19
|
+
|
20
|
+
RockOAuth::Provider.realm = 'Demo App'
|
21
|
+
end
|
22
|
+
|
23
|
+
let :token do
|
24
|
+
RockOAuth::Provider::AccessToken.new(@bob, ['profile'], 'magic-key')
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_examples_for "valid token" do
|
28
|
+
it "is valid" do
|
29
|
+
expect(token).to be_valid
|
30
|
+
end
|
31
|
+
it "does not add headers" do
|
32
|
+
expect(token.response_headers).to eq({})
|
33
|
+
end
|
34
|
+
it "has an OK status code" do
|
35
|
+
expect(token.response_status).to eq(200)
|
36
|
+
end
|
37
|
+
it "returns the owner who granted the authorization" do
|
38
|
+
expect(token.owner).to eq(@bob)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
shared_examples_for "invalid token" do
|
43
|
+
it "is not valid" do
|
44
|
+
expect(token).not_to be_valid
|
45
|
+
end
|
46
|
+
it "does not return the owner" do
|
47
|
+
expect(token.owner).to be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "with the right user, scope and token" do
|
52
|
+
it_should_behave_like "valid token"
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "with an implicit user" do
|
56
|
+
let :token do
|
57
|
+
RockOAuth::Provider::AccessToken.new(:implicit, ['profile'], 'magic-key')
|
58
|
+
end
|
59
|
+
it_should_behave_like "valid token"
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with no user" do
|
63
|
+
let :token do
|
64
|
+
RockOAuth::Provider::AccessToken.new(nil, ['profile'], 'magic-key')
|
65
|
+
end
|
66
|
+
it_should_behave_like "invalid token"
|
67
|
+
|
68
|
+
it "returns an error response" do
|
69
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='invalid_token'")
|
70
|
+
expect(token.response_status).to eq(401)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "with less scope than was granted" do
|
75
|
+
let :token do
|
76
|
+
RockOAuth::Provider::AccessToken.new(@bob, [], 'magic-key')
|
77
|
+
end
|
78
|
+
it_should_behave_like "valid token"
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "when the authorization has expired" do
|
82
|
+
before { @authorization.update_attribute(:expires_at, 1.hour.ago) }
|
83
|
+
it_should_behave_like "invalid token"
|
84
|
+
|
85
|
+
it "returns an error response" do
|
86
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='expired_token'")
|
87
|
+
expect(token.response_status).to eq(401)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "with a non-existent token" do
|
92
|
+
let :token do
|
93
|
+
RockOAuth::Provider::AccessToken.new(@bob, ['profile'], 'is-the-password-books')
|
94
|
+
end
|
95
|
+
it_should_behave_like "invalid token"
|
96
|
+
|
97
|
+
it "returns an error response" do
|
98
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='invalid_token'")
|
99
|
+
expect(token.response_status).to eq(401)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "with a token for the wrong user" do
|
104
|
+
let :token do
|
105
|
+
RockOAuth::Provider::AccessToken.new(@bob, ['profile'], 'sesame')
|
106
|
+
end
|
107
|
+
it_should_behave_like "invalid token"
|
108
|
+
|
109
|
+
it "returns an error response" do
|
110
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='insufficient_scope'")
|
111
|
+
expect(token.response_status).to eq(403)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "with a token for an ungranted scope" do
|
116
|
+
let :token do
|
117
|
+
RockOAuth::Provider::AccessToken.new(@bob, ['offline_access'], 'magic-key')
|
118
|
+
end
|
119
|
+
it_should_behave_like "invalid token"
|
120
|
+
|
121
|
+
it "returns an error response" do
|
122
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='insufficient_scope'")
|
123
|
+
expect(token.response_status).to eq(403)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "with no token string" do
|
128
|
+
let :token do
|
129
|
+
RockOAuth::Provider::AccessToken.new(@bob, ['profile'], nil)
|
130
|
+
end
|
131
|
+
it_should_behave_like "invalid token"
|
132
|
+
|
133
|
+
it "returns an error response" do
|
134
|
+
expect(token.response_headers['WWW-Authenticate']).to eq("OAuth realm='Demo App'")
|
135
|
+
expect(token.response_status).to eq(401)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|