songkick-oauth2-provider 0.10.2 → 0.10.3
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 +7 -0
- data/README.rdoc +18 -11
- data/example/README.rdoc +1 -1
- data/example/application.rb +9 -9
- data/example/schema.rb +1 -1
- data/example/views/authorize.erb +2 -2
- data/example/views/layout.erb +4 -4
- data/example/views/login.erb +2 -2
- data/example/views/new_client.erb +1 -1
- data/example/views/new_user.erb +1 -1
- data/lib/songkick/oauth2/model.rb +8 -6
- data/lib/songkick/oauth2/model/authorization.rb +31 -31
- data/lib/songkick/oauth2/model/client.rb +15 -15
- data/lib/songkick/oauth2/model/client_owner.rb +2 -2
- data/lib/songkick/oauth2/model/hashing.rb +3 -3
- data/lib/songkick/oauth2/model/helpers.rb +16 -0
- data/lib/songkick/oauth2/model/resource_owner.rb +4 -4
- data/lib/songkick/oauth2/provider.rb +16 -16
- data/lib/songkick/oauth2/provider/access_token.rb +20 -15
- data/lib/songkick/oauth2/provider/authorization.rb +43 -42
- data/lib/songkick/oauth2/provider/error.rb +4 -4
- data/lib/songkick/oauth2/provider/exchange.rb +46 -46
- data/lib/songkick/oauth2/router.rb +13 -13
- data/lib/songkick/oauth2/schema.rb +11 -3
- data/lib/songkick/oauth2/schema/20120828112156_songkick_oauth2_schema_original_schema.rb +2 -2
- data/lib/songkick/oauth2/schema/20121024180930_songkick_oauth2_schema_add_authorization_index.rb +3 -3
- data/lib/songkick/oauth2/schema/20121025180447_songkick_oauth2_schema_add_unique_indexes.rb +7 -7
- data/spec/request_helpers.rb +25 -21
- data/spec/songkick/oauth2/model/authorization_spec.rb +56 -56
- data/spec/songkick/oauth2/model/client_spec.rb +9 -9
- data/spec/songkick/oauth2/model/helpers_spec.rb +26 -0
- data/spec/songkick/oauth2/model/resource_owner_spec.rb +13 -13
- data/spec/songkick/oauth2/provider/access_token_spec.rb +32 -20
- data/spec/songkick/oauth2/provider/authorization_spec.rb +73 -62
- data/spec/songkick/oauth2/provider/exchange_spec.rb +72 -72
- data/spec/songkick/oauth2/provider_spec.rb +101 -101
- data/spec/spec_helper.rb +5 -3
- data/spec/test_app/helper.rb +11 -7
- data/spec/test_app/provider/application.rb +12 -12
- data/spec/test_app/provider/views/authorize.erb +2 -2
- metadata +71 -93
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9b0ad668ae50fbc3a9b425ec63dd98663b7cb67b
|
4
|
+
data.tar.gz: 1243ea91a6236236493d7c98fd55ef0b4be46008
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11235193ecb2927f87b4adf3c2f70007c65f6af0367773eb705d2fb5f7e257ca31b217bce9cd2ea6693b2fee17d0c6ea60e98c0b69885853e415a5d95bc13c54
|
7
|
+
data.tar.gz: dacea0ec9c18b48a30f5a9e0f7e5706471fbf63242598efc924e9fd902fcb75c031c993f61db92513cf549edcba88f558dbe8aadef1844869e76ea5fa1987f7c
|
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 0.10.3 / 2017-10-24
|
2
|
+
|
3
|
+
* Add support for query strings in the client redirect uri
|
4
|
+
* Do not allow the user arg for Provider.access_token() to be nil
|
5
|
+
(Use `:implicit` for implicit user lookup)
|
6
|
+
|
7
|
+
|
1
8
|
=== 0.10.2 / 2012-10-31
|
2
9
|
|
3
10
|
* Stop Model::Authorization#scope being set to an empty string and returned as
|
data/README.rdoc
CHANGED
@@ -17,6 +17,11 @@ framework-specific request objects, though you can pass those in and their
|
|
17
17
|
It stores the clients and authorizations using ActiveRecord.
|
18
18
|
|
19
19
|
|
20
|
+
=== Installation
|
21
|
+
|
22
|
+
gem install songkick-oauth2-provider
|
23
|
+
|
24
|
+
|
20
25
|
=== A note on versioning
|
21
26
|
|
22
27
|
This library is based on draft-10[http://tools.ietf.org/html/draft-ietf-oauth-v2-10],
|
@@ -108,6 +113,8 @@ the gem's migrations that have not yet been applied to your database.
|
|
108
113
|
-> 0.0009s
|
109
114
|
...
|
110
115
|
|
116
|
+
To rollback migrations, use <tt>Songkick::OAuth2::Model::Schema.rollback</tt>.
|
117
|
+
|
111
118
|
|
112
119
|
=== Model Mixins
|
113
120
|
|
@@ -196,14 +203,14 @@ the client:
|
|
196
203
|
__send__ method, '/oauth/authorize' do
|
197
204
|
@owner = User.find_by_id(session[:user_id])
|
198
205
|
@oauth2 = Songkick::OAuth2::Provider.parse(@owner, env)
|
199
|
-
|
206
|
+
|
200
207
|
if @oauth2.redirect?
|
201
208
|
redirect @oauth2.redirect_uri, @oauth2.response_status
|
202
209
|
end
|
203
|
-
|
210
|
+
|
204
211
|
headers @oauth2.response_headers
|
205
212
|
status @oauth2.response_status
|
206
|
-
|
213
|
+
|
207
214
|
if body = @oauth2.response_body
|
208
215
|
body
|
209
216
|
elsif @oauth2.valid?
|
@@ -228,7 +235,7 @@ You may also want to use scopes to provide granular access to your domain using
|
|
228
235
|
asked for so you can display them to the user:
|
229
236
|
|
230
237
|
<p>The application <%= @oauth2.client.name %> wants the following permissions:</p>
|
231
|
-
|
238
|
+
|
232
239
|
<ul>
|
233
240
|
<% @oauth2.scopes.each do |scope| %>
|
234
241
|
<li><%= PERMISSION_UI_STRINGS[scope] %></li>
|
@@ -251,7 +258,7 @@ user checks a box before posting a form to indicate their intent:
|
|
251
258
|
post '/oauth/allow' do
|
252
259
|
@user = User.find_by_id(session[:user_id])
|
253
260
|
@auth = Songkick::OAuth2::Provider::Authorization.new(@user, params)
|
254
|
-
|
261
|
+
|
255
262
|
if params['allow'] == '1'
|
256
263
|
@auth.grant_access!
|
257
264
|
else
|
@@ -311,10 +318,10 @@ requested scopes.
|
|
311
318
|
Songkick::OAuth2::Provider.handle_assertions 'https://graph.facebook.com/me' do |client, assertion, scopes|
|
312
319
|
facebook = URI.parse('https://graph.facebook.com/me?access_token=' + assertion)
|
313
320
|
response = Net::HTTP.get_response(facebook)
|
314
|
-
|
321
|
+
|
315
322
|
user_data = JSON.parse(response.body)
|
316
323
|
account = User.from_facebook_data(user_data)
|
317
|
-
|
324
|
+
|
318
325
|
account.grant_access!(client, :scopes => scopes)
|
319
326
|
end
|
320
327
|
|
@@ -337,10 +344,10 @@ simple, for example a call to get a user's notes:
|
|
337
344
|
get '/user/:username/notes' do
|
338
345
|
user = User.find_by_username(params[:username])
|
339
346
|
token = Songkick::OAuth2::Provider.access_token(user, ['read_notes'], env)
|
340
|
-
|
347
|
+
|
341
348
|
headers token.response_headers
|
342
349
|
status token.response_status
|
343
|
-
|
350
|
+
|
344
351
|
if token.valid?
|
345
352
|
JSON.unparse('notes' => user.notes)
|
346
353
|
else
|
@@ -357,10 +364,10 @@ determine whether to serve the request or not.
|
|
357
364
|
|
358
365
|
It is also common to provide a dynamic resource for getting some basic data
|
359
366
|
about a user by supplying their access token. This can be done by passing
|
360
|
-
<tt
|
367
|
+
<tt>:implicit</tt> as the resource owner:
|
361
368
|
|
362
369
|
get '/me' do
|
363
|
-
token = Songkick::OAuth2::Provider.access_token(
|
370
|
+
token = Songkick::OAuth2::Provider.access_token(:implicit, [], env)
|
364
371
|
if token.valid?
|
365
372
|
JSON.unparse('username' => token.owner.username)
|
366
373
|
else
|
data/example/README.rdoc
CHANGED
data/example/application.rb
CHANGED
@@ -65,14 +65,14 @@ end
|
|
65
65
|
__send__ method, '/oauth/authorize' do
|
66
66
|
@user = User.find_by_id(session[:user_id])
|
67
67
|
@oauth2 = Songkick::OAuth2::Provider.parse(@user, env)
|
68
|
-
|
68
|
+
|
69
69
|
if @oauth2.redirect?
|
70
70
|
redirect @oauth2.redirect_uri, @oauth2.response_status
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
headers @oauth2.response_headers
|
74
74
|
status @oauth2.response_status
|
75
|
-
|
75
|
+
|
76
76
|
if body = @oauth2.response_body
|
77
77
|
body
|
78
78
|
elsif @oauth2.valid?
|
@@ -105,10 +105,10 @@ end
|
|
105
105
|
# Domain API
|
106
106
|
|
107
107
|
get '/me' do
|
108
|
-
authorization = Songkick::OAuth2::Provider.access_token(
|
108
|
+
authorization = Songkick::OAuth2::Provider.access_token(:implicit, [], env)
|
109
109
|
headers authorization.response_headers
|
110
110
|
status authorization.response_status
|
111
|
-
|
111
|
+
|
112
112
|
if authorization.valid?
|
113
113
|
user = authorization.owner
|
114
114
|
JSON.unparse('username' => user.username)
|
@@ -141,15 +141,15 @@ helpers do
|
|
141
141
|
def verify_access(scope)
|
142
142
|
user = User.find_by_username(params[:username])
|
143
143
|
token = Songkick::OAuth2::Provider.access_token(user, [scope.to_s], env)
|
144
|
-
|
144
|
+
|
145
145
|
headers token.response_headers
|
146
146
|
status token.response_status
|
147
|
-
|
147
|
+
|
148
148
|
return ERROR_RESPONSE unless token.valid?
|
149
|
-
|
149
|
+
|
150
150
|
yield user
|
151
151
|
end
|
152
|
-
|
152
|
+
|
153
153
|
#================================================================
|
154
154
|
# Return the full app domain
|
155
155
|
def host
|
data/example/schema.rb
CHANGED
data/example/views/authorize.erb
CHANGED
@@ -15,12 +15,12 @@
|
|
15
15
|
<input type="hidden" name="<%= key %>" value="<%= value %>">
|
16
16
|
<% end %>
|
17
17
|
<input type="hidden" name="user_id" value="<%= @user.id %>">
|
18
|
-
|
18
|
+
|
19
19
|
<fieldset>
|
20
20
|
<input type="checkbox" name="allow" id="allow" value="1">
|
21
21
|
<label for="allow">Allow this application</label>
|
22
22
|
</fieldset>
|
23
|
-
|
23
|
+
|
24
24
|
<fieldset>
|
25
25
|
<input type="submit" value="Go!">
|
26
26
|
</fieldset>
|
data/example/views/layout.erb
CHANGED
@@ -6,20 +6,20 @@
|
|
6
6
|
<link rel="stylesheet" href="/style.css">
|
7
7
|
</head>
|
8
8
|
<body>
|
9
|
-
|
9
|
+
|
10
10
|
<div class="header"><div class="sub">
|
11
11
|
<h1>OAuth 2.0 demo</h1>
|
12
12
|
<h2>Steal my notes, why don’t you</h2>
|
13
13
|
</div></div>
|
14
|
-
|
14
|
+
|
15
15
|
<div class="content"><div class="sub">
|
16
16
|
<%= yield %>
|
17
17
|
</div></div>
|
18
|
-
|
18
|
+
|
19
19
|
<div class="footer"><div class="sub">
|
20
20
|
<p>Copyright © 2010 Songkick.com</p>
|
21
21
|
</div></div>
|
22
|
-
|
22
|
+
|
23
23
|
</body>
|
24
24
|
</html>
|
25
25
|
|
data/example/views/login.erb
CHANGED
@@ -7,12 +7,12 @@
|
|
7
7
|
<% @oauth2.params.each do |key, value| %>
|
8
8
|
<input type="hidden" name="<%= key %>" value="<%= value %>">
|
9
9
|
<% end %>
|
10
|
-
|
10
|
+
|
11
11
|
<fieldset>
|
12
12
|
<label for="username">Username</label>
|
13
13
|
<input type="text" name="username" id="username">
|
14
14
|
</fieldset>
|
15
|
-
|
15
|
+
|
16
16
|
<fieldset>
|
17
17
|
<input type="submit" value="Sign in">
|
18
18
|
</fieldset>
|
data/example/views/new_user.erb
CHANGED
@@ -3,34 +3,36 @@ require 'active_record'
|
|
3
3
|
module Songkick
|
4
4
|
module OAuth2
|
5
5
|
module Model
|
6
|
+
autoload :Helpers, ROOT + '/oauth2/model/helpers'
|
6
7
|
autoload :ClientOwner, ROOT + '/oauth2/model/client_owner'
|
7
8
|
autoload :ResourceOwner, ROOT + '/oauth2/model/resource_owner'
|
8
9
|
autoload :Hashing, ROOT + '/oauth2/model/hashing'
|
9
10
|
autoload :Authorization, ROOT + '/oauth2/model/authorization'
|
10
11
|
autoload :Client, ROOT + '/oauth2/model/client'
|
11
|
-
|
12
|
+
|
12
13
|
Schema = Songkick::OAuth2::Schema
|
13
|
-
|
14
|
+
|
14
15
|
DUPLICATE_RECORD_ERRORS = [
|
15
16
|
/^Mysql::Error:\s+Duplicate\s+entry\b/,
|
16
17
|
/^PG::Error:\s+ERROR:\s+duplicate\s+key\b/,
|
17
18
|
/\bConstraintException\b/
|
18
19
|
]
|
19
|
-
|
20
|
+
|
20
21
|
# ActiveRecord::RecordNotUnique was introduced in Rails 3.0 so referring
|
21
22
|
# to it while running earlier versions will raise an error. The above
|
22
23
|
# error strings should match PostgreSQL, MySQL and SQLite errors on
|
23
24
|
# Rails 2. If you're running a different adapter, add a suitable regex to
|
24
25
|
# the list:
|
25
|
-
#
|
26
|
+
#
|
26
27
|
# Songkick::OAuth2::Model::DUPLICATE_RECORD_ERRORS << /DB2 found a dup/
|
27
|
-
#
|
28
|
+
#
|
28
29
|
def self.duplicate_record_error?(error)
|
29
30
|
error.class.name == 'ActiveRecord::RecordNotUnique' or
|
30
31
|
DUPLICATE_RECORD_ERRORS.any? { |re| re =~ error.message }
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
def self.find_access_token(access_token)
|
35
|
+
return nil if access_token.nil?
|
34
36
|
Authorization.find_by_access_token_hash(Songkick::OAuth2.hashify(access_token))
|
35
37
|
end
|
36
38
|
end
|
@@ -1,64 +1,64 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
module Model
|
4
|
-
|
4
|
+
|
5
5
|
class Authorization < ActiveRecord::Base
|
6
6
|
self.table_name = :oauth2_authorizations
|
7
|
-
|
7
|
+
|
8
8
|
belongs_to :oauth2_resource_owner, :polymorphic => true
|
9
9
|
alias :owner :oauth2_resource_owner
|
10
10
|
alias :owner= :oauth2_resource_owner=
|
11
|
-
|
11
|
+
|
12
12
|
belongs_to :client, :class_name => 'Songkick::OAuth2::Model::Client'
|
13
|
-
|
13
|
+
|
14
14
|
validates_presence_of :client, :owner
|
15
|
-
|
15
|
+
|
16
16
|
validates_uniqueness_of :code, :scope => :client_id, :allow_nil => true
|
17
17
|
validates_uniqueness_of :refresh_token_hash, :scope => :client_id, :allow_nil => true
|
18
18
|
validates_uniqueness_of :access_token_hash, :allow_nil => true
|
19
|
-
|
19
|
+
|
20
20
|
attr_accessible nil
|
21
|
-
|
21
|
+
|
22
22
|
class << self
|
23
23
|
private :create, :new
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
extend Hashing
|
27
27
|
hashes_attributes :access_token, :refresh_token
|
28
|
-
|
28
|
+
|
29
29
|
def self.create_code(client)
|
30
30
|
Songkick::OAuth2.generate_id do |code|
|
31
|
-
client.authorizations
|
31
|
+
Helpers.count(client.authorizations, :code => code).zero?
|
32
32
|
end
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def self.create_access_token
|
36
36
|
Songkick::OAuth2.generate_id do |token|
|
37
37
|
hash = Songkick::OAuth2.hashify(token)
|
38
|
-
count(
|
38
|
+
Helpers.count(self, :access_token_hash => hash).zero?
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def self.create_refresh_token(client)
|
43
43
|
Songkick::OAuth2.generate_id do |refresh_token|
|
44
44
|
hash = Songkick::OAuth2.hashify(refresh_token)
|
45
|
-
client.authorizations
|
45
|
+
Helpers.count(client.authorizations, :refresh_token_hash => hash).zero?
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def self.for(owner, client, attributes = {})
|
50
50
|
return nil unless owner and client
|
51
|
-
|
51
|
+
|
52
52
|
unless client.is_a?(Client)
|
53
53
|
raise ArgumentError, "The argument should be a #{Client}, instead it was a #{client.class}"
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
instance = owner.oauth2_authorization_for(client) ||
|
57
57
|
new do |authorization|
|
58
58
|
authorization.owner = owner
|
59
59
|
authorization.client = client
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
case attributes[:response_type]
|
63
63
|
when CODE
|
64
64
|
instance.code ||= create_code(client)
|
@@ -70,19 +70,19 @@ module Songkick
|
|
70
70
|
instance.access_token ||= create_access_token
|
71
71
|
instance.refresh_token ||= create_refresh_token(client)
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
if attributes[:duration]
|
75
75
|
instance.expires_at = Time.now + attributes[:duration].to_i
|
76
76
|
else
|
77
77
|
instance.expires_at = nil
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
scopes = instance.scopes + (attributes[:scopes] || [])
|
81
81
|
scopes += attributes[:scope].split(/\s+/) if attributes[:scope]
|
82
82
|
instance.scope = scopes.empty? ? nil : scopes.entries.join(' ')
|
83
|
-
|
83
|
+
|
84
84
|
instance.save && instance
|
85
|
-
|
85
|
+
|
86
86
|
rescue Object => error
|
87
87
|
if Model.duplicate_record_error?(error)
|
88
88
|
retry
|
@@ -90,47 +90,47 @@ module Songkick
|
|
90
90
|
raise error
|
91
91
|
end
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
def exchange!
|
95
95
|
self.code = nil
|
96
96
|
self.access_token = self.class.create_access_token
|
97
97
|
self.refresh_token = nil
|
98
98
|
save!
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
def expired?
|
102
102
|
return false unless expires_at
|
103
103
|
expires_at < Time.now
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
def expires_in
|
107
107
|
expires_at && (expires_at - Time.now).ceil
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
def generate_code
|
111
111
|
self.code ||= self.class.create_code(client)
|
112
112
|
save && code
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
def generate_access_token
|
116
116
|
self.access_token ||= self.class.create_access_token
|
117
117
|
save && access_token
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
def grants_access?(user, *scopes)
|
121
121
|
not expired? and user == owner and in_scope?(scopes)
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
def in_scope?(request_scope)
|
125
125
|
[*request_scope].all?(&scopes.method(:include?))
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
def scopes
|
129
129
|
scopes = scope ? scope.split(/\s+/) : []
|
130
130
|
Set.new(scopes)
|
131
131
|
end
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
end
|
135
135
|
end
|
136
136
|
end
|