songkick-oauth2-provider 0.10.0 → 0.10.1

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/History.txt ADDED
@@ -0,0 +1,12 @@
1
+ === 0.10.1 / 2012-10-29
2
+
3
+ * Add uniqueness constraints to the database for various Authorization properties
4
+ * Make Authorization.new() and Authorization.create() private
5
+ * Disallow client_secret from being sent in a query string
6
+ * Use SecureRandom where available to generate tokens
7
+
8
+
9
+ === 0.10.0 / 2012-08-27
10
+
11
+ * Initial release, implements http://tools.ietf.org/html/draft-ietf-oauth-v2-10
12
+
@@ -1,11 +1,12 @@
1
- dir = File.expand_path('..', __FILE__)
2
- $:.unshift(dir + '/../lib')
3
- $:.unshift(dir)
1
+ require 'rubygems'
2
+ require 'bundler/setup'
4
3
 
4
+ require 'active_record'
5
5
  require 'songkick/oauth2/provider'
6
6
  Songkick::OAuth2::Provider.realm = 'Notes App'
7
7
 
8
- require 'models/connection'
9
- require 'models/user'
10
- require 'models/note'
8
+ dir = File.expand_path('..', __FILE__)
9
+ require dir + '/models/connection'
10
+ require dir + '/models/user'
11
+ require dir + '/models/note'
11
12
 
@@ -1,9 +1,9 @@
1
- require 'rubygems'
2
- require 'active_record'
1
+ require 'fileutils'
3
2
 
4
- dir = File.expand_path(File.dirname(__FILE__))
3
+ dbfile = File.expand_path('../../db/notes.sqlite3', __FILE__)
4
+ FileUtils.mkdir_p(File.dirname(dbfile))
5
5
 
6
6
  ActiveRecord::Base.establish_connection(
7
7
  :adapter => 'sqlite3',
8
- :database => dir + '/../db/notes.sqlite3')
8
+ :database => dbfile)
9
9
 
data/example/schema.rb CHANGED
@@ -1,13 +1,9 @@
1
- dir = File.expand_path('..', __FILE__)
2
- $:.unshift(dir + '/../lib')
3
-
4
1
  require 'rubygems'
5
- require 'songkick/oauth2/provider'
6
- require 'fileutils'
2
+ require 'bundler/setup'
7
3
 
8
- require dir + '/models/connection'
9
-
10
- FileUtils.mkdir_p(dir + '/db')
4
+ require 'songkick/oauth2/provider'
5
+ require 'active_record'
6
+ require File.expand_path('../models/connection', __FILE__)
11
7
 
12
8
  ActiveRecord::Schema.define do |version|
13
9
  create_table :users, :force => true do |t|
@@ -11,6 +11,25 @@ module Songkick
11
11
 
12
12
  Schema = Songkick::OAuth2::Schema
13
13
 
14
+ DUPLICATE_RECORD_ERRORS = [
15
+ /^Mysql::Error:\s+Duplicate\s+entry\b/,
16
+ /^PG::Error:\s+ERROR:\s+duplicate\s+key\b/,
17
+ /\bConstraintException\b/
18
+ ]
19
+
20
+ # ActiveRecord::RecordNotUnique was introduced in Rails 3.0 so referring
21
+ # to it while running earlier versions will raise an error. The above
22
+ # error strings should match PostgreSQL, MySQL and SQLite errors on
23
+ # Rails 2. If you're running a different adapter, add a suitable regex to
24
+ # the list:
25
+ #
26
+ # Songkick::OAuth2::Model::DUPLICATE_RECORD_ERRORS << /DB2 found a dup/
27
+ #
28
+ def self.duplicate_record_error?(error)
29
+ error.class.name == 'ActiveRecord::RecordNotUnique' or
30
+ DUPLICATE_RECORD_ERRORS.any? { |re| re =~ error.message }
31
+ end
32
+
14
33
  def self.find_access_token(access_token)
15
34
  Authorization.find_by_access_token_hash(Songkick::OAuth2.hashify(access_token))
16
35
  end
@@ -19,14 +19,13 @@ module Songkick
19
19
 
20
20
  attr_accessible nil
21
21
 
22
+ class << self
23
+ private :create, :new
24
+ end
25
+
22
26
  extend Hashing
23
27
  hashes_attributes :access_token, :refresh_token
24
28
 
25
- def self.for(resource_owner, client)
26
- return nil unless resource_owner and client
27
- resource_owner.oauth2_authorizations.find_by_client_id(client.id)
28
- end
29
-
30
29
  def self.create_code(client)
31
30
  Songkick::OAuth2.generate_id do |code|
32
31
  client.authorizations.count(:conditions => {:code => code}).zero?
@@ -47,23 +46,29 @@ module Songkick
47
46
  end
48
47
  end
49
48
 
50
- def self.for_response_type(response_type, attributes = {})
51
- instance = self.for(attributes[:owner], attributes[:client]) ||
49
+ def self.for(owner, client, attributes = {})
50
+ return nil unless owner and client
51
+
52
+ unless client.is_a?(Client)
53
+ raise ArgumentError, "The argument should be a #{Client}, instead it was a #{client.class}"
54
+ end
55
+
56
+ instance = owner.oauth2_authorization_for(client) ||
52
57
  new do |authorization|
53
- authorization.owner = attributes[:owner]
54
- authorization.client = attributes[:client]
58
+ authorization.owner = owner
59
+ authorization.client = client
55
60
  end
56
61
 
57
- case response_type
62
+ case attributes[:response_type]
58
63
  when CODE
59
- instance.code ||= create_code(attributes[:client])
64
+ instance.code ||= create_code(client)
60
65
  when TOKEN
61
66
  instance.access_token ||= create_access_token
62
- instance.refresh_token ||= create_refresh_token(attributes[:client])
67
+ instance.refresh_token ||= create_refresh_token(client)
63
68
  when CODE_AND_TOKEN
64
- instance.code = create_code(attributes[:client])
69
+ instance.code = create_code(client)
65
70
  instance.access_token ||= create_access_token
66
- instance.refresh_token ||= create_refresh_token(attributes[:client])
71
+ instance.refresh_token ||= create_refresh_token(client)
67
72
  end
68
73
 
69
74
  if attributes[:duration]
@@ -72,12 +77,18 @@ module Songkick
72
77
  instance.expires_at = nil
73
78
  end
74
79
 
75
- if attributes[:scope]
76
- scopes = instance.scopes + attributes[:scope].split(/\s+/)
77
- instance.scope = scopes.entries.join(' ')
78
- end
80
+ scopes = instance.scopes + (attributes[:scopes] || [])
81
+ scopes += attributes[:scope].split(/\s+/) if attributes[:scope]
82
+ instance.scope = scopes.entries.join(' ')
79
83
 
80
84
  instance.save && instance
85
+
86
+ rescue Object => error
87
+ if Model.duplicate_record_error?(error)
88
+ retry
89
+ else
90
+ raise error
91
+ end
81
92
  end
82
93
 
83
94
  def exchange!
@@ -1,5 +1,3 @@
1
- require 'bcrypt'
2
-
3
1
  module Songkick
4
2
  module OAuth2
5
3
  module Model
@@ -13,7 +11,7 @@ module Songkick
13
11
 
14
12
  has_many :authorizations, :class_name => 'Songkick::OAuth2::Model::Authorization', :dependent => :destroy
15
13
 
16
- validates_uniqueness_of :client_id
14
+ validates_uniqueness_of :client_id, :name
17
15
  validates_presence_of :name, :redirect_uri
18
16
  validate :check_format_of_redirect_uri
19
17
 
@@ -2,50 +2,20 @@ module Songkick
2
2
  module OAuth2
3
3
  module Model
4
4
 
5
- module AuthorizationAssociation
6
- def find_or_create_for_client(client)
7
- unless client.is_a?(Client)
8
- raise ArgumentError, "The argument should be a #{Client}, instead it was a #{client.class}"
9
- end
10
-
11
- # find_or_create_by_client_id does not work across AR versions
12
- authorization = find_by_client_id(client.id) || build
13
- authorization.client = client
14
- authorization.owner = owner
15
- authorization.save
16
- authorization
17
- end
18
-
19
- private
20
-
21
- def owner
22
- respond_to?(:proxy_association) ? proxy_association.owner : proxy_owner
23
- end
24
- end
25
-
26
5
  module ResourceOwner
27
6
  def self.included(klass)
28
7
  klass.has_many :oauth2_authorizations,
29
- :class_name => 'Songkick::OAuth2::Model::Authorization',
8
+ :class_name => Authorization.name,
30
9
  :as => :oauth2_resource_owner,
31
- :dependent => :destroy,
32
- :extend => AuthorizationAssociation
10
+ :dependent => :destroy
33
11
  end
34
12
 
35
13
  def grant_access!(client, options = {})
36
- authorization = oauth2_authorizations.find_or_create_for_client(client)
37
-
38
- if scopes = options[:scopes]
39
- scopes = authorization.scopes + scopes
40
- authorization.scope = scopes.entries.join(' ')
41
- end
42
-
43
- if duration = options[:duration]
44
- authorization.expires_at = Time.now + duration.to_i
45
- end
46
-
47
- authorization.save! if authorization.changed?
48
- authorization
14
+ Authorization.for(self, client, options)
15
+ end
16
+
17
+ def oauth2_authorization_for(client)
18
+ oauth2_authorizations.find_by_client_id(client.id)
49
19
  end
50
20
  end
51
21
 
@@ -1,6 +1,15 @@
1
+ require 'base64'
2
+ require 'bcrypt'
3
+ require 'cgi'
1
4
  require 'digest/sha1'
2
5
  require 'json'
3
6
  require 'logger'
7
+ require 'rack'
8
+
9
+ begin
10
+ require 'securerandom'
11
+ rescue LoadError
12
+ end
4
13
 
5
14
  module Songkick
6
15
  module OAuth2
@@ -12,7 +21,11 @@ module Songkick
12
21
  autoload :Schema, ROOT + '/oauth2/schema'
13
22
 
14
23
  def self.random_string
15
- rand(2 ** TOKEN_SIZE).to_s(36)
24
+ if defined? SecureRandom
25
+ SecureRandom.hex(TOKEN_SIZE / 8).to_i(16).to_s(36)
26
+ else
27
+ rand(2 ** TOKEN_SIZE).to_s(36)
28
+ end
16
29
  end
17
30
 
18
31
  def self.generate_id(&predicate)
@@ -63,6 +76,13 @@ module Songkick
63
76
  ACCESS_DENIED = 'access_denied'
64
77
 
65
78
  class Provider
79
+ EXPIRY_TIME = 3600
80
+
81
+ autoload :Authorization, ROOT + '/oauth2/provider/authorization'
82
+ autoload :Exchange, ROOT + '/oauth2/provider/exchange'
83
+ autoload :AccessToken, ROOT + '/oauth2/provider/access_token'
84
+ autoload :Error, ROOT + '/oauth2/provider/error'
85
+
66
86
  class << self
67
87
  attr_accessor :realm, :enforce_ssl
68
88
  end
@@ -109,14 +129,8 @@ module Songkick
109
129
  def self.access_token_from_request(*args)
110
130
  Router.access_token_from_request(*args)
111
131
  end
112
-
113
- EXPIRY_TIME = 3600
114
-
115
- autoload :Authorization, ROOT + '/oauth2/provider/authorization'
116
- autoload :Exchange, ROOT + '/oauth2/provider/exchange'
117
- autoload :AccessToken, ROOT + '/oauth2/provider/access_token'
118
- autoload :Error, ROOT + '/oauth2/provider/error'
119
132
  end
133
+
120
134
  end
121
135
  end
122
136
 
@@ -1,5 +1,3 @@
1
- require 'cgi'
2
-
3
1
  module Songkick
4
2
  module OAuth2
5
3
  class Provider
@@ -26,7 +24,7 @@ module Songkick
26
24
 
27
25
  return unless @owner and not @error
28
26
 
29
- @model = Model::Authorization.for(@owner, @client)
27
+ @model = @owner.oauth2_authorization_for(@client)
30
28
  return unless @model and @model.in_scope?(scopes) and not @model.expired?
31
29
 
32
30
  @authorized = true
@@ -50,11 +48,10 @@ module Songkick
50
48
  end
51
49
 
52
50
  def grant_access!(options = {})
53
- @model = Model::Authorization.for_response_type(@params[RESPONSE_TYPE],
54
- :owner => @owner,
55
- :client => @client,
56
- :scope => @scope,
57
- :duration => options[:duration])
51
+ @model = Model::Authorization.for(@owner, @client,
52
+ :response_type => @params[RESPONSE_TYPE],
53
+ :scope => @scope,
54
+ :duration => options[:duration])
58
55
 
59
56
  @code = @model.code
60
57
  @access_token = @model.access_token
@@ -62,7 +59,7 @@ module Songkick
62
59
  @expires_in = @model.expires_in
63
60
 
64
61
  unless @params[RESPONSE_TYPE] == CODE
65
- @expires_in = @model.expires_in
62
+ @expires_in = @model.expires_in
66
63
  end
67
64
 
68
65
  @authorized = true
@@ -1,6 +1,3 @@
1
- require 'base64'
2
- require 'rack'
3
-
4
1
  module Songkick
5
2
  module OAuth2
6
3
  class Router
@@ -68,12 +65,14 @@ module Songkick
68
65
  request = request_from(env)
69
66
 
70
67
  if Provider.enforce_ssl and not request.ssl?
71
- Provider::Error.new("must make requests using HTTPS")
68
+ Provider::Error.new('must make requests using HTTPS')
69
+ elsif request.GET['client_secret']
70
+ Provider::Error.new('must not send client credentials in the URI')
72
71
  end
73
72
  end
74
73
  end
75
74
 
76
75
  end
77
76
  end
78
- end
77
+ end
79
78
 
@@ -9,7 +9,7 @@ class SongkickOauth2SchemaOriginalSchema < ActiveRecord::Migration
9
9
  t.string :client_secret_hash
10
10
  t.string :redirect_uri
11
11
  end
12
- add_index :oauth2_clients, :client_id
12
+ add_index :oauth2_clients, [:client_id]
13
13
 
14
14
  create_table :oauth2_authorizations do |t|
15
15
  t.timestamps
@@ -0,0 +1,14 @@
1
+ class SongkickOauth2SchemaAddAuthorizationIndex < ActiveRecord::Migration
2
+ INDEX_NAME = 'index_owner_client_pairs'
3
+
4
+ def self.up
5
+ remove_index :oauth2_authorizations, [:client_id, :access_token_hash]
6
+ add_index :oauth2_authorizations, [:client_id, :oauth2_resource_owner_type, :oauth2_resource_owner_id], :name => INDEX_NAME, :unique => true
7
+ end
8
+
9
+ def self.down
10
+ remove_index :oauth2_authorizations, INDEX_NAME
11
+ add_index :oauth2_authorizations, [:client_id, :access_token_hash]
12
+ end
13
+ end
14
+
@@ -0,0 +1,32 @@
1
+ class SongkickOauth2SchemaAddUniqueIndexes < ActiveRecord::Migration
2
+ FIELDS = [:code, :refresh_token_hash]
3
+
4
+ def self.up
5
+ FIELDS.each do |field|
6
+ remove_index :oauth2_authorizations, [:client_id, field]
7
+ add_index :oauth2_authorizations, [:client_id, field], :unique => true
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_clients :oauth2_clients, [:name]
30
+ end
31
+ end
32
+
data/spec/factories.rb CHANGED
@@ -19,9 +19,3 @@ Factory.define :client, :class => Songkick::OAuth2::Model::Client do |c|
19
19
  c.redirect_uri 'https://client.example.com/cb'
20
20
  end
21
21
 
22
- Factory.define :authorization, :class => Songkick::OAuth2::Model::Authorization do |ac|
23
- ac.client Factory(:client)
24
- ac.code { Songkick::OAuth2.random_string }
25
- ac.expires_at nil
26
- end
27
-
@@ -1,9 +1,12 @@
1
1
  module RequestHelpers
2
2
  require 'net/http'
3
3
 
4
+ def querystring(params)
5
+ params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
6
+ end
7
+
4
8
  def get(query_params)
5
- qs = params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
6
- uri = URI.parse('http://localhost:8000/authorize?' + qs)
9
+ uri = URI.parse('http://localhost:8000/authorize?' + querystring(query_params))
7
10
  Net::HTTP.get_response(uri)
8
11
  end
9
12
 
@@ -16,8 +19,11 @@ module RequestHelpers
16
19
  Net::HTTP.post_form(URI.parse(url), query_params)
17
20
  end
18
21
 
19
- def post(query_params)
20
- Net::HTTP.post_form(URI.parse('http://localhost:8000/authorize'), query_params)
22
+ def post(body_params, query_params = {})
23
+ uri = URI.parse('http://localhost:8000/authorize?' + querystring(query_params))
24
+ Net::HTTP.start(uri.host, uri.port) do |http|
25
+ http.post(uri.path + '?' + uri.query.to_s, querystring(body_params))
26
+ end
21
27
  end
22
28
 
23
29
  def validate_response(response, status, body)
@@ -5,9 +5,10 @@ describe Songkick::OAuth2::Model::Authorization do
5
5
  let(:impostor) { Factory :client }
6
6
  let(:owner) { Factory :owner }
7
7
  let(:user) { Factory :owner }
8
+ let(:tester) { Factory(:owner) }
8
9
 
9
10
  let(:authorization) do
10
- create_authorization(:owner => owner, :client => client)
11
+ create_authorization(:owner => tester, :client => client)
11
12
  end
12
13
 
13
14
  it "is vaid" do
@@ -32,7 +33,7 @@ describe Songkick::OAuth2::Model::Authorization do
32
33
  :access_token => 'existing_access_token')
33
34
 
34
35
  create_authorization(
35
- :owner => owner,
36
+ :owner => user,
36
37
  :client => client,
37
38
  :code => 'existing_code')
38
39
 
@@ -110,6 +111,27 @@ describe Songkick::OAuth2::Model::Authorization do
110
111
  Songkick::OAuth2::Model::Authorization.create_refresh_token(impostor).should == 'existing_refresh_token'
111
112
  end
112
113
  end
114
+
115
+ describe "duplicate records" do
116
+ it "raises an error if a duplicate authorization is created" do
117
+ lambda {
118
+ authorization = Songkick::OAuth2::Model::Authorization.__send__(:new)
119
+ authorization.owner = user
120
+ authorization.client = client
121
+ authorization.save
122
+ }.should raise_error
123
+ end
124
+
125
+ it "finds an existing record after a race" do
126
+ user.stub(:oauth2_authorization_for) do
127
+ user.unstub(:oauth2_authorization_for)
128
+ raise TypeError, 'Mysql::Error: Duplicate entry'
129
+ end
130
+ authorization = Songkick::OAuth2::Model::Authorization.for(user, client)
131
+ authorization.owner.should == user
132
+ authorization.client.should == client
133
+ end
134
+ end
113
135
  end
114
136
 
115
137
  describe "#exchange!" do
@@ -150,7 +172,7 @@ describe Songkick::OAuth2::Model::Authorization do
150
172
 
151
173
  describe "#grants_access?" do
152
174
  it "returns true given the right user" do
153
- authorization.grants_access?(owner).should be_true
175
+ authorization.grants_access?(tester).should be_true
154
176
  end
155
177
 
156
178
  it "returns false given the wrong user" do
@@ -161,7 +183,7 @@ describe Songkick::OAuth2::Model::Authorization do
161
183
  before { authorization.expires_at = 2.days.ago }
162
184
 
163
185
  it "returns false in all cases" do
164
- authorization.grants_access?(owner).should be_false
186
+ authorization.grants_access?(tester).should be_false
165
187
  authorization.grants_access?(user).should be_false
166
188
  end
167
189
  end
@@ -184,23 +206,23 @@ describe Songkick::OAuth2::Model::Authorization do
184
206
 
185
207
  describe "#grants_access?" do
186
208
  it "returns true given the right user and all authorization scopes" do
187
- authorization.grants_access?(owner, 'foo', 'bar').should be_true
209
+ authorization.grants_access?(tester, 'foo', 'bar').should be_true
188
210
  end
189
211
 
190
212
  it "returns true given the right user and some authorization scopes" do
191
- authorization.grants_access?(owner, 'bar').should be_true
213
+ authorization.grants_access?(tester, 'bar').should be_true
192
214
  end
193
215
 
194
216
  it "returns false given the right user and some unauthorization scopes" do
195
- authorization.grants_access?(owner, 'foo', 'bar', 'qux').should be_false
217
+ authorization.grants_access?(tester, 'foo', 'bar', 'qux').should be_false
196
218
  end
197
219
 
198
220
  it "returns false given an unauthorized scope" do
199
- authorization.grants_access?(owner, 'qux').should be_false
221
+ authorization.grants_access?(tester, 'qux').should be_false
200
222
  end
201
223
 
202
224
  it "returns true given the right user" do
203
- authorization.grants_access?(owner).should be_true
225
+ authorization.grants_access?(tester).should be_true
204
226
  end
205
227
 
206
228
  it "returns false given the wrong user" do
@@ -4,7 +4,7 @@ describe Songkick::OAuth2::Model::Client do
4
4
  before do
5
5
  @client = Songkick::OAuth2::Model::Client.create(:name => 'App', :redirect_uri => 'http://example.com/cb')
6
6
  @owner = Factory(:owner)
7
- Factory(:authorization, :client => @client, :owner => @owner)
7
+ Songkick::OAuth2::Model::Authorization.for(@owner, @client)
8
8
  end
9
9
 
10
10
  it "is valid" do
@@ -12,7 +12,7 @@ describe Songkick::OAuth2::Model::ResourceOwner do
12
12
  end
13
13
 
14
14
  it "creates an authorization between the owner and the client" do
15
- authorization = Songkick::OAuth2::Model::Authorization.new
15
+ authorization = Songkick::OAuth2::Model::Authorization.__send__(:new)
16
16
  Songkick::OAuth2::Model::Authorization.should_receive(:new).and_return(authorization)
17
17
  @owner.grant_access!(@client)
18
18
  end
@@ -45,7 +45,7 @@ describe Songkick::OAuth2::Model::ResourceOwner do
45
45
 
46
46
  describe "when there is an existing authorization" do
47
47
  before do
48
- @authorization = Factory(:authorization, :owner => @owner, :client => @client)
48
+ @authorization = create_authorization(:owner => @owner, :client => @client)
49
49
  end
50
50
 
51
51
  it "does not create a new one" do
@@ -80,7 +80,7 @@ describe Songkick::OAuth2::Model::ResourceOwner do
80
80
  end
81
81
 
82
82
  it "destroys its authorizations on destroy" do
83
- Factory(:authorization, :owner => @owner, :client => @client)
83
+ Songkick::OAuth2::Model::Authorization.for(@owner, @client)
84
84
  @owner.destroy
85
85
  Songkick::OAuth2::Model::Authorization.count.should be_zero
86
86
  end
@@ -5,13 +5,15 @@ describe Songkick::OAuth2::Provider::AccessToken do
5
5
  @alice = TestApp::User['Alice']
6
6
  @bob = TestApp::User['Bob']
7
7
 
8
- Factory(:authorization,
8
+ create_authorization(
9
9
  :owner => @alice,
10
+ :client => Factory(:client),
10
11
  :scope => 'profile',
11
12
  :access_token => 'sesame')
12
13
 
13
- @authorization = Factory(:authorization,
14
+ @authorization = create_authorization(
14
15
  :owner => @bob,
16
+ :client => Factory(:client),
15
17
  :scope => 'profile',
16
18
  :access_token => 'magic-key')
17
19
 
@@ -156,7 +156,7 @@ describe Songkick::OAuth2::Provider::Authorization do
156
156
  describe "#grant_access!" do
157
157
  describe "when there is an existing authorization with no code" do
158
158
  before do
159
- @model = Factory(:authorization,
159
+ @model = create_authorization(
160
160
  :owner => resource_owner,
161
161
  :client => @client,
162
162
  :code => nil)
@@ -172,7 +172,7 @@ describe Songkick::OAuth2::Provider::Authorization do
172
172
 
173
173
  describe "when there is an existing authorization with scopes" do
174
174
  before do
175
- @model = Factory(:authorization,
175
+ @model = create_authorization(
176
176
  :owner => resource_owner,
177
177
  :client => @client,
178
178
  :code => nil,
@@ -190,7 +190,7 @@ describe Songkick::OAuth2::Provider::Authorization do
190
190
 
191
191
  describe "when there is an existing expired authorization" do
192
192
  before do
193
- @model = Factory(:authorization,
193
+ @model = create_authorization(
194
194
  :owner => resource_owner,
195
195
  :client => @client,
196
196
  :expires_at => 2.months.ago,
@@ -5,7 +5,7 @@ describe Songkick::OAuth2::Provider::Exchange do
5
5
  @client = Factory(:client)
6
6
  @alice = TestApp::User['Alice']
7
7
  @bob = TestApp::User['Bob']
8
- @authorization = Factory(:authorization, :client => @client, :owner => @bob, :scope => 'foo bar')
8
+ @authorization = create_authorization(:client => @client, :owner => @bob, :code => 'a_fake_code', :scope => 'foo bar')
9
9
  Songkick::OAuth2.stub(:random_string).and_return('random_string')
10
10
  end
11
11
 
@@ -321,11 +321,11 @@ describe Songkick::OAuth2::Provider::Exchange do
321
321
 
322
322
  describe "using refresh_token grant type" do
323
323
  before do
324
- @refresher = Factory(:authorization, :client => @client,
325
- :owner => @bob,
326
- :scope => 'foo bar',
327
- :code => nil,
328
- :refresh_token => 'roflscale')
324
+ @refresher = create_authorization(:client => @client,
325
+ :owner => @alice,
326
+ :scope => 'foo bar',
327
+ :code => nil,
328
+ :refresh_token => 'roflscale')
329
329
  end
330
330
 
331
331
  let(:params) { { 'client_id' => @client.client_id,
@@ -322,7 +322,11 @@ describe Songkick::OAuth2::Provider do
322
322
  describe "access token request" do
323
323
  before do
324
324
  @client = Factory(:client)
325
- @authorization = Factory(:authorization, :client => @client, :owner => @owner, :expires_at => 3.hours.from_now)
325
+ @authorization = create_authorization(
326
+ :owner => @owner,
327
+ :client => @client,
328
+ :code => 'a_fake_code',
329
+ :expires_at => 3.hours.from_now)
326
330
  end
327
331
 
328
332
  let(:auth_params) { { 'client_id' => @client.client_id,
@@ -341,6 +345,7 @@ describe Songkick::OAuth2::Provider do
341
345
  describe "with valid parameters" do
342
346
  it "does not respond to GET" do
343
347
  Songkick::OAuth2::Provider::Authorization.should_not_receive(:new)
348
+ params.delete('client_secret')
344
349
  response = get(params)
345
350
  validate_json_response(response, 400,
346
351
  'error' => 'invalid_request',
@@ -348,6 +353,16 @@ describe Songkick::OAuth2::Provider do
348
353
  )
349
354
  end
350
355
 
356
+ it "does not allow client credentials to be passed in the query string" do
357
+ Songkick::OAuth2::Provider::Authorization.should_not_receive(:new)
358
+ query_string = {'client_id' => params.delete('client_id'), 'client_secret' => params.delete('client_secret')}
359
+ response = post(params, query_string)
360
+ validate_json_response(response, 400,
361
+ 'error' => 'invalid_request',
362
+ 'error_description' => 'Bad request: must not send client credentials in the URI'
363
+ )
364
+ end
365
+
351
366
  describe "enforcing SSL" do
352
367
  before { Songkick::OAuth2::Provider.enforce_ssl = true }
353
368
 
@@ -375,7 +390,7 @@ describe Songkick::OAuth2::Provider do
375
390
  it "returns a successful response" do
376
391
  Songkick::OAuth2.stub(:random_string).and_return('random_access_token')
377
392
  response = post_basic_auth(auth_params, query_params)
378
- validate_json_response(response, 200, 'access_token' => 'random_access_token', 'expires_in' => 10800)
393
+ validate_json_response(response, 200, 'access_token' => 'random_access_token', 'expires_in' => 10800)
379
394
  end
380
395
 
381
396
  describe "with a scope parameter" do
@@ -445,8 +460,9 @@ describe Songkick::OAuth2::Provider do
445
460
 
446
461
  describe "protected resource request" do
447
462
  before do
448
- @authorization = Factory(:authorization,
463
+ @authorization = create_authorization(
449
464
  :owner => @owner,
465
+ :client => @client,
450
466
  :access_token => 'magic-key',
451
467
  :scope => 'profile')
452
468
  end
data/spec/spec_helper.rb CHANGED
@@ -2,11 +2,29 @@ require 'rubygems'
2
2
  require 'bundler/setup'
3
3
 
4
4
  require 'active_record'
5
- require File.expand_path('../../lib/songkick/oauth2/provider', __FILE__)
5
+ require 'songkick/oauth2/provider'
6
6
 
7
- dbfile = File.expand_path('../test.sqlite3', __FILE__)
8
- File.unlink(dbfile) if File.file?(dbfile)
9
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => dbfile)
7
+ case ENV['DB']
8
+ when 'mysql'
9
+ ActiveRecord::Base.establish_connection(
10
+ :adapter => 'mysql',
11
+ :host => '127.0.0.1',
12
+ :username => 'root',
13
+ :database => 'oauth2_test')
14
+ when 'postgres'
15
+ ActiveRecord::Base.establish_connection(
16
+ :adapter => 'postgresql',
17
+ :host => '127.0.0.1',
18
+ :username => 'postgres',
19
+ :database => 'oauth2_test')
20
+ else
21
+ dbfile = File.expand_path('../test.sqlite3', __FILE__)
22
+ File.unlink(dbfile) if File.file?(dbfile)
23
+
24
+ ActiveRecord::Base.establish_connection(
25
+ :adapter => 'sqlite3',
26
+ :database => dbfile)
27
+ end
10
28
 
11
29
  require 'logger'
12
30
  ActiveRecord::Base.logger = Logger.new(STDERR)
@@ -53,7 +71,7 @@ RSpec.configure do |config|
53
71
  end
54
72
 
55
73
  def create_authorization(params)
56
- Songkick::OAuth2::Model::Authorization.create do |authorization|
74
+ Songkick::OAuth2::Model::Authorization.__send__(:create) do |authorization|
57
75
  params.each do |key, value|
58
76
  authorization.__send__ "#{key}=", value
59
77
  end
metadata CHANGED
@@ -1,193 +1,200 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: songkick-oauth2-provider
3
- version: !ruby/object:Gem::Version
4
- hash: 55
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.1
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 10
9
- - 0
10
- version: 0.10.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - James Coglan
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-08-28 00:00:00 +01:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2012-10-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: activerecord
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: bcrypt-ruby
37
23
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
39
25
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bcrypt-ruby
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
47
38
  type: :runtime
48
- version_requirements: *id002
49
- - !ruby/object:Gem::Dependency
50
- name: json
51
39
  prerelease: false
52
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
53
41
  none: false
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- hash: 3
58
- segments:
59
- - 0
60
- version: "0"
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: json
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
61
54
  type: :runtime
62
- version_requirements: *id003
63
- - !ruby/object:Gem::Dependency
64
- name: rack
65
55
  prerelease: false
66
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rack
64
+ requirement: !ruby/object:Gem::Requirement
67
65
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- hash: 3
72
- segments:
73
- - 0
74
- version: "0"
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
75
70
  type: :runtime
76
- version_requirements: *id004
77
- - !ruby/object:Gem::Dependency
78
- name: appraisal
79
71
  prerelease: false
80
- requirement: &id005 !ruby/object:Gem::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: appraisal
80
+ requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
- requirements:
82
+ requirements:
83
83
  - - ~>
84
- - !ruby/object:Gem::Version
85
- hash: 15
86
- segments:
87
- - 0
88
- - 4
89
- - 0
84
+ - !ruby/object:Gem::Version
90
85
  version: 0.4.0
91
86
  type: :development
92
- version_requirements: *id005
93
- - !ruby/object:Gem::Dependency
94
- name: activerecord
95
87
  prerelease: false
96
- requirement: &id006 !ruby/object:Gem::Requirement
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.4.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: activerecord
96
+ requirement: !ruby/object:Gem::Requirement
97
97
  none: false
98
- requirements:
98
+ requirements:
99
99
  - - ~>
100
- - !ruby/object:Gem::Version
101
- hash: 15
102
- segments:
103
- - 3
104
- - 2
105
- - 0
100
+ - !ruby/object:Gem::Version
106
101
  version: 3.2.0
107
102
  type: :development
108
- version_requirements: *id006
109
- - !ruby/object:Gem::Dependency
110
- name: rspec
111
103
  prerelease: false
112
- requirement: &id007 !ruby/object:Gem::Requirement
104
+ version_requirements: !ruby/object:Gem::Requirement
113
105
  none: false
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- hash: 3
118
- segments:
119
- - 0
120
- version: "0"
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 3.2.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
121
118
  type: :development
122
- version_requirements: *id007
123
- - !ruby/object:Gem::Dependency
124
- name: sqlite3
125
119
  prerelease: false
126
- requirement: &id008 !ruby/object:Gem::Requirement
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: sqlite3
128
+ requirement: !ruby/object:Gem::Requirement
127
129
  none: false
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- hash: 3
132
- segments:
133
- - 0
134
- version: "0"
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
135
134
  type: :development
136
- version_requirements: *id008
137
- - !ruby/object:Gem::Dependency
138
- name: sinatra
139
135
  prerelease: false
140
- requirement: &id009 !ruby/object:Gem::Requirement
136
+ version_requirements: !ruby/object:Gem::Requirement
141
137
  none: false
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- hash: 27
146
- segments:
147
- - 1
148
- - 3
149
- - 0
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: sinatra
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
150
149
  version: 1.3.0
151
150
  type: :development
152
- version_requirements: *id009
153
- - !ruby/object:Gem::Dependency
154
- name: thin
155
151
  prerelease: false
156
- requirement: &id010 !ruby/object:Gem::Requirement
152
+ version_requirements: !ruby/object:Gem::Requirement
157
153
  none: false
158
- requirements:
159
- - - ">="
160
- - !ruby/object:Gem::Version
161
- hash: 3
162
- segments:
163
- - 0
164
- version: "0"
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: 1.3.0
158
+ - !ruby/object:Gem::Dependency
159
+ name: thin
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
165
166
  type: :development
166
- version_requirements: *id010
167
- - !ruby/object:Gem::Dependency
168
- name: factory_girl
169
167
  prerelease: false
170
- requirement: &id011 !ruby/object:Gem::Requirement
168
+ version_requirements: !ruby/object:Gem::Requirement
171
169
  none: false
172
- requirements:
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: factory_girl
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
173
179
  - - ~>
174
- - !ruby/object:Gem::Version
175
- hash: 3
176
- segments:
177
- - 2
178
- - 0
179
- version: "2.0"
180
+ - !ruby/object:Gem::Version
181
+ version: '2.0'
180
182
  type: :development
181
- version_requirements: *id011
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ~>
188
+ - !ruby/object:Gem::Version
189
+ version: '2.0'
182
190
  description:
183
191
  email: james@songkick.com
184
192
  executables: []
185
-
186
193
  extensions: []
187
-
188
- extra_rdoc_files:
194
+ extra_rdoc_files:
189
195
  - README.rdoc
190
- files:
196
+ files:
197
+ - History.txt
191
198
  - README.rdoc
192
199
  - example/public/style.css
193
200
  - example/views/login.erb
@@ -218,7 +225,9 @@ files:
218
225
  - lib/songkick/oauth2/provider/error.rb
219
226
  - lib/songkick/oauth2/provider/access_token.rb
220
227
  - lib/songkick/oauth2/schema.rb
228
+ - lib/songkick/oauth2/schema/20121024180930_songkick_oauth2_schema_add_authorization_index.rb
221
229
  - lib/songkick/oauth2/schema/20120828112156_songkick_oauth2_schema_original_schema.rb
230
+ - lib/songkick/oauth2/schema/20121025180447_songkick_oauth2_schema_add_unique_indexes.rb
222
231
  - lib/songkick/oauth2/router.rb
223
232
  - lib/songkick/oauth2/model.rb
224
233
  - spec/test_app/provider/views/authorize.erb
@@ -234,40 +243,30 @@ files:
234
243
  - spec/songkick/oauth2/provider/access_token_spec.rb
235
244
  - spec/songkick/oauth2/provider_spec.rb
236
245
  - spec/spec_helper.rb
237
- has_rdoc: true
238
- homepage: http://www.songkick.com
246
+ homepage: http://github.com/songkick/oauth2-provider
239
247
  licenses: []
240
-
241
248
  post_install_message:
242
- rdoc_options:
249
+ rdoc_options:
243
250
  - --main
244
251
  - README.rdoc
245
- require_paths:
252
+ require_paths:
246
253
  - lib
247
- required_ruby_version: !ruby/object:Gem::Requirement
254
+ required_ruby_version: !ruby/object:Gem::Requirement
248
255
  none: false
249
- requirements:
250
- - - ">="
251
- - !ruby/object:Gem::Version
252
- hash: 3
253
- segments:
254
- - 0
255
- version: "0"
256
- required_rubygems_version: !ruby/object:Gem::Requirement
256
+ requirements:
257
+ - - ! '>='
258
+ - !ruby/object:Gem::Version
259
+ version: '0'
260
+ required_rubygems_version: !ruby/object:Gem::Requirement
257
261
  none: false
258
- requirements:
259
- - - ">="
260
- - !ruby/object:Gem::Version
261
- hash: 3
262
- segments:
263
- - 0
264
- version: "0"
262
+ requirements:
263
+ - - ! '>='
264
+ - !ruby/object:Gem::Version
265
+ version: '0'
265
266
  requirements: []
266
-
267
267
  rubyforge_project:
268
- rubygems_version: 1.6.2
268
+ rubygems_version: 1.8.23
269
269
  signing_key:
270
270
  specification_version: 3
271
271
  summary: Simple OAuth 2.0 provider toolkit
272
272
  test_files: []
273
-