songkick-oauth2-provider 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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
-