songkick-oauth2-provider 0.10.2 → 0.10.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|