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