Class: Rack::Auth::OpenID
- Inherits:
-
AbstractHandler
- Object
- AbstractHandler
- Rack::Auth::OpenID
- Defined in:
- lib/gems/rack-0.9.1/lib/rack/auth/openid.rb
Overview
Rack::Auth::OpenID provides a simple method for permitting openid based logins. It requires the ruby-openid library from janrain to operate, as well as a rack method of session management.
The ruby-openid home page is at openidenabled.com/ruby-openid/.
The OpenID specifications can be found at openid.net/specs/openid-authentication-1_1.html and openid.net/specs/openid-authentication-2_0.html. Documentation for published OpenID extensions and related topics can be found at openid.net/developers/specs/.
It is recommended to read through the OpenID spec, as well as ruby-openid’s documentation, to understand what exactly goes on. However a setup as simple as the presented examples is enough to provide functionality.
This library strongly intends to utilize the OpenID 2.0 features of the ruby-openid library, while maintaining OpenID 1.0 compatiblity.
All responses from this rack application will be 303 redirects unless an error occurs, with the exception of an authentication request requiring an HTML form submission.
NOTE: Extensions are not currently supported by this implimentation of the OpenID rack application due to the complexity of the current ruby-openid extension handling.
NOTE: Due to the amount of data that this library stores in the session, Rack::Session::Cookie may fault.
Defined Under Namespace
Classes: NoSession
Constant Summary collapse
- OIDStore =
Required for ruby-openid
::OpenID::Store::Memory.new
- HTML =
'<html><head><title>%s</title></head><body>%s</body></html>'
Instance Attribute Summary collapse
-
#extensions ⇒ Object
readonly
Returns the value of attribute extensions.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Attributes inherited from AbstractHandler
Instance Method Summary collapse
-
#add_extension(ext, *args) ⇒ Object
The first argument should be the main extension module.
-
#call(env) ⇒ Object
It sets up and uses session data at
:openid
within the session. -
#check(consumer, session, req) ⇒ Object
As the first part of OpenID consumer action, #check retrieves the data required for completion.
-
#extension_namespaces ⇒ Object
A conveniance method that returns the namespace of all current extensions used by this instance.
-
#finish(consumer, session, req) ⇒ Object
This is the final portion of authentication.
-
#initialize(realm, options = {}) ⇒ OpenID
constructor
A Hash of options is taken as it’s single initializing argument.
Constructor Details
#initialize(realm, options = {}) ⇒ OpenID
A Hash of options is taken as it’s single initializing argument. For example:
simple_oid = OpenID.new('http://mysite.com/')
return_oid = OpenID.new('http://mysite.com/', {
:return_to => 'http://mysite.com/openid'
})
page_oid = OpenID.new('http://mysite.com/',
:login_good => 'http://mysite.com/auth_good'
)
complex_oid = OpenID.new('http://mysite.com/',
:return_to => 'http://mysite.com/openid',
:login_good => 'http://mysite.com/user/preferences',
:auth_fail => [500, {'Content-Type'=>'text/plain'},
'Unable to negotiate with foreign server.'],
:immediate => true,
:extensions => {
::OpenID::SReg => [['email'],['nickname']]
}
)
Arguments
The first argument is the realm, identifying the site they are trusting with their identity. This is required.
NOTE: In OpenID 1.x, the realm or trust_root is optional and the return_to url is required. As this library strives tward ruby-openid 2.0, and OpenID 2.0 compatibiliy, the realm is required and return_to is optional. However, this implimentation is still backwards compatible with OpenID 1.0 servers.
The optional second argument is a hash of options.
Options
:return_to
defines the url to return to after the client authenticates with the openid service provider. This url should point to where Rack::Auth::OpenID is mounted. If :return_to
is not provided, :return_to will be the current url including all query parameters.
:session_key
defines the key to the session hash in the env. It defaults to ‘rack.session’.
:openid_param
defines at what key in the request parameters to find the identifier to resolve. As per the 2.0 spec, the default is ‘openid_identifier’.
:immediate
as true will make immediate type of requests the default. See OpenID specification documentation.
URL options
:login_good
is the url to go to after the authentication process has completed.
:login_fail
is the url to go to after the authentication process has failed.
:login_quit
is the url to go to after the authentication process has been cancelled.
Response options
:no_session
should be a rack response to be returned if no or an incompatible session is found.
:auth_fail
should be a rack response to be returned if an OpenID::DiscoveryFailure occurs. This is typically due to being unable to access the identity url or identity server.
:error
should be a rack response to return if any other generic error would occur and options[:catch_errors]
is true.
Extensions
:extensions
should be a hash of openid extension implementations. The key should be the extension main module, the value should be an array of arguments for extension::Request.new
The hash is iterated over and passed to #add_extension for processing. Please see #add_extension for further documentation.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 137 def initialize(realm, ={}) @realm = realm realm = URI(realm) if realm.path.empty? raise ArgumentError, "Invalid realm path: '#{realm.path}'" elsif not realm.absolute? raise ArgumentError, "Realm '#{@realm}' not absolute" end [:return_to, :login_good, :login_fail, :login_quit].each do |key| if .key? key and luri = URI([key]) if !luri.absolute? raise ArgumentError, ":#{key} is not an absolute uri: '#{luri}'" end end end if [:return_to] and ruri = URI([:return_to]) if ruri.path.empty? raise ArgumentError, "Invalid return_to path: '#{ruri.path}'" elsif realm.path != ruri.path[0, realm.path.size] raise ArgumentError, 'return_to not within realm.' \ end end # TODO: extension support if extensions = .delete(:extensions) extensions.each do |ext, args| add_extension ext, *args end end @options = { :session_key => 'rack.session', :openid_param => 'openid_identifier', #:return_to, :login_good, :login_fail, :login_quit #:no_session, :auth_fail, :error :store => OIDStore, :immediate => false, :anonymous => false, :catch_errors => false }.merge() @extensions = {} end |
Instance Attribute Details
#extensions ⇒ Object (readonly)
Returns the value of attribute extensions.
182 183 184 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 182 def extensions @extensions end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
182 183 184 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 182 def @options end |
Instance Method Details
#add_extension(ext, *args) ⇒ Object
The first argument should be the main extension module. The extension module should contain the constants:
* class Request, with OpenID::Extension as an ancestor
* class Response, with OpenID::Extension as an ancestor
* string NS_URI, which defines the namespace of the extension, should
be an absolute http uri
All trailing arguments will be passed to extension::Request.new in #check. The openid response will be passed to extension::Response#from_success_response, #get_extension_args will be called on the result to attain the gathered data.
This method returns the key at which the response data will be found in the session, which is the namespace uri by default.
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 402 def add_extension ext, *args if not ext.is_a? Module raise TypeError, "#{ext.inspect} is not a module" elsif !(m = %w'Request Response NS_URI' - ext.constants.map{ |c| c.to_s }).empty? raise ArgumentError, "#{ext.inspect} missing #{m*', '}" end consts = [ext::Request, ext::Response] if not consts.all?{|c| c.is_a? Class } raise TypeError, "#{ext.inspect}'s Request or Response is not a class" elsif not consts.all?{|c| ::OpenID::Extension > c } raise ArgumentError, "#{ext.inspect}'s Request or Response not a decendant of OpenID::Extension" end if not ext::NS_URI.is_a? String raise TypeError, "#{ext.inspect}'s NS_URI is not a string" elsif not uri = URI(ext::NS_URI) raise ArgumentError, "#{ext.inspect}'s NS_URI is not a valid uri" elsif not uri.scheme =~ /^https?$/ raise ArgumentError, "#{ext.inspect}'s NS_URI is not an http uri" elsif not uri.absolute? raise ArgumentError, "#{ext.inspect}'s NS_URI is not and absolute uri" end @extensions[ext] = args return ext::NS_URI end |
#call(env) ⇒ Object
It sets up and uses session data at :openid
within the session. It sets up the ::OpenID::Consumer using the store specified by options[:store]
.
If the parameter specified by options[:openid_param]
is present, processing is passed to #check and the result is returned.
If the parameter ‘openid.mode’ is set, implying a followup from the openid server, processing is passed to #finish and the result is returned.
If neither of these conditions are met, a 400 error is returned.
If an error is thrown and options[:catch_errors]
is false, the exception will be reraised. Otherwise a 500 error is returned.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 199 def call(env) env['rack.auth.openid'] = self session = env[@options[:session_key]] unless session and session.is_a? Hash raise(NoSession, 'No compatible session') end # let us work in our own namespace... session = (session[:openid] ||= {}) unless session and session.is_a? Hash raise(NoSession, 'Incompatible session') end request = Rack::Request.new env consumer = ::OpenID::Consumer.new session, @options[:store] if request.params['openid.mode'] finish consumer, session, request elsif request.params[@options[:openid_param]] check consumer, session, request else env['rack.errors'].puts "No valid params provided." bad_request end rescue NoSession env['rack.errors'].puts($!., *$@) @options. ### Missing or incompatible session fetch :no_session, [ 500, {'Content-Type'=>'text/plain'}, $!. ] rescue env['rack.errors'].puts($!., *$@) if not @options[:catch_error] raise($!) end @options. fetch :error, [ 500, {'Content-Type'=>'text/plain'}, 'OpenID has encountered an error.' ] end |
#check(consumer, session, req) ⇒ Object
As the first part of OpenID consumer action, #check retrieves the data required for completion.
-
session[:openid][:openid_param]
is set to the submitted identifier to be authenticated. -
session[:openid][:site_return]
is set as the request’s HTTP_REFERER, unless already set. -
env['rack.auth.openid.request']
is the openid checkid request instance.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 250 def check(consumer, session, req) session[:openid_param] = req.params[@options[:openid_param]] oid = consumer.begin(session[:openid_param], @options[:anonymous]) pp oid if $DEBUG req.env['rack.auth.openid.request'] = oid session[:site_return] ||= req.env['HTTP_REFERER'] # SETUP_NEEDED check! # see OpenID::Consumer::CheckIDRequest docs query_args = [@realm, *@options.values_at(:return_to, :immediate)] query_args[1] ||= req.url query_args[2] = false if session.key? :setup_needed pp query_args if $DEBUG ## Extension support extensions.each do |ext,args| oid.add_extension ext::Request.new(*args) end if oid.send_redirect?(*query_args) redirect = oid.redirect_url(*query_args) if $DEBUG pp redirect pp Rack::Utils.parse_query(URI(redirect).query) end [ 303, {'Location'=>redirect}, [] ] else # check on 'action' option. formbody = oid.form_markup(*query_args) if $DEBUG pp formbody end body = HTML % ['Confirm...', formbody] [ 200, {'Content-Type'=>'text/html'}, body.to_a ] end rescue ::OpenID::DiscoveryFailure => e # thrown from inside OpenID::Consumer#begin by yadis stuff req.env['rack.errors'].puts($!., *$@) @options. ### Foreign server failed fetch :auth_fail, [ 503, {'Content-Type'=>'text/plain'}, 'Foreign server failure.' ] end |
#extension_namespaces ⇒ Object
A conveniance method that returns the namespace of all current extensions used by this instance.
433 434 435 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 433 def extension_namespaces @extensions.keys.map{|e|e::NS_URI} end |
#finish(consumer, session, req) ⇒ Object
This is the final portion of authentication. Unless any errors outside of specification occur, a 303 redirect will be returned with Location determined by the OpenID response type. If none of the response type :login_* urls are set, the redirect will be set to session[:openid][:site_return]
. If session[:openid][:site_return]
is unset, the realm will be used.
Any messages from OpenID’s response are appended to the 303 response body.
Data gathered from extensions are stored in session with the extension’s namespace uri as the key.
-
env['rack.auth.openid.response']
is the openid response.
The four valid possible outcomes are:
-
failure:
options[:login_fail]
orsession[:site_return]
or the realm-
session[:openid]
is cleared and any messages are send to rack.errors -
session[:openid]['authenticated']
isfalse
-
-
success:
options[:login_good]
orsession[:site_return]
or the realm-
session[:openid]
is cleared -
session[:openid]['authenticated']
istrue
-
session[:openid]['identity']
is the actual identifier -
session[:openid]['identifier']
is the pretty identifier
-
-
cancel:
options[:login_good]
orsession[:site_return]
or the realm-
session[:openid]
is cleared -
session[:openid]['authenticated']
isfalse
-
-
setup_needed: resubmits the authentication request. A flag is set for non-immediate handling.
-
session[:openid][:setup_needed]
is set totrue
, which will prevent immediate style openid authentication.
-
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/gems/rack-0.9.1/lib/rack/auth/openid.rb', line 332 def finish(consumer, session, req) oid = consumer.complete(req.params, req.url) pp oid if $DEBUG req.env['rack.auth.openid.response'] = oid goto = session.fetch :site_return, @realm body = [] case oid.status when ::OpenID::Consumer::FAILURE session.clear session['authenticated'] = false req.env['rack.errors'].puts oid. goto = @options[:login_fail] if @options.key? :login_fail body << "Authentication unsuccessful.\n" when ::OpenID::Consumer::SUCCESS session.clear ## Extension support extensions.each do |ext, args| session[ext::NS_URI] = ext::Response. from_success_response(oid). get_extension_args end session['authenticated'] = true # Value for unique identification and such session['identity'] = oid.identity_url # Value for display and UI labels session['identifier'] = oid.display_identifier goto = @options[:login_good] if @options.key? :login_good body << "Authentication successful.\n" when ::OpenID::Consumer::CANCEL session.clear session['authenticated'] = false goto = @options[:login_fail] if @options.key? :login_fail body << "Authentication cancelled.\n" when ::OpenID::Consumer::SETUP_NEEDED session[:setup_needed] = true unless o_id = session[:openid_param] raise('Required values missing.') end goto = req.script_name+ '?'+@options[:openid_param]+ '='+o_id body << "Reauthentication required.\n" end body << oid. if oid. [ 303, {'Location'=>goto}, body] end |