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
@@ -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