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,58 +1,58 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
module Model
|
4
|
-
|
4
|
+
|
5
5
|
class Client < ActiveRecord::Base
|
6
6
|
self.table_name = :oauth2_clients
|
7
|
-
|
7
|
+
|
8
8
|
belongs_to :oauth2_client_owner, :polymorphic => true
|
9
9
|
alias :owner :oauth2_client_owner
|
10
10
|
alias :owner= :oauth2_client_owner=
|
11
|
-
|
11
|
+
|
12
12
|
has_many :authorizations, :class_name => 'Songkick::OAuth2::Model::Authorization', :dependent => :destroy
|
13
|
-
|
13
|
+
|
14
14
|
validates_uniqueness_of :client_id, :name
|
15
15
|
validates_presence_of :name, :redirect_uri
|
16
16
|
validate :check_format_of_redirect_uri
|
17
|
-
|
17
|
+
|
18
18
|
attr_accessible :name, :redirect_uri
|
19
|
-
|
19
|
+
|
20
20
|
before_create :generate_credentials
|
21
|
-
|
21
|
+
|
22
22
|
def self.create_client_id
|
23
23
|
Songkick::OAuth2.generate_id do |client_id|
|
24
|
-
count(
|
24
|
+
Helpers.count(self, :client_id => client_id).zero?
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
attr_reader :client_secret
|
29
|
-
|
29
|
+
|
30
30
|
def client_secret=(secret)
|
31
31
|
@client_secret = secret
|
32
32
|
hash = BCrypt::Password.create(secret)
|
33
33
|
hash.force_encoding('UTF-8') if hash.respond_to?(:force_encoding)
|
34
34
|
self.client_secret_hash = hash
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def valid_client_secret?(secret)
|
38
38
|
BCrypt::Password.new(client_secret_hash) == secret
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
private
|
42
|
-
|
42
|
+
|
43
43
|
def check_format_of_redirect_uri
|
44
44
|
uri = URI.parse(redirect_uri)
|
45
45
|
errors.add(:redirect_uri, 'must be an absolute URI') unless uri.absolute?
|
46
46
|
rescue
|
47
47
|
errors.add(:redirect_uri, 'must be a URI')
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def generate_credentials
|
51
51
|
self.client_id = self.class.create_client_id
|
52
52
|
self.client_secret = Songkick::OAuth2.random_string
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
module Model
|
4
|
-
|
4
|
+
|
5
5
|
module Hashing
|
6
6
|
def hashes_attributes(*attributes)
|
7
7
|
attributes.each do |attribute|
|
@@ -11,7 +11,7 @@ module Songkick
|
|
11
11
|
end
|
12
12
|
attr_reader attribute
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
class_eval <<-RUBY
|
16
16
|
def reload(*args)
|
17
17
|
super
|
@@ -22,7 +22,7 @@ module Songkick
|
|
22
22
|
RUBY
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Songkick
|
2
|
+
module OAuth2
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def self.count(model, conditions={})
|
7
|
+
if model.respond_to?(:where)
|
8
|
+
model.where(conditions).count
|
9
|
+
else
|
10
|
+
model.count(:conditions => conditions)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
module Model
|
4
|
-
|
4
|
+
|
5
5
|
module ResourceOwner
|
6
6
|
def self.included(klass)
|
7
7
|
klass.has_many :oauth2_authorizations,
|
@@ -9,16 +9,16 @@ module Songkick
|
|
9
9
|
:as => :oauth2_resource_owner,
|
10
10
|
:dependent => :destroy
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def grant_access!(client, options = {})
|
14
14
|
Authorization.for(self, client, options)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def oauth2_authorization_for(client)
|
18
18
|
oauth2_authorizations.find_by_client_id(client.id)
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -15,11 +15,11 @@ module Songkick
|
|
15
15
|
module OAuth2
|
16
16
|
ROOT = File.expand_path(File.dirname(__FILE__) + '/..')
|
17
17
|
TOKEN_SIZE = 160
|
18
|
-
|
18
|
+
|
19
19
|
autoload :Model, ROOT + '/oauth2/model'
|
20
20
|
autoload :Router, ROOT + '/oauth2/router'
|
21
21
|
autoload :Schema, ROOT + '/oauth2/schema'
|
22
|
-
|
22
|
+
|
23
23
|
def self.random_string
|
24
24
|
if defined? SecureRandom
|
25
25
|
SecureRandom.hex(TOKEN_SIZE / 8).to_i(16).to_s(36)
|
@@ -27,18 +27,18 @@ module Songkick
|
|
27
27
|
rand(2 ** TOKEN_SIZE).to_s(36)
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def self.generate_id(&predicate)
|
32
32
|
id = random_string
|
33
33
|
id = random_string until predicate.call(id)
|
34
34
|
id
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def self.hashify(token)
|
38
38
|
return nil unless String === token
|
39
39
|
Digest::SHA1.hexdigest(token)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
ACCESS_TOKEN = 'access_token'
|
43
43
|
ASSERTION = 'assertion'
|
44
44
|
ASSERTION_TYPE = 'assertion_type'
|
@@ -61,7 +61,7 @@ module Songkick
|
|
61
61
|
STATE = 'state'
|
62
62
|
TOKEN = 'token'
|
63
63
|
USERNAME = 'username'
|
64
|
-
|
64
|
+
|
65
65
|
INVALID_REQUEST = 'invalid_request'
|
66
66
|
UNSUPPORTED_RESPONSE = 'unsupported_response_type'
|
67
67
|
REDIRECT_MISMATCH = 'redirect_uri_mismatch'
|
@@ -74,7 +74,7 @@ module Songkick
|
|
74
74
|
EXPIRED_TOKEN = 'expired_token'
|
75
75
|
INSUFFICIENT_SCOPE = 'insufficient_scope'
|
76
76
|
ACCESS_DENIED = 'access_denied'
|
77
|
-
|
77
|
+
|
78
78
|
class Provider
|
79
79
|
EXPIRY_TIME = 3600
|
80
80
|
|
@@ -86,42 +86,42 @@ module Songkick
|
|
86
86
|
class << self
|
87
87
|
attr_accessor :realm, :enforce_ssl
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def self.clear_assertion_handlers!
|
91
91
|
@password_handler = nil
|
92
92
|
@assertion_handlers = {}
|
93
93
|
@assertion_filters = []
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
clear_assertion_handlers!
|
97
|
-
|
97
|
+
|
98
98
|
def self.handle_passwords(&block)
|
99
99
|
@password_handler = block
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
def self.handle_password(client, username, password, scopes)
|
103
103
|
return nil unless @password_handler
|
104
104
|
@password_handler.call(client, username, password, scopes)
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
def self.filter_assertions(&filter)
|
108
108
|
@assertion_filters.push(filter)
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
def self.handle_assertions(assertion_type, &handler)
|
112
112
|
@assertion_handlers[assertion_type] = handler
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
def self.handle_assertion(client, assertion, scopes)
|
116
116
|
return nil unless @assertion_filters.all? { |f| f.call(client) }
|
117
117
|
handler = @assertion_handlers[assertion.type]
|
118
118
|
handler ? handler.call(client, assertion.value, scopes) : nil
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
def self.parse(*args)
|
122
122
|
Router.parse(*args)
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
def self.access_token(*args)
|
126
126
|
Router.access_token(*args)
|
127
127
|
end
|
@@ -1,35 +1,35 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
class Provider
|
4
|
-
|
4
|
+
|
5
5
|
class AccessToken
|
6
6
|
attr_reader :authorization
|
7
|
-
|
7
|
+
|
8
8
|
def initialize(resource_owner = nil, scopes = [], access_token = nil, error = nil)
|
9
9
|
@resource_owner = resource_owner
|
10
10
|
@scopes = scopes
|
11
11
|
@access_token = access_token
|
12
12
|
@error = error && INVALID_REQUEST
|
13
|
-
|
13
|
+
|
14
14
|
authorize!(access_token, error)
|
15
15
|
validate!
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def client
|
19
19
|
valid? ? @authorization.client : nil
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def owner
|
23
23
|
valid? ? @authorization.owner : nil
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def response_headers
|
27
27
|
return {} if valid?
|
28
28
|
error_message = "OAuth realm='#{ Provider.realm }'"
|
29
29
|
error_message << ", error='#{ @error }'" unless @error == ''
|
30
30
|
{'WWW-Authenticate' => error_message}
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def response_status
|
34
34
|
case @error
|
35
35
|
when INVALID_REQUEST, INVALID_TOKEN, EXPIRED_TOKEN then 401
|
@@ -38,30 +38,35 @@ module Songkick
|
|
38
38
|
else 200
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def valid?
|
43
43
|
@error.nil?
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
private
|
47
|
-
|
47
|
+
|
48
48
|
def authorize!(access_token, error)
|
49
49
|
return unless @authorization = Model.find_access_token(access_token)
|
50
50
|
@authorization.update_attribute(:access_token, nil) if error
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def validate!
|
54
54
|
return @error = '' unless @access_token
|
55
55
|
return @error = INVALID_TOKEN unless @authorization
|
56
56
|
return @error = EXPIRED_TOKEN if @authorization.expired?
|
57
57
|
return @error = INSUFFICIENT_SCOPE unless @authorization.in_scope?(@scopes)
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
|
59
|
+
case @resource_owner
|
60
|
+
when :implicit
|
61
|
+
# no error
|
62
|
+
when nil
|
63
|
+
@error = INVALID_TOKEN
|
64
|
+
else
|
65
|
+
@error = INSUFFICIENT_SCOPE if @authorization.owner != @resource_owner
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
64
|
-
|
69
|
+
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
@@ -1,152 +1,153 @@
|
|
1
1
|
module Songkick
|
2
2
|
module OAuth2
|
3
3
|
class Provider
|
4
|
-
|
4
|
+
|
5
5
|
class Authorization
|
6
6
|
attr_reader :owner, :client,
|
7
7
|
:code, :access_token,
|
8
8
|
:expires_in, :refresh_token,
|
9
9
|
:error, :error_description
|
10
|
-
|
10
|
+
|
11
11
|
REQUIRED_PARAMS = [RESPONSE_TYPE, CLIENT_ID, REDIRECT_URI]
|
12
12
|
VALID_PARAMS = REQUIRED_PARAMS + [SCOPE, STATE]
|
13
13
|
VALID_RESPONSES = [CODE, TOKEN, CODE_AND_TOKEN]
|
14
|
-
|
14
|
+
|
15
15
|
def initialize(resource_owner, params, transport_error = nil)
|
16
16
|
@owner = resource_owner
|
17
17
|
@params = params
|
18
18
|
@scope = params[SCOPE]
|
19
19
|
@state = params[STATE]
|
20
|
-
|
20
|
+
|
21
21
|
@transport_error = transport_error
|
22
|
-
|
22
|
+
|
23
23
|
validate!
|
24
|
-
|
24
|
+
|
25
25
|
return unless @owner and not @error
|
26
|
-
|
26
|
+
|
27
27
|
@model = @owner.oauth2_authorization_for(@client)
|
28
28
|
return unless @model and @model.in_scope?(scopes) and not @model.expired?
|
29
|
-
|
29
|
+
|
30
30
|
@authorized = true
|
31
|
-
|
31
|
+
|
32
32
|
if @params[RESPONSE_TYPE] =~ /code/
|
33
33
|
@code = @model.generate_code
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
if @params[RESPONSE_TYPE] =~ /token/
|
37
37
|
@access_token = @model.generate_access_token
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def scopes
|
42
42
|
scopes = @scope ? @scope.split(/\s+/).delete_if { |s| s.empty? } : []
|
43
43
|
Set.new(scopes)
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def unauthorized_scopes
|
47
47
|
@model ? scopes.select { |s| not @model.in_scope?(s) } : scopes
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def grant_access!(options = {})
|
51
51
|
@model = Model::Authorization.for(@owner, @client,
|
52
52
|
:response_type => @params[RESPONSE_TYPE],
|
53
53
|
:scope => @scope,
|
54
54
|
:duration => options[:duration])
|
55
|
-
|
55
|
+
|
56
56
|
@code = @model.code
|
57
57
|
@access_token = @model.access_token
|
58
58
|
@refresh_token = @model.refresh_token
|
59
59
|
@expires_in = @model.expires_in
|
60
|
-
|
60
|
+
|
61
61
|
unless @params[RESPONSE_TYPE] == CODE
|
62
62
|
@expires_in = @model.expires_in
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
@authorized = true
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def deny_access!
|
69
69
|
@code = @access_token = @refresh_token = nil
|
70
70
|
@error = ACCESS_DENIED
|
71
71
|
@error_description = "The user denied you access"
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def params
|
75
75
|
params = {}
|
76
76
|
VALID_PARAMS.each { |key| params[key] = @params[key] if @params.has_key?(key) }
|
77
77
|
params
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
def redirect?
|
81
81
|
@client and (@authorized or not valid?)
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
def redirect_uri
|
85
85
|
return nil unless @client
|
86
86
|
base_redirect_uri = @client.redirect_uri
|
87
|
-
|
87
|
+
q = (base_redirect_uri =~ /\?/) ? '&' : '?'
|
88
|
+
|
88
89
|
if not valid?
|
89
90
|
query = to_query_string(ERROR, ERROR_DESCRIPTION, STATE)
|
90
|
-
"#{ base_redirect_uri }
|
91
|
-
|
91
|
+
"#{ base_redirect_uri }#{ q }#{ query }"
|
92
|
+
|
92
93
|
elsif @params[RESPONSE_TYPE] == CODE_AND_TOKEN
|
93
94
|
query = to_query_string(CODE, STATE)
|
94
95
|
fragment = to_query_string(ACCESS_TOKEN, EXPIRES_IN, SCOPE)
|
95
|
-
"#{ base_redirect_uri }#{ query.empty? ? '' :
|
96
|
-
|
97
|
-
elsif @params[RESPONSE_TYPE] ==
|
96
|
+
"#{ base_redirect_uri }#{ query.empty? ? '' : q + query }##{ fragment }"
|
97
|
+
|
98
|
+
elsif @params[RESPONSE_TYPE] == TOKEN
|
98
99
|
fragment = to_query_string(ACCESS_TOKEN, EXPIRES_IN, SCOPE, STATE)
|
99
100
|
"#{ base_redirect_uri }##{ fragment }"
|
100
|
-
|
101
|
+
|
101
102
|
else
|
102
103
|
query = to_query_string(CODE, SCOPE, STATE)
|
103
|
-
"#{ base_redirect_uri }
|
104
|
+
"#{ base_redirect_uri }#{ q }#{ query }"
|
104
105
|
end
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
def response_body
|
108
109
|
warn "Songkick::OAuth2::Provider::Authorization no longer returns a response body "+
|
109
110
|
"when the request is invalid. You should call valid? to determine "+
|
110
111
|
"whether to render your login page or an error page."
|
111
112
|
nil
|
112
113
|
end
|
113
|
-
|
114
|
+
|
114
115
|
def response_headers
|
115
116
|
redirect? ? {} : {'Cache-Control' => 'no-store'}
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
def response_status
|
119
120
|
return 302 if redirect?
|
120
121
|
return 200 if valid?
|
121
122
|
@client ? 302 : 400
|
122
123
|
end
|
123
|
-
|
124
|
+
|
124
125
|
def valid?
|
125
126
|
@error.nil?
|
126
127
|
end
|
127
|
-
|
128
|
+
|
128
129
|
private
|
129
|
-
|
130
|
+
|
130
131
|
def validate!
|
131
132
|
if @transport_error
|
132
133
|
@error = @transport_error.error
|
133
134
|
@error_description = @transport_error.error_description
|
134
135
|
return
|
135
136
|
end
|
136
|
-
|
137
|
+
|
137
138
|
@client = @params[CLIENT_ID] && Model::Client.find_by_client_id(@params[CLIENT_ID])
|
138
139
|
unless @client
|
139
140
|
@error = INVALID_CLIENT
|
140
141
|
@error_description = "Unknown client ID #{@params[CLIENT_ID]}"
|
141
142
|
end
|
142
|
-
|
143
|
+
|
143
144
|
REQUIRED_PARAMS.each do |param|
|
144
145
|
next if @params.has_key?(param)
|
145
146
|
@error = INVALID_REQUEST
|
146
147
|
@error_description = "Missing required parameter #{param}"
|
147
148
|
end
|
148
149
|
return if @error
|
149
|
-
|
150
|
+
|
150
151
|
[SCOPE, STATE].each do |param|
|
151
152
|
next unless @params.has_key?(param)
|
152
153
|
if @params[param] =~ /\r\n/
|
@@ -154,24 +155,24 @@ module Songkick
|
|
154
155
|
@error_description = "Illegal value for #{param} parameter"
|
155
156
|
end
|
156
157
|
end
|
157
|
-
|
158
|
+
|
158
159
|
unless VALID_RESPONSES.include?(@params[RESPONSE_TYPE])
|
159
160
|
@error = UNSUPPORTED_RESPONSE
|
160
161
|
@error_description = "Response type #{@params[RESPONSE_TYPE]} is not supported"
|
161
162
|
end
|
162
|
-
|
163
|
+
|
163
164
|
@client = Model::Client.find_by_client_id(@params[CLIENT_ID])
|
164
165
|
unless @client
|
165
166
|
@error = INVALID_CLIENT
|
166
167
|
@error_description = "Unknown client ID #{@params[CLIENT_ID]}"
|
167
168
|
end
|
168
|
-
|
169
|
+
|
169
170
|
if @client and @client.redirect_uri and @client.redirect_uri != @params[REDIRECT_URI]
|
170
171
|
@error = REDIRECT_MISMATCH
|
171
172
|
@error_description = "Parameter #{REDIRECT_URI} does not match registered URI"
|
172
173
|
end
|
173
174
|
end
|
174
|
-
|
175
|
+
|
175
176
|
def to_query_string(*ivars)
|
176
177
|
ivars.map { |key|
|
177
178
|
value = instance_variable_get("@#{key}")
|
@@ -180,7 +181,7 @@ module Songkick
|
|
180
181
|
}.compact.join('&')
|
181
182
|
end
|
182
183
|
end
|
183
|
-
|
184
|
+
|
184
185
|
end
|
185
186
|
end
|
186
187
|
end
|