diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-04-22 15:26:03 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-04-22 15:26:03 +0100 |
commit | 5f3f100ce2d689480da85abc88e5e940cf90189e (patch) | |
tree | 15c1a05a5308a9eea56d7f0889ac46d9cac5b57c /actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth | |
parent | d758d996d1b66e2a65640f79f01ce2ac674d7ed5 (diff) | |
parent | ca49299434bc764b667cd86846d892e91a150ef3 (diff) | |
download | rails-5f3f100ce2d689480da85abc88e5e940cf90189e.tar.gz rails-5f3f100ce2d689480da85abc88e5e940cf90189e.tar.bz2 rails-5f3f100ce2d689480da85abc88e5e940cf90189e.zip |
Merge branch 'master' into active_model
Conflicts:
activeresource/lib/active_resource/validations.rb
Diffstat (limited to 'actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth')
8 files changed, 882 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/handler.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/handler.rb new file mode 100644 index 0000000000..214df6299e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/handler.rb @@ -0,0 +1,37 @@ +module Rack + module Auth + # Rack::Auth::AbstractHandler implements common authentication functionality. + # + # +realm+ should be set for all handlers. + + class AbstractHandler + + attr_accessor :realm + + def initialize(app, realm=nil, &authenticator) + @app, @realm, @authenticator = app, realm, authenticator + end + + + private + + def unauthorized(www_authenticate = challenge) + return [ 401, + { 'Content-Type' => 'text/plain', + 'Content-Length' => '0', + 'WWW-Authenticate' => www_authenticate.to_s }, + [] + ] + end + + def bad_request + return [ 400, + { 'Content-Type' => 'text/plain', + 'Content-Length' => '0' }, + [] + ] + end + + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/request.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/request.rb new file mode 100644 index 0000000000..1d9ccec685 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/abstract/request.rb @@ -0,0 +1,37 @@ +module Rack + module Auth + class AbstractRequest + + def initialize(env) + @env = env + end + + def provided? + !authorization_key.nil? + end + + def parts + @parts ||= @env[authorization_key].split(' ', 2) + end + + def scheme + @scheme ||= parts.first.downcase.to_sym + end + + def params + @params ||= parts.last + end + + + private + + AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] + + def authorization_key + @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } + end + + end + + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/basic.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/basic.rb new file mode 100644 index 0000000000..9557224648 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/basic.rb @@ -0,0 +1,58 @@ +require 'rack/auth/abstract/handler' +require 'rack/auth/abstract/request' + +module Rack + module Auth + # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. + # + # Initialize with the Rack application that you want protecting, + # and a block that checks if a username and password pair are valid. + # + # See also: <tt>example/protectedlobster.rb</tt> + + class Basic < AbstractHandler + + def call(env) + auth = Basic::Request.new(env) + + return unauthorized unless auth.provided? + + return bad_request unless auth.basic? + + if valid?(auth) + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + + unauthorized + end + + + private + + def challenge + 'Basic realm="%s"' % realm + end + + def valid?(auth) + @authenticator.call(*auth.credentials) + end + + class Request < Auth::AbstractRequest + def basic? + :basic == scheme + end + + def credentials + @credentials ||= params.unpack("m*").first.split(/:/, 2) + end + + def username + credentials.first + end + end + + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/md5.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/md5.rb new file mode 100644 index 0000000000..e579dc9632 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/md5.rb @@ -0,0 +1,124 @@ +require 'rack/auth/abstract/handler' +require 'rack/auth/digest/request' +require 'rack/auth/digest/params' +require 'rack/auth/digest/nonce' +require 'digest/md5' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of + # HTTP Digest Authentication, as per RFC 2617. + # + # Initialize with the [Rack] application that you want protecting, + # and a block that looks up a plaintext password for a given username. + # + # +opaque+ needs to be set to a constant base64/hexadecimal string. + # + class MD5 < AbstractHandler + + attr_accessor :opaque + + attr_writer :passwords_hashed + + def initialize(*args) + super + @passwords_hashed = nil + end + + def passwords_hashed? + !!@passwords_hashed + end + + def call(env) + auth = Request.new(env) + + unless auth.provided? + return unauthorized + end + + if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) + return bad_request + end + + if valid?(auth) + if auth.nonce.stale? + return unauthorized(challenge(:stale => true)) + else + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + end + + unauthorized + end + + + private + + QOP = 'auth'.freeze + + def params(hash = {}) + Params.new do |params| + params['realm'] = realm + params['nonce'] = Nonce.new.to_s + params['opaque'] = H(opaque) + params['qop'] = QOP + + hash.each { |k, v| params[k] = v } + end + end + + def challenge(hash = {}) + "Digest #{params(hash)}" + end + + def valid?(auth) + valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) + end + + def valid_qop?(auth) + QOP == auth.qop + end + + def valid_opaque?(auth) + H(opaque) == auth.opaque + end + + def valid_nonce?(auth) + auth.nonce.valid? + end + + def valid_digest?(auth) + digest(auth, @authenticator.call(auth.username)) == auth.response + end + + def md5(data) + ::Digest::MD5.hexdigest(data) + end + + alias :H :md5 + + def KD(secret, data) + H([secret, data] * ':') + end + + def A1(auth, password) + [ auth.username, auth.realm, password ] * ':' + end + + def A2(auth) + [ auth.method, auth.uri ] * ':' + end + + def digest(auth, password) + password_hash = passwords_hashed? ? password : H(A1(auth, password)) + + KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':') + end + + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/nonce.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/nonce.rb new file mode 100644 index 0000000000..dbe109f29a --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/nonce.rb @@ -0,0 +1,51 @@ +require 'digest/md5' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::Nonce is the default nonce generator for the + # Rack::Auth::Digest::MD5 authentication handler. + # + # +private_key+ needs to set to a constant string. + # + # +time_limit+ can be optionally set to an integer (number of seconds), + # to limit the validity of the generated nonces. + + class Nonce + + class << self + attr_accessor :private_key, :time_limit + end + + def self.parse(string) + new(*string.unpack("m*").first.split(' ', 2)) + end + + def initialize(timestamp = Time.now, given_digest = nil) + @timestamp, @given_digest = timestamp.to_i, given_digest + end + + def to_s + [([ @timestamp, digest ] * ' ')].pack("m*").strip + end + + def digest + ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':') + end + + def valid? + digest == @given_digest + end + + def stale? + !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit + end + + def fresh? + !stale? + end + + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/params.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/params.rb new file mode 100644 index 0000000000..730e2efdc8 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/params.rb @@ -0,0 +1,55 @@ +module Rack + module Auth + module Digest + class Params < Hash + + def self.parse(str) + split_header_value(str).inject(new) do |header, param| + k, v = param.split('=', 2) + header[k] = dequote(v) + header + end + end + + def self.dequote(str) # From WEBrick::HTTPUtils + ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup + ret.gsub!(/\\(.)/, "\\1") + ret + end + + def self.split_header_value(str) + str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] } + end + + def initialize + super + + yield self if block_given? + end + + def [](k) + super k.to_s + end + + def []=(k, v) + super k.to_s, v.to_s + end + + UNQUOTED = ['qop', 'nc', 'stale'] + + def to_s + inject([]) do |parts, (k, v)| + parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v)) + parts + end.join(', ') + end + + def quote(str) # From WEBrick::HTTPUtils + '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' + end + + end + end + end +end + diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/request.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/request.rb new file mode 100644 index 0000000000..a8aa3bf996 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/digest/request.rb @@ -0,0 +1,40 @@ +require 'rack/auth/abstract/request' +require 'rack/auth/digest/params' +require 'rack/auth/digest/nonce' + +module Rack + module Auth + module Digest + class Request < Auth::AbstractRequest + + def method + @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD'] + end + + def digest? + :digest == scheme + end + + def correct_uri? + (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri + end + + def nonce + @nonce ||= Nonce.parse(params['nonce']) + end + + def params + @params ||= Params.parse(parts.last) + end + + def method_missing(sym) + if params.has_key? key = sym.to_s + return params[key] + end + super + end + + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/openid.rb b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/openid.rb new file mode 100644 index 0000000000..c5f6a5143e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-1.0/rack/auth/openid.rb @@ -0,0 +1,480 @@ +# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net + +gem 'ruby-openid', '~> 2' if defined? Gem +require 'rack/request' +require 'rack/utils' +require 'rack/auth/abstract/handler' +require 'uri' +require 'openid' #gem +require 'openid/extension' #gem +require 'openid/store/memory' #gem + +module Rack + class Request + def openid_request + @env['rack.auth.openid.request'] + end + + def openid_response + @env['rack.auth.openid.response'] + end + end + + module Auth + + # Rack::Auth::OpenID provides a simple method for setting up an OpenID + # Consumer. 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 http://openidenabled.com/ruby-openid/. + # + # The OpenID specifications can be found at + # http://openid.net/specs/openid-authentication-1_1.html + # and + # http://openid.net/specs/openid-authentication-2_0.html. Documentation + # for published OpenID extensions and related topics can be found at + # http://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 + # Consumer functionality. + # + # This library strongly intends to utilize the OpenID 2.0 features of the + # ruby-openid library, which provides OpenID 1.0 compatiblity. + # + # NOTE: Due to the amount of data that this library stores in the + # session, Rack::Session::Cookie may fault. + + class OpenID + + class NoSession < RuntimeError; end + class BadExtension < RuntimeError; end + # Required for ruby-openid + ValidStatus = [:success, :setup_needed, :cancel, :failure] + + # = Arguments + # + # The first argument is the realm, identifying the site they are trusting + # with their identity. This is required, also treated as the trust_root + # in OpenID 1.x exchanges. + # + # The optional second argument is a hash of options. + # + # == Options + # + # <tt>:return_to</tt> 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 <tt>:return_to</tt> is not + # provided, return_to will be the current url which allows flexibility + # with caveats. + # + # <tt>:session_key</tt> defines the key to the session hash in the env. + # It defaults to 'rack.session'. + # + # <tt>:openid_param</tt> 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'. + # + # <tt>:store</tt> defined what OpenID Store to use for persistant + # information. By default a Store::Memory will be used. + # + # <tt>:immediate</tt> as true will make initial requests to be of an + # immediate type. This is false by default. See OpenID specification + # documentation. + # + # <tt>:extensions</tt> 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. + # + # == Examples + # + # simple_oid = OpenID.new('http://mysite.com/') + # + # return_oid = OpenID.new('http://mysite.com/', { + # :return_to => 'http://mysite.com/openid' + # }) + # + # complex_oid = OpenID.new('http://mysite.com/', + # :immediate => true, + # :extensions => { + # ::OpenID::SReg => [['email'],['nickname']] + # } + # ) + # + # = Advanced + # + # Most of the functionality of this library is encapsulated such that + # expansion and overriding functions isn't difficult nor tricky. + # Alternately, to avoid opening up singleton objects or subclassing, a + # wrapper rack middleware can be composed to act upon Auth::OpenID's + # responses. See #check and #finish for locations of pertinent data. + # + # == Responses + # + # To change the responses that Auth::OpenID returns, override the methods + # #redirect, #bad_request, #unauthorized, #access_denied, and + # #foreign_server_failure. + # + # Additionally #confirm_post_params is used when the URI would exceed + # length limits on a GET request when doing the initial verification + # request. + # + # == Processing + # + # To change methods of processing completed transactions, override the + # methods #success, #setup_needed, #cancel, and #failure. Please ensure + # the returned object is a rack compatible response. + # + # The first argument is an OpenID::Response, the second is a + # Rack::Request of the current request, the last is the hash used in + # ruby-openid handling, which can be found manually at + # env['rack.session'][:openid]. + # + # This is useful if you wanted to expand the processing done, such as + # setting up user accounts. + # + # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to + # def oid_app.success oid, request, session + # user = Models::User[oid.identity_url] + # user ||= Models::User.create_from_openid oid + # request['rack.session'][:user] = user.id + # redirect MyApp.site_home + # end + # + # site_map['/openid'] = oid_app + # map = Rack::URLMap.new site_map + # ... + + def initialize(realm, options={}) + realm = URI(realm) + raise ArgumentError, "Invalid realm: #{realm}" \ + unless realm.absolute? \ + and realm.fragment.nil? \ + and realm.scheme =~ /^https?$/ \ + and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/ + realm.path = '/' if realm.path.empty? + @realm = realm.to_s + + if ruri = options[:return_to] + ruri = URI(ruri) + raise ArgumentError, "Invalid return_to: #{ruri}" \ + unless ruri.absolute? \ + and ruri.scheme =~ /^https?$/ \ + and ruri.fragment.nil? + raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \ + unless self.within_realm?(ruri) + @return_to = ruri.to_s + end + + @session_key = options[:session_key] || 'rack.session' + @openid_param = options[:openid_param] || 'openid_identifier' + @store = options[:store] || ::OpenID::Store::Memory.new + @immediate = !!options[:immediate] + + @extensions = {} + if extensions = options.delete(:extensions) + extensions.each do |ext, args| + add_extension ext, *args + end + end + + # Undocumented, semi-experimental + @anonymous = !!options[:anonymous] + end + + attr_reader :realm, :return_to, :session_key, :openid_param, :store, + :immediate, :extensions + + # Sets up and uses session data at <tt>:openid</tt> within the session. + # Errors in this setup will raise a NoSession exception. + # + # If the parameter 'openid.mode' is set, which implies a followup from + # the openid server, processing is passed to #finish and the result is + # returned. However, if there is no appropriate openid information in the + # session, a 400 error is returned. + # + # If the parameter specified by <tt>options[:openid_param]</tt> is + # present, processing is passed to #check and the result is returned. + # + # If neither of these conditions are met, #unauthorized is called. + + def call(env) + env['rack.auth.openid'] = self + env_session = env[@session_key] + unless env_session and env_session.is_a?(Hash) + raise NoSession, 'No compatible session' + end + # let us work in our own namespace... + session = (env_session[:openid] ||= {}) + unless session and session.is_a?(Hash) + raise NoSession, 'Incompatible openid session' + end + + request = Rack::Request.new(env) + consumer = ::OpenID::Consumer.new(session, @store) + + if mode = request.GET['openid.mode'] + if session.key?(:openid_param) + finish(consumer, session, request) + else + bad_request + end + elsif request.GET[@openid_param] + check(consumer, session, request) + else + unauthorized + end + end + + # As the first part of OpenID consumer action, #check retrieves the data + # required for completion. + # + # If all parameters fit within the max length of a URI, a 303 redirect + # will be returned. Otherwise #confirm_post_params will be called. + # + # Any messages from OpenID's request are logged to env['rack.errors'] + # + # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request + # instance. + # + # <tt>session[:openid_param]</tt> is set to the openid identifier + # provided by the user. + # + # <tt>session[:return_to]</tt> is set to the return_to uri given to the + # identity provider. + + def check(consumer, session, req) + oid = consumer.begin(req.GET[@openid_param], @anonymous) + req.env['rack.auth.openid.request'] = oid + req.env['rack.errors'].puts(oid.message) + p oid if $DEBUG + + ## Extension support + extensions.each do |ext,args| + oid.add_extension(ext::Request.new(*args)) + end + + session[:openid_param] = req.GET[openid_param] + return_to_uri = return_to ? return_to : req.url + session[:return_to] = return_to_uri + immediate = session.key?(:setup_needed) ? false : immediate + + if oid.send_redirect?(realm, return_to_uri, immediate) + uri = oid.redirect_url(realm, return_to_uri, immediate) + redirect(uri) + else + confirm_post_params(oid, realm, return_to_uri, immediate) + end + rescue ::OpenID::DiscoveryFailure => e + # thrown from inside OpenID::Consumer#begin by yadis stuff + req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n") + return foreign_server_failure + end + + # This is the final portion of authentication. + # If successful, a redirect to the realm is be returned. + # Data gathered from extensions are stored in session[:openid] with the + # extension's namespace uri as the key. + # + # Any messages from OpenID's response are logged to env['rack.errors'] + # + # <tt>env['rack.auth.openid.response']</tt> will contain the openid + # response. + + def finish(consumer, session, req) + oid = consumer.complete(req.GET, req.url) + req.env['rack.auth.openid.response'] = oid + req.env['rack.errors'].puts(oid.message) + p oid if $DEBUG + + raise unless ValidStatus.include?(oid.status) + __send__(oid.status, oid, req, session) + end + + # The first argument should be the main extension module. + # The extension module should contain the constants: + # * class Request, should have OpenID::Extension as an ancestor + # * class Response, should have OpenID::Extension as an ancestor + # * string NS_URI, which defining the namespace of the extension + # + # 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. + + def add_extension(ext, *args) + raise BadExtension unless valid_extension?(ext) + extensions[ext] = args + return ext::NS_URI + end + + # Checks the validitity, in the context of usage, of a submitted + # extension. + + def valid_extension?(ext) + if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) } + raise ArgumentError, 'Extension is missing constants.' + elsif not ext::Response.respond_to?(:from_success_response) + raise ArgumentError, 'Response is missing required method.' + end + return true + rescue + return false + end + + # Checks the provided uri to ensure it'd be considered within the realm. + # is currently not compatible with wildcard realms. + + def within_realm? uri + uri = URI.parse(uri.to_s) + realm = URI.parse(self.realm) + return false unless uri.absolute? + return false unless uri.path[0, realm.path.size] == realm.path + return false unless uri.host == realm.host or realm.host[/^\*\./] + # for wildcard support, is awkward with URI limitations + realm_match = Regexp.escape(realm.host). + sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$' + return false unless uri.host.match(realm_match) + return true + end + alias_method :include?, :within_realm? + + protected + + ### These methods define some of the boilerplate responses. + + # Returns an html form page for posting to an Identity Provider if the + # GET request would exceed the upper URI length limit. + + def confirm_post_params(oid, realm, return_to, immediate) + Rack::Response.new.finish do |r| + r.write '<html><head><title>Confirm...</title></head><body>' + r.write oid.form_markup(realm, return_to, immediate) + r.write '</body></html>' + end + end + + # Returns a 303 redirect with the destination of that provided by the + # argument. + + def redirect(uri) + [ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain', + 'Location' => uri}, + [] ] + end + + # Returns an empty 400 response. + + def bad_request + [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'}, + [''] ] + end + + # Returns a basic unauthorized 401 response. + + def unauthorized + [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'}, + ['Unauthorized.'] ] + end + + # Returns a basic access denied 403 response. + + def access_denied + [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'}, + ['Access denied.'] ] + end + + # Returns a 503 response to be used if communication with the remote + # OpenID server fails. + + def foreign_server_failure + [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'}, + ['Foreign server failure.'] ] + end + + private + + ### These methods are called after a transaction is completed, depending + # on its outcome. These should all return a rack compatible response. + # You'd want to override these to provide additional functionality. + + # Called to complete processing on a successful transaction. + # Within the openid session, :openid_identity and :openid_identifier are + # set to the user friendly and the standard representation of the + # validated identity. All other data in the openid session is cleared. + + def success(oid, request, session) + session.clear + session[:openid_identity] = oid.display_identifier + session[:openid_identifier] = oid.identity_url + extensions.keys.each do |ext| + label = ext.name[/[^:]+$/].downcase + response = ext::Response.from_success_response(oid) + session[label] = response.data + end + redirect(realm) + end + + # Called if the Identity Provider indicates further setup by the user is + # required. + # The identifier is retrived from the openid session at :openid_param. + # And :setup_needed is set to true to prevent looping. + + def setup_needed(oid, request, session) + identifier = session[:openid_param] + session[:setup_needed] = true + redirect req.script_name + '?' + openid_param + '=' + identifier + end + + # Called if the user indicates they wish to cancel identification. + # Data within openid session is cleared. + + def cancel(oid, request, session) + session.clear + access_denied + end + + # Called if the Identity Provider indicates the user is unable to confirm + # their identity. Data within the openid session is left alone, in case + # of swarm auth attacks. + + def failure(oid, request, session) + unauthorized + end + end + + # A class developed out of the request to use OpenID as an authentication + # middleware. The request will be sent to the OpenID instance unless the + # block evaluates to true. For example in rackup, you can use it as such: + # + # use Rack::Session::Pool + # use Rack::Auth::OpenIDAuth, realm, openid_options do |env| + # env['rack.session'][:authkey] == a_string + # end + # run RackApp + # + # Or simply: + # + # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth + + class OpenIDAuth < Rack::Auth::AbstractHandler + attr_reader :oid + def initialize(app, realm, options={}, &auth) + @oid = OpenID.new(realm, options) + super(app, &auth) + end + + def call(env) + to = auth.call(env) ? @app : @oid + to.call env + end + end + end +end |