C0 code coverage information
Generated on Fri Jul 11 15:55:31 -0700 2008 with rcov 0.7.0
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 require "openid/consumer/idres.rb"
2 require "openid/consumer/checkid_request.rb"
3 require "openid/consumer/associationmanager.rb"
4 require "openid/consumer/responses.rb"
5 require "openid/consumer/discovery_manager"
6 require "openid/consumer/discovery"
7 require "openid/message"
8 require "openid/yadis/discovery"
9 require "openid/store/nonce"
10
11 module OpenID
12 # OpenID support for Relying Parties (aka Consumers).
13 #
14 # This module documents the main interface with the OpenID consumer
15 # library. The only part of the library which has to be used and
16 # isn't documented in full here is the store required to create an
17 # Consumer instance.
18 #
19 # = OVERVIEW
20 #
21 # The OpenID identity verification process most commonly uses the
22 # following steps, as visible to the user of this library:
23 #
24 # 1. The user enters their OpenID into a field on the consumer's
25 # site, and hits a login button.
26 #
27 # 2. The consumer site discovers the user's OpenID provider using
28 # the Yadis protocol.
29 #
30 # 3. The consumer site sends the browser a redirect to the OpenID
31 # provider. This is the authentication request as described in
32 # the OpenID specification.
33 #
34 # 4. The OpenID provider's site sends the browser a redirect back to
35 # the consumer site. This redirect contains the provider's
36 # response to the authentication request.
37 #
38 # The most important part of the flow to note is the consumer's site
39 # must handle two separate HTTP requests in order to perform the
40 # full identity check.
41 #
42 # = LIBRARY DESIGN
43 #
44 # This consumer library is designed with that flow in mind. The
45 # goal is to make it as easy as possible to perform the above steps
46 # securely.
47 #
48 # At a high level, there are two important parts in the consumer
49 # library. The first important part is this module, which contains
50 # the interface to actually use this library. The second is
51 # openid/store/interface.rb, which describes the interface to use if
52 # you need to create a custom method for storing the state this
53 # library needs to maintain between requests.
54 #
55 # In general, the second part is less important for users of the
56 # library to know about, as several implementations are provided
57 # which cover a wide variety of situations in which consumers may
58 # use the library.
59 #
60 # The Consumer class has methods corresponding to the actions
61 # necessary in each of steps 2, 3, and 4 described in the overview.
62 # Use of this library should be as easy as creating an Consumer
63 # instance and calling the methods appropriate for the action the
64 # site wants to take.
65 #
66 # This library automatically detects which version of the OpenID
67 # protocol should be used for a transaction and constructs the
68 # proper requests and responses. Users of this library do not need
69 # to worry about supporting multiple protocol versions; the library
70 # supports them implicitly. Depending on the version of the
71 # protocol in use, the OpenID transaction may be more secure. See
72 # the OpenID specifications for more information.
73 #
74 # = SESSIONS, STORES, AND STATELESS MODE
75 #
76 # The Consumer object keeps track of two types of state:
77 #
78 # 1. State of the user's current authentication attempt. Things
79 # like the identity URL, the list of endpoints discovered for
80 # that URL, and in case where some endpoints are unreachable, the
81 # list of endpoints already tried. This state needs to be held
82 # from Consumer.begin() to Consumer.complete(), but it is only
83 # applicable to a single session with a single user agent, and at
84 # the end of the authentication process (i.e. when an OP replies
85 # with either <tt>id_res</tt>. or <tt>cancel</tt> it may be
86 # discarded.
87 #
88 # 2. State of relationships with servers, i.e. shared secrets
89 # (associations) with servers and nonces seen on signed messages.
90 # This information should persist from one session to the next
91 # and should not be bound to a particular user-agent.
92 #
93 # These two types of storage are reflected in the first two
94 # arguments of Consumer's constructor, <tt>session</tt> and
95 # <tt>store</tt>. <tt>session</tt> is a dict-like object and we
96 # hope your web framework provides you with one of these bound to
97 # the user agent. <tt>store</tt> is an instance of Store.
98 #
99 # Since the store does hold secrets shared between your application
100 # and the OpenID provider, you should be careful about how you use
101 # it in a shared hosting environment. If the filesystem or database
102 # permissions of your web host allow strangers to read from them, do
103 # not store your data there! If you have no safe place to store
104 # your data, construct your consumer with nil for the store, and it
105 # will operate only in stateless mode. Stateless mode may be
106 # slower, put more load on the OpenID provider, and trusts the
107 # provider to keep you safe from replay attacks.
108 #
109 # Several store implementation are provided, and the interface is
110 # fully documented so that custom stores can be used as well. See
111 # the documentation for the Consumer class for more information on
112 # the interface for stores. The implementations that are provided
113 # allow the consumer site to store the necessary data in several
114 # different ways, including several SQL databases and normal files
115 # on disk.
116 #
117 # = IMMEDIATE MODE
118 #
119 # In the flow described above, the user may need to confirm to the
120 # OpenID provider that it's ok to disclose his or her identity. The
121 # provider may draw pages asking for information from the user
122 # before it redirects the browser back to the consumer's site. This
123 # is generally transparent to the consumer site, so it is typically
124 # ignored as an implementation detail.
125 #
126 # There can be times, however, where the consumer site wants to get
127 # a response immediately. When this is the case, the consumer can
128 # put the library in immediate mode. In immediate mode, there is an
129 # extra response possible from the server, which is essentially the
130 # server reporting that it doesn't have enough information to answer
131 # the question yet.
132 #
133 # = USING THIS LIBRARY
134 #
135 # Integrating this library into an application is usually a
136 # relatively straightforward process. The process should basically
137 # follow this plan:
138 #
139 # Add an OpenID login field somewhere on your site. When an OpenID
140 # is entered in that field and the form is submitted, it should make
141 # a request to the your site which includes that OpenID URL.
142 #
143 # First, the application should instantiate a Consumer with a
144 # session for per-user state and store for shared state using the
145 # store of choice.
146 #
147 # Next, the application should call the <tt>begin</tt> method of
148 # Consumer instance. This method takes the OpenID URL as entered by
149 # the user. The <tt>begin</tt> method returns a CheckIDRequest
150 # object.
151 #
152 # Next, the application should call the redirect_url method on the
153 # CheckIDRequest object. The parameter <tt>return_to</tt> is the
154 # URL that the OpenID server will send the user back to after
155 # attempting to verify his or her identity. The <tt>realm</tt>
156 # parameter is the URL (or URL pattern) that identifies your web
157 # site to the user when he or she is authorizing it. Send a
158 # redirect to the resulting URL to the user's browser.
159 #
160 # That's the first half of the authentication process. The second
161 # half of the process is done after the user's OpenID Provider sends
162 # the user's browser a redirect back to your site to complete their
163 # login.
164 #
165 # When that happens, the user will contact your site at the URL
166 # given as the <tt>return_to</tt> URL to the redirect_url call made
167 # above. The request will have several query parameters added to
168 # the URL by the OpenID provider as the information necessary to
169 # finish the request.
170 #
171 # Get a Consumer instance with the same session and store as before
172 # and call its complete() method, passing in all the received query
173 # arguments and URL currently being handled.
174 #
175 # There are multiple possible return types possible from that
176 # method. These indicate the whether or not the login was
177 # successful, and include any additional information appropriate for
178 # their type.
179 class Consumer
180 attr_accessor :session_key_prefix
181
182 # Initialize a Consumer instance.
183 #
184 # You should create a new instance of the Consumer object with
185 # every HTTP request that handles OpenID transactions.
186 #
187 # session: the session object to use to store request information.
188 # The session should behave like a hash.
189 #
190 # store: an object that implements the interface in Store.
191 def initialize(session, store)
192 @session = session
193 @store = store
194 @session_key_prefix = 'OpenID::Consumer::'
195 end
196
197 # Start the OpenID authentication process. See steps 1-2 in the
198 # overview for the Consumer class.
199 #
200 # user_url: Identity URL given by the user. This method performs a
201 # textual transformation of the URL to try and make sure it is
202 # normalized. For example, a user_url of example.com will be
203 # normalized to http://example.com/ normalizing and resolving any
204 # redirects the server might issue.
205 #
206 # anonymous: A boolean value. Whether to make an anonymous
207 # request of the OpenID provider. Such a request does not ask for
208 # an authorization assertion for an OpenID identifier, but may be
209 # used with extensions to pass other data. e.g. "I don't care who
210 # you are, but I'd like to know your time zone."
211 #
212 # Returns a CheckIDRequest object containing the discovered
213 # information, with a method for building a redirect URL to the
214 # server, as described in step 3 of the overview. This object may
215 # also be used to add extension arguments to the request, using
216 # its add_extension_arg method.
217 #
218 # Raises DiscoveryFailure when no OpenID server can be found for
219 # this URL.
220 def begin(openid_identifier, anonymous=false)
221 manager = discovery_manager(openid_identifier)
222 service = manager.get_next_service(&method(:discover))
223
224 if service.nil?
225 raise DiscoveryFailure.new("No usable OpenID services were found "\
226 "for #{openid_identifier.inspect}", nil)
227 else
228 begin_without_discovery(service, anonymous)
229 end
230 end
231
232 # Start OpenID verification without doing OpenID server
233 # discovery. This method is used internally by Consumer.begin()
234 # after discovery is performed, and exists to provide an interface
235 # for library users needing to perform their own discovery.
236 #
237 # service: an OpenID service endpoint descriptor. This object and
238 # factories for it are found in the openid/consumer/discovery.rb
239 # module.
240 #
241 # Returns an OpenID authentication request object.
242 def begin_without_discovery(service, anonymous)
243 assoc = association_manager(service).get_association
244 checkid_request = CheckIDRequest.new(assoc, service)
245 checkid_request.anonymous = anonymous
246
247 if service.compatibility_mode
248 rt_args = checkid_request.return_to_args
249 rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce
250 rt_args[Consumer.openid1_return_to_claimed_id_name] =
251 service.claimed_id
252 end
253
254 self.last_requested_endpoint = service
255 return checkid_request
256 end
257
258 # Called to interpret the server's response to an OpenID
259 # request. It is called in step 4 of the flow described in the
260 # Consumer overview.
261 #
262 # query: A hash of the query parameters for this HTTP request.
263 # Note that in rails, this is <b>not</b> <tt>params</tt> but
264 # <tt>params.reject{|k,v|request.path_parameters[k]}</tt>
265 # because <tt>controller</tt> and <tt>action</tt> and other
266 # "path parameters" are included in params.
267 #
268 # current_url: Extract the URL of the current request from your
269 # application's web request framework and specify it here to have it
270 # checked against the openid.return_to value in the response. Do not
271 # just pass <tt>args['openid.return_to']</tt> here; that will defeat the
272 # purpose of this check. (See OpenID Authentication 2.0 section 11.1.)
273 #
274 # If the return_to URL check fails, the status of the completion will be
275 # FAILURE.
276
277 #
278 # Returns a subclass of Response. The type of response is
279 # indicated by the status attribute, which will be one of
280 # SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
281 def complete(query, current_url)
282 message = Message.from_post_args(query)
283 mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
284 begin
285 meth = method('complete_' + mode)
286 rescue NameError
287 meth = method(:complete_invalid)
288 end
289 response = meth.call(message, current_url)
290 cleanup_last_requested_endpoint
291 if [SUCCESS, CANCEL].member?(response.status)
292 cleanup_session
293 end
294 return response
295 end
296
297 protected
298
299 def session_get(name)
300 @session[session_key(name)]
301 end
302
303 def session_set(name, val)
304 @session[session_key(name)] = val
305 end
306
307 def session_key(suffix)
308 @session_key_prefix + suffix
309 end
310
311 def last_requested_endpoint
312 session_get('last_requested_endpoint')
313 end
314
315 def last_requested_endpoint=(endpoint)
316 session_set('last_requested_endpoint', endpoint)
317 end
318
319 def cleanup_last_requested_endpoint
320 @session[session_key('last_requested_endpoint')] = nil
321 end
322
323 def discovery_manager(openid_identifier)
324 DiscoveryManager.new(@session, openid_identifier, @session_key_prefix)
325 end
326
327 def cleanup_session
328 discovery_manager(nil).cleanup(true)
329 end
330
331
332 def discover(identifier)
333 OpenID.discover(identifier)
334 end
335
336 def negotiator
337 DefaultNegotiator
338 end
339
340 def association_manager(service)
341 AssociationManager.new(@store, service.server_url,
342 service.compatibility_mode, negotiator)
343 end
344
345 def handle_idres(message, current_url)
346 IdResHandler.new(message, current_url, @store, last_requested_endpoint)
347 end
348
349 def complete_invalid(message, unused_return_to)
350 mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')
351 return FailureResponse.new(last_requested_endpoint,
352 "Invalid openid.mode: #{mode}")
353 end
354
355 def complete_cancel(unused_message, unused_return_to)
356 return CancelResponse.new(last_requested_endpoint)
357 end
358
359 def complete_error(message, unused_return_to)
360 error = message.get_arg(OPENID_NS, 'error')
361 contact = message.get_arg(OPENID_NS, 'contact')
362 reference = message.get_arg(OPENID_NS, 'reference')
363
364 return FailureResponse.new(last_requested_endpoint,
365 error, contact, reference)
366 end
367
368 def complete_setup_needed(message, unused_return_to)
369 if message.is_openid1
370 return complete_invalid(message, nil)
371 else
372 setup_url = message.get_arg(OPENID2_NS, 'user_setup_url')
373 return SetupNeededResponse.new(last_requested_endpoint, setup_url)
374 end
375 end
376
377 def complete_id_res(message, current_url)
378 if message.is_openid1
379 setup_url = message.get_arg(OPENID1_NS, 'user_setup_url')
380 if !setup_url.nil?
381 return SetupNeededResponse.new(last_requested_endpoint, setup_url)
382 end
383 end
384
385 begin
386 idres = handle_idres(message, current_url)
387 rescue OpenIDError => why
388 return FailureResponse.new(last_requested_endpoint, why.message)
389 else
390 return SuccessResponse.new(idres.endpoint, message,
391 idres.signed_fields)
392 end
393 end
394 end
395 end
Generated using the rcov code coverage analysis tool for Ruby version 0.7.0.