Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/plugins/open_id_authentication/lib/open_id_authentication.rb

Revision 9246, 4.9 kB (checked in by josh, 5 months ago)

Fixed OpenID file store class and safely fallback if there is no root_url. Closes #11559. [markpercival]

Line 
1 require 'uri'
2 require 'openid/extensions/sreg'
3 require 'openid/store/filesystem'
4
5 module OpenIdAuthentication
6   OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
7
8   def self.store
9     @@store
10   end
11
12   def self.store=(value)
13     @@store = value
14   end
15
16   self.store = :db
17
18   def store
19     OpenIdAuthentication.store
20   end
21
22   class InvalidOpenId < StandardError
23   end
24
25   class Result
26     ERROR_MESSAGES = {
27       :missing      => "Sorry, the OpenID server couldn't be found",
28       :canceled     => "OpenID verification was canceled",
29       :failed       => "OpenID verification failed",
30       :setup_needed => "OpenID verification needs setup"
31     }
32
33     def self.[](code)
34       new(code)
35     end
36
37     def initialize(code)
38       @code = code
39     end
40
41     def ===(code)
42       if code == :unsuccessful && unsuccessful?
43         true
44       else
45         @code == code
46       end
47     end
48
49     ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
50
51     def successful?
52       @code == :successful
53     end
54
55     def unsuccessful?
56       ERROR_MESSAGES.keys.include?(@code)
57     end
58
59     def message
60       ERROR_MESSAGES[@code]
61     end
62   end
63
64   def self.normalize_url(url)
65     uri = URI.parse(url.to_s.strip)
66     uri = URI.parse("http://#{uri}") unless uri.scheme
67     uri.scheme = uri.scheme.downcase  # URI should do this
68     uri.normalize.to_s
69   rescue URI::InvalidURIError
70     raise InvalidOpenId.new("#{url} is not an OpenID URL")
71   end
72
73   protected
74     def normalize_url(url)
75       OpenIdAuthentication.normalize_url(url)
76     end
77
78     # The parameter name of "openid_url" is used rather than the Rails convention "open_id_url"
79     # because that's what the specification dictates in order to get browser auto-complete working across sites
80     def using_open_id?(identity_url = params[:openid_url]) #:doc:
81       !identity_url.blank? || params[:open_id_complete]
82     end
83
84     def authenticate_with_open_id(identity_url = params[:openid_url], options = {}, &block) #:doc:
85       if params[:open_id_complete].nil?
86         begin_open_id_authentication(normalize_url(identity_url), options, &block)
87       else
88         complete_open_id_authentication(&block)
89       end
90     end
91
92   private
93     def begin_open_id_authentication(identity_url, options = {})
94       return_to = options.delete(:return_to)
95       open_id_request = open_id_consumer.begin(identity_url)
96       add_simple_registration_fields(open_id_request, options)
97       redirect_to(open_id_redirect_url(open_id_request, return_to))
98     rescue OpenID::OpenIDError, Timeout::Error => e
99       logger.error("[OPENID] #{e}")
100       yield Result[:missing], identity_url, nil
101     end
102
103     def complete_open_id_authentication
104       params_with_path = params.reject { |key, value| request.path_parameters[key] }
105       params_with_path.delete(:format)
106       open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
107       identity_url     = normalize_url(open_id_response.endpoint.claimed_id) if open_id_response.endpoint.claimed_id
108
109       case open_id_response.status
110       when OpenID::Consumer::SUCCESS
111         yield Result[:successful], identity_url, OpenID::SReg::Response.from_success_response(open_id_response)
112       when OpenID::Consumer::CANCEL
113         yield Result[:canceled], identity_url, nil
114       when OpenID::Consumer::FAILURE
115         yield Result[:failed], identity_url, nil
116       when OpenID::Consumer::SETUP_NEEDED
117         yield Result[:setup_needed], open_id_response.setup_url, nil
118       end
119     end
120
121     def open_id_consumer
122       OpenID::Consumer.new(session, open_id_store)
123     end
124
125     def open_id_store
126       case store
127       when :db
128         OpenIdAuthentication::DbStore.new
129       when :file
130         OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
131       else
132         raise "Unknown store: #{store}"
133       end
134     end
135
136     def add_simple_registration_fields(open_id_request, fields)
137       sreg_request = OpenID::SReg::Request.new
138       sreg_request.request_fields(Array(fields[:required]).map(&:to_s), true) if fields[:required]
139       sreg_request.request_fields(Array(fields[:optional]).map(&:to_s), false) if fields[:optional]
140       sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
141       open_id_request.add_extension(sreg_request)
142     end
143
144     def open_id_redirect_url(open_id_request, return_to = nil)
145       open_id_request.return_to_args['open_id_complete'] = '1'
146       open_id_request.redirect_url(realm, return_to || requested_url)
147     end
148
149     def requested_url
150       "#{request.protocol + request.host_with_port + request.relative_url_root + request.path}"
151     end
152
153     def realm
154       respond_to?(:root_url) ? root_url : "#{request.protocol + request.host_with_port}"
155     end
156
157     def timeout_protection_from_identity_server
158       yield
159     rescue Timeout::Error
160       Class.new do
161         def status
162           OpenID::FAILURE
163         end
164
165         def msg
166           "Identity server timed out"
167         end
168       end.new
169     end
170 end
Note: See TracBrowser for help on using the browser.