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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/History.txt +7 -0
  3. data/README.rdoc +18 -11
  4. data/example/README.rdoc +1 -1
  5. data/example/application.rb +9 -9
  6. data/example/schema.rb +1 -1
  7. data/example/views/authorize.erb +2 -2
  8. data/example/views/layout.erb +4 -4
  9. data/example/views/login.erb +2 -2
  10. data/example/views/new_client.erb +1 -1
  11. data/example/views/new_user.erb +1 -1
  12. data/lib/songkick/oauth2/model.rb +8 -6
  13. data/lib/songkick/oauth2/model/authorization.rb +31 -31
  14. data/lib/songkick/oauth2/model/client.rb +15 -15
  15. data/lib/songkick/oauth2/model/client_owner.rb +2 -2
  16. data/lib/songkick/oauth2/model/hashing.rb +3 -3
  17. data/lib/songkick/oauth2/model/helpers.rb +16 -0
  18. data/lib/songkick/oauth2/model/resource_owner.rb +4 -4
  19. data/lib/songkick/oauth2/provider.rb +16 -16
  20. data/lib/songkick/oauth2/provider/access_token.rb +20 -15
  21. data/lib/songkick/oauth2/provider/authorization.rb +43 -42
  22. data/lib/songkick/oauth2/provider/error.rb +4 -4
  23. data/lib/songkick/oauth2/provider/exchange.rb +46 -46
  24. data/lib/songkick/oauth2/router.rb +13 -13
  25. data/lib/songkick/oauth2/schema.rb +11 -3
  26. data/lib/songkick/oauth2/schema/20120828112156_songkick_oauth2_schema_original_schema.rb +2 -2
  27. data/lib/songkick/oauth2/schema/20121024180930_songkick_oauth2_schema_add_authorization_index.rb +3 -3
  28. data/lib/songkick/oauth2/schema/20121025180447_songkick_oauth2_schema_add_unique_indexes.rb +7 -7
  29. data/spec/request_helpers.rb +25 -21
  30. data/spec/songkick/oauth2/model/authorization_spec.rb +56 -56
  31. data/spec/songkick/oauth2/model/client_spec.rb +9 -9
  32. data/spec/songkick/oauth2/model/helpers_spec.rb +26 -0
  33. data/spec/songkick/oauth2/model/resource_owner_spec.rb +13 -13
  34. data/spec/songkick/oauth2/provider/access_token_spec.rb +32 -20
  35. data/spec/songkick/oauth2/provider/authorization_spec.rb +73 -62
  36. data/spec/songkick/oauth2/provider/exchange_spec.rb +72 -72
  37. data/spec/songkick/oauth2/provider_spec.rb +101 -101
  38. data/spec/spec_helper.rb +5 -3
  39. data/spec/test_app/helper.rb +11 -7
  40. data/spec/test_app/provider/application.rb +12 -12
  41. data/spec/test_app/provider/views/authorize.erb +2 -2
  42. metadata +71 -93
@@ -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
@@ -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
@@ -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>nil</tt> as the resource owner:
367
+ <tt>:implicit</tt> as the resource owner:
361
368
 
362
369
  get '/me' do
363
- token = Songkick::OAuth2::Provider.access_token(nil, [], env)
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
@@ -4,7 +4,7 @@ To get up and running:
4
4
 
5
5
  # in parent directory
6
6
  bundle install
7
-
7
+
8
8
  cd example/
9
9
  ruby schema.rb
10
10
  rackup config.ru
@@ -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(nil, [], env)
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
@@ -10,7 +10,7 @@ ActiveRecord::Schema.define do |version|
10
10
  t.timestamps
11
11
  t.string :username
12
12
  end
13
-
13
+
14
14
  create_table :notes, :force => true do |t|
15
15
  t.timestamps
16
16
  t.belongs_to :user
@@ -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>
@@ -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&rsquo;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 &copy; 2010 Songkick.com</p>
21
21
  </div></div>
22
-
22
+
23
23
  </body>
24
24
  </html>
25
25
 
@@ -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>
@@ -17,7 +17,7 @@
17
17
  <label for="redirect_uri">Callback URI</label>
18
18
  <input type="text" name="redirect_uri" id="redirect_uri">
19
19
  </fieldset>
20
-
20
+
21
21
  <fieldset>
22
22
  <input type="submit" value="Register">
23
23
  </fieldset>
@@ -13,7 +13,7 @@
13
13
  <label for="name">Username</label>
14
14
  <input type="text" name="username" id="username">
15
15
  </fieldset>
16
-
16
+
17
17
  <fieldset>
18
18
  <input type="submit" value="Register">
19
19
  </fieldset>
@@ -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.count(:conditions => {:code => code}).zero?
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(:conditions => {:access_token_hash => hash}).zero?
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.count(:conditions => {:refresh_token_hash => hash}).zero?
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