annotate vendor/plugins/open_id_authentication/lib/open_id_authentication.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 1d32c0a0efbf
rev   line source
Chris@0 1 require 'uri'
Chris@0 2 require 'openid/extensions/sreg'
Chris@0 3 require 'openid/extensions/ax'
Chris@0 4 require 'openid/store/filesystem'
Chris@0 5
Chris@0 6 require File.dirname(__FILE__) + '/open_id_authentication/db_store'
Chris@0 7 require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
Chris@0 8 require File.dirname(__FILE__) + '/open_id_authentication/request'
Chris@0 9 require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
Chris@0 10
Chris@0 11 module OpenIdAuthentication
Chris@0 12 OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
Chris@0 13
Chris@0 14 def self.store
Chris@0 15 @@store
Chris@0 16 end
Chris@0 17
Chris@0 18 def self.store=(*store_option)
Chris@0 19 store, *parameters = *([ store_option ].flatten)
Chris@0 20
Chris@0 21 @@store = case store
Chris@0 22 when :db
Chris@0 23 OpenIdAuthentication::DbStore.new
Chris@0 24 when :mem_cache
Chris@0 25 OpenIdAuthentication::MemCacheStore.new(*parameters)
Chris@0 26 when :file
Chris@0 27 OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
Chris@0 28 else
Chris@0 29 raise "Unknown store: #{store}"
Chris@0 30 end
Chris@0 31 end
Chris@0 32
Chris@0 33 self.store = :db
Chris@0 34
Chris@0 35 class InvalidOpenId < StandardError
Chris@0 36 end
Chris@0 37
Chris@0 38 class Result
Chris@0 39 ERROR_MESSAGES = {
Chris@0 40 :missing => "Sorry, the OpenID server couldn't be found",
Chris@0 41 :invalid => "Sorry, but this does not appear to be a valid OpenID",
Chris@0 42 :canceled => "OpenID verification was canceled",
Chris@0 43 :failed => "OpenID verification failed",
Chris@0 44 :setup_needed => "OpenID verification needs setup"
Chris@0 45 }
Chris@0 46
Chris@0 47 def self.[](code)
Chris@0 48 new(code)
Chris@0 49 end
Chris@0 50
Chris@0 51 def initialize(code)
Chris@0 52 @code = code
Chris@0 53 end
Chris@0 54
Chris@0 55 def status
Chris@0 56 @code
Chris@0 57 end
Chris@0 58
Chris@0 59 ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
Chris@0 60
Chris@0 61 def successful?
Chris@0 62 @code == :successful
Chris@0 63 end
Chris@0 64
Chris@0 65 def unsuccessful?
Chris@0 66 ERROR_MESSAGES.keys.include?(@code)
Chris@0 67 end
Chris@0 68
Chris@0 69 def message
Chris@0 70 ERROR_MESSAGES[@code]
Chris@0 71 end
Chris@0 72 end
Chris@0 73
Chris@0 74 # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
Chris@0 75 def self.normalize_identifier(identifier)
Chris@0 76 # clean up whitespace
Chris@0 77 identifier = identifier.to_s.strip
Chris@0 78
Chris@0 79 # if an XRI has a prefix, strip it.
Chris@0 80 identifier.gsub!(/xri:\/\//i, '')
Chris@0 81
Chris@0 82 # dodge XRIs -- TODO: validate, don't just skip.
Chris@0 83 unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
Chris@0 84 # does it begin with http? if not, add it.
Chris@0 85 identifier = "http://#{identifier}" unless identifier =~ /^http/i
Chris@0 86
Chris@0 87 # strip any fragments
Chris@0 88 identifier.gsub!(/\#(.*)$/, '')
Chris@0 89
Chris@0 90 begin
Chris@0 91 uri = URI.parse(identifier)
Chris@0 92 uri.scheme = uri.scheme.downcase # URI should do this
Chris@0 93 identifier = uri.normalize.to_s
Chris@0 94 rescue URI::InvalidURIError
Chris@0 95 raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
Chris@0 96 end
Chris@0 97 end
Chris@0 98
Chris@0 99 return identifier
Chris@0 100 end
Chris@0 101
Chris@0 102 # deprecated for OpenID 2.0, where not all OpenIDs are URLs
Chris@0 103 def self.normalize_url(url)
Chris@0 104 ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
Chris@0 105 self.normalize_identifier(url)
Chris@0 106 end
Chris@0 107
Chris@0 108 protected
Chris@0 109 def normalize_url(url)
Chris@0 110 OpenIdAuthentication.normalize_url(url)
Chris@0 111 end
Chris@0 112
Chris@0 113 def normalize_identifier(url)
Chris@0 114 OpenIdAuthentication.normalize_identifier(url)
Chris@0 115 end
Chris@0 116
Chris@0 117 # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
Chris@0 118 # because that's what the specification dictates in order to get browser auto-complete working across sites
Chris@0 119 def using_open_id?(identity_url = nil) #:doc:
Chris@0 120 identity_url ||= params[:openid_identifier] || params[:openid_url]
Chris@0 121 !identity_url.blank? || params[:open_id_complete]
Chris@0 122 end
Chris@0 123
Chris@0 124 def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
Chris@0 125 identity_url ||= params[:openid_identifier] || params[:openid_url]
Chris@0 126
Chris@0 127 if params[:open_id_complete].nil?
Chris@0 128 begin_open_id_authentication(identity_url, options, &block)
Chris@0 129 else
Chris@0 130 complete_open_id_authentication(&block)
Chris@0 131 end
Chris@0 132 end
Chris@0 133
Chris@0 134 private
Chris@0 135 def begin_open_id_authentication(identity_url, options = {})
Chris@0 136 identity_url = normalize_identifier(identity_url)
Chris@0 137 return_to = options.delete(:return_to)
Chris@0 138 method = options.delete(:method)
Chris@0 139
Chris@0 140 options[:required] ||= [] # reduces validation later
Chris@0 141 options[:optional] ||= []
Chris@0 142
Chris@0 143 open_id_request = open_id_consumer.begin(identity_url)
Chris@0 144 add_simple_registration_fields(open_id_request, options)
Chris@0 145 add_ax_fields(open_id_request, options)
Chris@0 146 redirect_to(open_id_redirect_url(open_id_request, return_to, method))
Chris@0 147 rescue OpenIdAuthentication::InvalidOpenId => e
Chris@0 148 yield Result[:invalid], identity_url, nil
Chris@0 149 rescue OpenID::OpenIDError, Timeout::Error => e
Chris@0 150 logger.error("[OPENID] #{e}")
Chris@0 151 yield Result[:missing], identity_url, nil
Chris@0 152 end
Chris@0 153
Chris@0 154 def complete_open_id_authentication
Chris@0 155 params_with_path = params.reject { |key, value| request.path_parameters[key] }
Chris@0 156 params_with_path.delete(:format)
Chris@0 157 open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
Chris@0 158 identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
Chris@0 159
Chris@0 160 case open_id_response.status
Chris@0 161 when OpenID::Consumer::SUCCESS
Chris@0 162 profile_data = {}
Chris@0 163
Chris@0 164 # merge the SReg data and the AX data into a single hash of profile data
Chris@0 165 [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
Chris@0 166 if data_response.from_success_response( open_id_response )
Chris@0 167 profile_data.merge! data_response.from_success_response( open_id_response ).data
Chris@0 168 end
Chris@0 169 end
Chris@0 170
Chris@0 171 yield Result[:successful], identity_url, profile_data
Chris@0 172 when OpenID::Consumer::CANCEL
Chris@0 173 yield Result[:canceled], identity_url, nil
Chris@0 174 when OpenID::Consumer::FAILURE
Chris@0 175 yield Result[:failed], identity_url, nil
Chris@0 176 when OpenID::Consumer::SETUP_NEEDED
Chris@0 177 yield Result[:setup_needed], open_id_response.setup_url, nil
Chris@0 178 end
Chris@0 179 end
Chris@0 180
Chris@0 181 def open_id_consumer
Chris@0 182 OpenID::Consumer.new(session, OpenIdAuthentication.store)
Chris@0 183 end
Chris@0 184
Chris@0 185 def add_simple_registration_fields(open_id_request, fields)
Chris@0 186 sreg_request = OpenID::SReg::Request.new
Chris@0 187
Chris@0 188 # filter out AX identifiers (URIs)
Chris@0 189 required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
Chris@0 190 optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
Chris@0 191
Chris@0 192 sreg_request.request_fields(required_fields, true) unless required_fields.blank?
Chris@0 193 sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
Chris@0 194 sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
Chris@0 195 open_id_request.add_extension(sreg_request)
Chris@0 196 end
Chris@0 197
Chris@0 198 def add_ax_fields( open_id_request, fields )
Chris@0 199 ax_request = OpenID::AX::FetchRequest.new
Chris@0 200
Chris@0 201 # look through the :required and :optional fields for URIs (AX identifiers)
Chris@0 202 fields[:required].each do |f|
Chris@0 203 next unless f =~ /^https?:\/\//
Chris@0 204 ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
Chris@0 205 end
Chris@0 206
Chris@0 207 fields[:optional].each do |f|
Chris@0 208 next unless f =~ /^https?:\/\//
Chris@0 209 ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
Chris@0 210 end
Chris@0 211
Chris@0 212 open_id_request.add_extension( ax_request )
Chris@0 213 end
Chris@0 214
Chris@0 215 def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
Chris@0 216 open_id_request.return_to_args['_method'] = (method || request.method).to_s
Chris@0 217 open_id_request.return_to_args['open_id_complete'] = '1'
Chris@0 218 open_id_request.redirect_url(root_url, return_to || requested_url)
Chris@0 219 end
Chris@0 220
Chris@0 221 def requested_url
Chris@0 222 relative_url_root = self.class.respond_to?(:relative_url_root) ?
Chris@0 223 self.class.relative_url_root.to_s :
Chris@0 224 request.relative_url_root
Chris@0 225 "#{request.protocol}#{request.host_with_port}#{relative_url_root}#{request.path}"
Chris@0 226 end
Chris@0 227
Chris@0 228 def timeout_protection_from_identity_server
Chris@0 229 yield
Chris@0 230 rescue Timeout::Error
Chris@0 231 Class.new do
Chris@0 232 def status
Chris@0 233 OpenID::FAILURE
Chris@0 234 end
Chris@0 235
Chris@0 236 def msg
Chris@0 237 "Identity server timed out"
Chris@0 238 end
Chris@0 239 end.new
Chris@0 240 end
Chris@0 241 end