songkick-oauth2-provider 0.10.2 → 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,21 +1,21 @@
1
1
  module Songkick
2
2
  module OAuth2
3
3
  class Provider
4
-
4
+
5
5
  class Error
6
6
  def initialize(message = nil)
7
7
  @message = message
8
8
  end
9
-
9
+
10
10
  def error
11
11
  INVALID_REQUEST
12
12
  end
13
-
13
+
14
14
  def error_description
15
15
  'Bad request' + (@message ? ": #{@message}" : '')
16
16
  end
17
17
  end
18
-
18
+
19
19
  end
20
20
  end
21
21
  end
@@ -1,43 +1,43 @@
1
1
  module Songkick
2
2
  module OAuth2
3
3
  class Provider
4
-
4
+
5
5
  class Exchange
6
6
  attr_reader :client, :error, :error_description
7
-
7
+
8
8
  REQUIRED_PARAMS = [CLIENT_ID, CLIENT_SECRET, GRANT_TYPE]
9
9
  VALID_GRANT_TYPES = [AUTHORIZATION_CODE, PASSWORD, ASSERTION, REFRESH_TOKEN]
10
-
10
+
11
11
  REQUIRED_PASSWORD_PARAMS = [USERNAME, PASSWORD]
12
12
  REQUIRED_ASSERTION_PARAMS = [ASSERTION_TYPE, ASSERTION]
13
-
13
+
14
14
  RESPONSE_HEADERS = {
15
15
  'Cache-Control' => 'no-store',
16
16
  'Content-Type' => 'application/json'
17
17
  }
18
-
18
+
19
19
  def initialize(resource_owner, params, transport_error = nil)
20
20
  @params = params
21
21
  @scope = params[SCOPE]
22
22
  @grant_type = @params[GRANT_TYPE]
23
-
23
+
24
24
  @transport_error = transport_error
25
-
25
+
26
26
  validate!
27
27
  end
28
-
28
+
29
29
  def owner
30
30
  @authorization && @authorization.owner
31
31
  end
32
-
32
+
33
33
  def redirect?
34
34
  false
35
35
  end
36
-
36
+
37
37
  def response_body
38
38
  return jsonize(ERROR, ERROR_DESCRIPTION) unless valid?
39
39
  update_authorization
40
-
40
+
41
41
  response = {}
42
42
  [ACCESS_TOKEN, REFRESH_TOKEN, SCOPE].each do |key|
43
43
  value = @authorization.__send__(key)
@@ -46,18 +46,18 @@ module Songkick
46
46
  if expiry = @authorization.expires_in
47
47
  response[EXPIRES_IN] = expiry
48
48
  end
49
-
49
+
50
50
  JSON.unparse(response)
51
51
  end
52
-
52
+
53
53
  def response_headers
54
54
  RESPONSE_HEADERS
55
55
  end
56
-
56
+
57
57
  def response_status
58
58
  valid? ? 200 : 400
59
59
  end
60
-
60
+
61
61
  def scopes
62
62
  scopes = @scope ? @scope.split(/\s+/).delete_if { |s| s.empty? } : []
63
63
  Set.new(scopes)
@@ -68,41 +68,41 @@ module Songkick
68
68
  @authorization.exchange!
69
69
  @already_updated = true
70
70
  end
71
-
71
+
72
72
  def valid?
73
73
  @error.nil?
74
74
  end
75
-
75
+
76
76
  private
77
-
77
+
78
78
  def jsonize(*ivars)
79
79
  hash = {}
80
80
  ivars.each { |key| hash[key] = instance_variable_get("@#{key}") }
81
81
  JSON.unparse(hash)
82
82
  end
83
-
83
+
84
84
  def validate!
85
85
  if @transport_error
86
86
  @error = @transport_error.error
87
87
  @error_description = @transport_error.error_description
88
88
  return
89
89
  end
90
-
90
+
91
91
  validate_required_params
92
-
92
+
93
93
  return if @error
94
94
  validate_client
95
-
95
+
96
96
  unless VALID_GRANT_TYPES.include?(@grant_type)
97
97
  @error = UNSUPPORTED_GRANT_TYPE
98
98
  @error_description = "The grant type #{@grant_type} is not recognized"
99
99
  end
100
100
  return if @error
101
-
101
+
102
102
  __send__("validate_#{@grant_type}")
103
103
  validate_scope
104
104
  end
105
-
105
+
106
106
  def validate_required_params
107
107
  REQUIRED_PARAMS.each do |param|
108
108
  next if @params.has_key?(param)
@@ -110,72 +110,72 @@ module Songkick
110
110
  @error_description = "Missing required parameter #{param}"
111
111
  end
112
112
  end
113
-
113
+
114
114
  def validate_client
115
115
  @client = Model::Client.find_by_client_id(@params[CLIENT_ID])
116
116
  unless @client
117
117
  @error = INVALID_CLIENT
118
118
  @error_description = "Unknown client ID #{@params[CLIENT_ID]}"
119
119
  end
120
-
120
+
121
121
  if @client and not @client.valid_client_secret?(@params[CLIENT_SECRET])
122
122
  @error = INVALID_CLIENT
123
123
  @error_description = 'Parameter client_secret does not match'
124
124
  end
125
125
  end
126
-
126
+
127
127
  def validate_scope
128
128
  if @authorization and not @authorization.in_scope?(scopes)
129
129
  @error = INVALID_SCOPE
130
130
  @error_description = 'The request scope was never granted by the user'
131
131
  end
132
132
  end
133
-
133
+
134
134
  def validate_authorization_code
135
135
  unless @params[CODE]
136
136
  @error = INVALID_REQUEST
137
137
  @error_description = "Missing required parameter code"
138
138
  end
139
-
139
+
140
140
  if @client.redirect_uri and @client.redirect_uri != @params[REDIRECT_URI]
141
141
  @error = REDIRECT_MISMATCH
142
142
  @error_description = "Parameter redirect_uri does not match registered URI"
143
143
  end
144
-
144
+
145
145
  unless @params.has_key?(REDIRECT_URI)
146
146
  @error = INVALID_REQUEST
147
147
  @error_description = "Missing required parameter redirect_uri"
148
148
  end
149
-
149
+
150
150
  return if @error
151
-
151
+
152
152
  @authorization = @client.authorizations.find_by_code(@params[CODE])
153
153
  validate_authorization
154
154
  end
155
-
155
+
156
156
  def validate_password
157
157
  REQUIRED_PASSWORD_PARAMS.each do |param|
158
158
  next if @params.has_key?(param)
159
159
  @error = INVALID_REQUEST
160
160
  @error_description = "Missing required parameter #{param}"
161
161
  end
162
-
162
+
163
163
  return if @error
164
-
164
+
165
165
  @authorization = Provider.handle_password(@client, @params[USERNAME], @params[PASSWORD], scopes)
166
166
  return validate_authorization if @authorization
167
-
167
+
168
168
  @error = INVALID_GRANT
169
169
  @error_description = 'The access grant you supplied is invalid'
170
170
  end
171
-
171
+
172
172
  def validate_assertion
173
173
  REQUIRED_ASSERTION_PARAMS.each do |param|
174
174
  next if @params.has_key?(param)
175
175
  @error = INVALID_REQUEST
176
176
  @error_description = "Missing required parameter #{param}"
177
177
  end
178
-
178
+
179
179
  if @params[ASSERTION_TYPE]
180
180
  uri = URI.parse(@params[ASSERTION_TYPE]) rescue nil
181
181
  unless uri and uri.absolute?
@@ -183,36 +183,36 @@ module Songkick
183
183
  @error_description = 'Parameter assertion_type must be an absolute URI'
184
184
  end
185
185
  end
186
-
186
+
187
187
  return if @error
188
-
188
+
189
189
  assertion = Assertion.new(@params)
190
190
  @authorization = Provider.handle_assertion(@client, assertion, scopes)
191
191
  return validate_authorization if @authorization
192
-
192
+
193
193
  @error = UNAUTHORIZED_CLIENT
194
194
  @error_description = 'Client cannot use the given assertion type'
195
195
  end
196
-
196
+
197
197
  def validate_refresh_token
198
198
  refresh_token_hash = Songkick::OAuth2.hashify(@params[REFRESH_TOKEN])
199
199
  @authorization = @client.authorizations.find_by_refresh_token_hash(refresh_token_hash)
200
200
  validate_authorization
201
201
  end
202
-
202
+
203
203
  def validate_authorization
204
204
  unless @authorization
205
205
  @error = INVALID_GRANT
206
206
  @error_description = 'The access grant you supplied is invalid'
207
207
  end
208
-
208
+
209
209
  if @authorization and @authorization.expired?
210
210
  @error = INVALID_GRANT
211
211
  @error_description = 'The access grant you supplied is invalid'
212
212
  end
213
213
  end
214
214
  end
215
-
215
+
216
216
  class Assertion
217
217
  attr_reader :type, :value
218
218
  def initialize(params)
@@ -220,7 +220,7 @@ module Songkick
220
220
  @value = params[ASSERTION]
221
221
  end
222
222
  end
223
-
223
+
224
224
  end
225
225
  end
226
226
  end
@@ -1,25 +1,25 @@
1
1
  module Songkick
2
2
  module OAuth2
3
3
  class Router
4
-
4
+
5
5
  # Public methods in the namespace take either Rack env objects, or Request
6
6
  # objects from Rails/Sinatra and an optional params hash which it then
7
7
  # coerces to Rack requests. This is for backward compatibility; originally
8
8
  # it only took request objects.
9
-
9
+
10
10
  class << self
11
11
  def parse(resource_owner, env)
12
12
  error = detect_transport_error(env)
13
13
  request = request_from(env)
14
14
  params = request.params
15
15
  auth = auth_params(env)
16
-
16
+
17
17
  if auth[CLIENT_ID] and auth[CLIENT_ID] != params[CLIENT_ID]
18
18
  error ||= Provider::Error.new("#{CLIENT_ID} from Basic Auth and request body do not match")
19
19
  end
20
-
20
+
21
21
  params = params.merge(auth)
22
-
22
+
23
23
  if params[GRANT_TYPE]
24
24
  error ||= Provider::Error.new('must be a POST request') unless request.post?
25
25
  Provider::Exchange.new(resource_owner, params, error)
@@ -27,7 +27,7 @@ module Songkick
27
27
  Provider::Authorization.new(resource_owner, params, error)
28
28
  end
29
29
  end
30
-
30
+
31
31
  def access_token(resource_owner, scopes, env)
32
32
  access_token = access_token_from_request(env)
33
33
  Provider::AccessToken.new(resource_owner,
@@ -40,30 +40,30 @@ module Songkick
40
40
  request = request_from(env)
41
41
  params = request.params
42
42
  header = request.env['HTTP_AUTHORIZATION']
43
-
43
+
44
44
  header && header =~ /^OAuth\s+/ ?
45
45
  header.gsub(/^OAuth\s+/, '') :
46
46
  params[OAUTH_TOKEN]
47
47
  end
48
-
48
+
49
49
  private
50
-
50
+
51
51
  def request_from(env_or_request)
52
52
  env = env_or_request.respond_to?(:env) ? env_or_request.env : env_or_request
53
53
  env = Rack::MockRequest.env_for(env['REQUEST_URI'] || '', :input => env['RAW_POST_DATA']).merge(env)
54
54
  Rack::Request.new(env)
55
55
  end
56
-
56
+
57
57
  def auth_params(env)
58
58
  return {} unless basic = env['HTTP_AUTHORIZATION']
59
59
  parts = basic.split(/\s+/)
60
60
  username, password = Base64.decode64(parts.last).split(':')
61
61
  {CLIENT_ID => username, CLIENT_SECRET => password}
62
62
  end
63
-
63
+
64
64
  def detect_transport_error(env)
65
65
  request = request_from(env)
66
-
66
+
67
67
  if Provider.enforce_ssl and not request.ssl?
68
68
  Provider::Error.new('must make requests using HTTPS')
69
69
  elsif request.GET['client_secret']
@@ -71,7 +71,7 @@ module Songkick
71
71
  end
72
72
  end
73
73
  end
74
-
74
+
75
75
  end
76
76
  end
77
77
  end
@@ -1,6 +1,6 @@
1
1
  module Songkick
2
2
  module OAuth2
3
-
3
+
4
4
  class Schema
5
5
  def self.migrate
6
6
  ActiveRecord::Base.logger ||= Logger.new(StringIO.new)
@@ -9,12 +9,20 @@ module Songkick
9
9
  class << self
10
10
  alias :up :migrate
11
11
  end
12
-
12
+
13
+ def self.rollback
14
+ ActiveRecord::Base.logger ||= Logger.new(StringIO.new)
15
+ ActiveRecord::Migrator.down(migrations_path)
16
+ end
17
+ class << self
18
+ alias :down :rollback
19
+ end
20
+
13
21
  def self.migrations_path
14
22
  File.expand_path('../schema', __FILE__)
15
23
  end
16
24
  end
17
-
25
+
18
26
  end
19
27
  end
20
28
 
@@ -10,7 +10,7 @@ class SongkickOauth2SchemaOriginalSchema < ActiveRecord::Migration
10
10
  t.string :redirect_uri
11
11
  end
12
12
  add_index :oauth2_clients, [:client_id]
13
-
13
+
14
14
  create_table :oauth2_authorizations do |t|
15
15
  t.timestamps
16
16
  t.string :oauth2_resource_owner_type
@@ -27,7 +27,7 @@ class SongkickOauth2SchemaOriginalSchema < ActiveRecord::Migration
27
27
  add_index :oauth2_authorizations, [:client_id, :access_token_hash]
28
28
  add_index :oauth2_authorizations, [:client_id, :refresh_token_hash]
29
29
  end
30
-
30
+
31
31
  def self.down
32
32
  drop_table :oauth2_clients
33
33
  drop_table :oauth2_authorizations
@@ -1,13 +1,13 @@
1
1
  class SongkickOauth2SchemaAddAuthorizationIndex < ActiveRecord::Migration
2
2
  INDEX_NAME = 'index_owner_client_pairs'
3
-
3
+
4
4
  def self.up
5
5
  remove_index :oauth2_authorizations, [:client_id, :access_token_hash]
6
6
  add_index :oauth2_authorizations, [:client_id, :oauth2_resource_owner_type, :oauth2_resource_owner_id], :name => INDEX_NAME, :unique => true
7
7
  end
8
-
8
+
9
9
  def self.down
10
- remove_index :oauth2_authorizations, INDEX_NAME
10
+ remove_index :oauth2_authorizations, :name => INDEX_NAME
11
11
  add_index :oauth2_authorizations, [:client_id, :access_token_hash]
12
12
  end
13
13
  end
@@ -1,6 +1,6 @@
1
1
  class SongkickOauth2SchemaAddUniqueIndexes < ActiveRecord::Migration
2
2
  FIELDS = [:code, :refresh_token_hash]
3
-
3
+
4
4
  def self.up
5
5
  FIELDS.each do |field|
6
6
  remove_index :oauth2_authorizations, [:client_id, field]
@@ -8,13 +8,13 @@ class SongkickOauth2SchemaAddUniqueIndexes < ActiveRecord::Migration
8
8
  end
9
9
  remove_index :oauth2_authorizations, [:access_token_hash]
10
10
  add_index :oauth2_authorizations, [:access_token_hash], :unique => true
11
-
11
+
12
12
  remove_index :oauth2_clients, [:client_id]
13
13
  add_index :oauth2_clients, [:client_id], :unique => true
14
-
14
+
15
15
  add_index :oauth2_clients, [:name], :unique => true
16
16
  end
17
-
17
+
18
18
  def self.down
19
19
  FIELDS.each do |field|
20
20
  remove_index :oauth2_authorizations, [:client_id, field]
@@ -22,11 +22,11 @@ class SongkickOauth2SchemaAddUniqueIndexes < ActiveRecord::Migration
22
22
  end
23
23
  remove_index :oauth2_authorizations, [:access_token_hash]
24
24
  add_index :oauth2_authorizations, [:access_token_hash]
25
-
25
+
26
26
  remove_index :oauth2_clients, [:client_id]
27
27
  add_index :oauth2_clients, [:client_id]
28
-
29
- remove_clients :oauth2_clients, [:name]
28
+
29
+ remove_index :oauth2_clients, [:name]
30
30
  end
31
31
  end
32
32