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.
- checksums.yaml +7 -0
- data/History.txt +7 -0
- data/README.rdoc +18 -11
- data/example/README.rdoc +1 -1
- data/example/application.rb +9 -9
- data/example/schema.rb +1 -1
- data/example/views/authorize.erb +2 -2
- data/example/views/layout.erb +4 -4
- data/example/views/login.erb +2 -2
- data/example/views/new_client.erb +1 -1
- data/example/views/new_user.erb +1 -1
- data/lib/songkick/oauth2/model.rb +8 -6
- data/lib/songkick/oauth2/model/authorization.rb +31 -31
- data/lib/songkick/oauth2/model/client.rb +15 -15
- data/lib/songkick/oauth2/model/client_owner.rb +2 -2
- data/lib/songkick/oauth2/model/hashing.rb +3 -3
- data/lib/songkick/oauth2/model/helpers.rb +16 -0
- data/lib/songkick/oauth2/model/resource_owner.rb +4 -4
- data/lib/songkick/oauth2/provider.rb +16 -16
- data/lib/songkick/oauth2/provider/access_token.rb +20 -15
- data/lib/songkick/oauth2/provider/authorization.rb +43 -42
- data/lib/songkick/oauth2/provider/error.rb +4 -4
- data/lib/songkick/oauth2/provider/exchange.rb +46 -46
- data/lib/songkick/oauth2/router.rb +13 -13
- data/lib/songkick/oauth2/schema.rb +11 -3
- data/lib/songkick/oauth2/schema/20120828112156_songkick_oauth2_schema_original_schema.rb +2 -2
- data/lib/songkick/oauth2/schema/20121024180930_songkick_oauth2_schema_add_authorization_index.rb +3 -3
- data/lib/songkick/oauth2/schema/20121025180447_songkick_oauth2_schema_add_unique_indexes.rb +7 -7
- data/spec/request_helpers.rb +25 -21
- data/spec/songkick/oauth2/model/authorization_spec.rb +56 -56
- data/spec/songkick/oauth2/model/client_spec.rb +9 -9
- data/spec/songkick/oauth2/model/helpers_spec.rb +26 -0
- data/spec/songkick/oauth2/model/resource_owner_spec.rb +13 -13
- data/spec/songkick/oauth2/provider/access_token_spec.rb +32 -20
- data/spec/songkick/oauth2/provider/authorization_spec.rb +73 -62
- data/spec/songkick/oauth2/provider/exchange_spec.rb +72 -72
- data/spec/songkick/oauth2/provider_spec.rb +101 -101
- data/spec/spec_helper.rb +5 -3
- data/spec/test_app/helper.rb +11 -7
- data/spec/test_app/provider/application.rb +12 -12
- data/spec/test_app/provider/views/authorize.erb +2 -2
- 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
|
data/lib/songkick/oauth2/schema/20121024180930_songkick_oauth2_schema_add_authorization_index.rb
CHANGED
@@ -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
|
-
|
28
|
+
|
29
|
+
remove_index :oauth2_clients, [:name]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|