ruby-openid 1.1.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. data/INSTALL +0 -9
  2. data/README +21 -22
  3. data/UPGRADE +117 -0
  4. data/admin/runtests.rb +36 -0
  5. data/examples/README +13 -21
  6. data/examples/active_record_openid_store/README +8 -3
  7. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +4 -8
  8. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  9. data/examples/active_record_openid_store/lib/association.rb +2 -0
  10. data/examples/active_record_openid_store/lib/openid_ar_store.rb +22 -47
  11. data/examples/active_record_openid_store/test/store_test.rb +78 -48
  12. data/examples/discover +46 -0
  13. data/examples/{rails_server → rails_openid}/README +0 -0
  14. data/examples/{rails_server → rails_openid}/Rakefile +0 -0
  15. data/examples/{rails_server → rails_openid}/app/controllers/application.rb +0 -0
  16. data/examples/rails_openid/app/controllers/consumer_controller.rb +115 -0
  17. data/examples/{rails_server → rails_openid}/app/controllers/login_controller.rb +10 -2
  18. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  19. data/examples/{rails_server → rails_openid}/app/helpers/application_helper.rb +0 -0
  20. data/examples/{rails_server → rails_openid}/app/helpers/login_helper.rb +0 -0
  21. data/examples/{rails_server → rails_openid}/app/helpers/server_helper.rb +0 -0
  22. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  23. data/examples/rails_openid/app/views/consumer/start.rhtml +8 -0
  24. data/examples/{rails_server → rails_openid}/app/views/layouts/server.rhtml +0 -0
  25. data/examples/{rails_server → rails_openid}/app/views/login/index.rhtml +1 -1
  26. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  27. data/examples/{rails_server → rails_openid}/config/boot.rb +0 -0
  28. data/examples/{rails_server → rails_openid}/config/database.yml +0 -0
  29. data/examples/{rails_server → rails_openid}/config/environment.rb +0 -0
  30. data/examples/{rails_server → rails_openid}/config/environments/development.rb +0 -0
  31. data/examples/{rails_server → rails_openid}/config/environments/production.rb +0 -0
  32. data/examples/{rails_server → rails_openid}/config/environments/test.rb +0 -0
  33. data/examples/{rails_server → rails_openid}/config/routes.rb +2 -1
  34. data/examples/{rails_server → rails_openid}/doc/README_FOR_APP +0 -0
  35. data/examples/{rails_server → rails_openid}/public/404.html +0 -0
  36. data/examples/{rails_server → rails_openid}/public/500.html +0 -0
  37. data/examples/{rails_server → rails_openid}/public/dispatch.cgi +0 -0
  38. data/examples/{rails_server → rails_openid}/public/dispatch.fcgi +0 -0
  39. data/examples/{rails_server → rails_openid}/public/dispatch.rb +0 -0
  40. data/examples/{rails_server → rails_openid}/public/favicon.ico +0 -0
  41. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  42. data/examples/{rails_server → rails_openid}/public/javascripts/controls.js +0 -0
  43. data/examples/{rails_server → rails_openid}/public/javascripts/dragdrop.js +0 -0
  44. data/examples/{rails_server → rails_openid}/public/javascripts/effects.js +0 -0
  45. data/examples/{rails_server → rails_openid}/public/javascripts/prototype.js +0 -0
  46. data/examples/{rails_server → rails_openid}/public/robots.txt +0 -0
  47. data/examples/{rails_server → rails_openid}/script/about +0 -0
  48. data/examples/{rails_server → rails_openid}/script/breakpointer +0 -0
  49. data/examples/{rails_server → rails_openid}/script/console +0 -0
  50. data/examples/{rails_server → rails_openid}/script/destroy +0 -0
  51. data/examples/{rails_server → rails_openid}/script/generate +0 -0
  52. data/examples/{rails_server → rails_openid}/script/performance/benchmarker +0 -0
  53. data/examples/{rails_server → rails_openid}/script/performance/profiler +0 -0
  54. data/examples/{rails_server → rails_openid}/script/plugin +0 -0
  55. data/examples/{rails_server → rails_openid}/script/process/reaper +0 -0
  56. data/examples/{rails_server → rails_openid}/script/process/spawner +0 -0
  57. data/examples/{rails_server → rails_openid}/script/process/spinner +0 -0
  58. data/examples/{rails_server → rails_openid}/script/runner +0 -0
  59. data/examples/{rails_server → rails_openid}/script/server +0 -0
  60. data/examples/{rails_server → rails_openid}/test/functional/login_controller_test.rb +0 -0
  61. data/examples/{rails_server → rails_openid}/test/functional/server_controller_test.rb +0 -0
  62. data/examples/{rails_server → rails_openid}/test/test_helper.rb +0 -0
  63. data/lib/{hmac.rb → hmac/hmac.rb} +0 -0
  64. data/lib/{hmac-sha1.rb → hmac/sha1.rb} +1 -1
  65. data/lib/{hmac-sha2.rb → hmac/sha2.rb} +1 -1
  66. data/lib/openid/association.rb +213 -73
  67. data/lib/openid/consumer/associationmanager.rb +338 -0
  68. data/lib/openid/consumer/checkid_request.rb +175 -0
  69. data/lib/openid/consumer/discovery.rb +480 -0
  70. data/lib/openid/consumer/discovery_manager.rb +123 -0
  71. data/lib/openid/consumer/html_parse.rb +136 -0
  72. data/lib/openid/consumer/idres.rb +525 -0
  73. data/lib/openid/consumer/responses.rb +133 -0
  74. data/lib/openid/consumer.rb +280 -807
  75. data/lib/openid/cryptutil.rb +85 -0
  76. data/lib/openid/dh.rb +60 -23
  77. data/lib/openid/extension.rb +31 -0
  78. data/lib/openid/extensions/ax.rb +506 -0
  79. data/lib/openid/extensions/pape.rb +182 -0
  80. data/lib/openid/extensions/sreg.rb +275 -0
  81. data/lib/openid/extras.rb +11 -0
  82. data/lib/openid/fetchers.rb +132 -93
  83. data/lib/openid/kvform.rb +133 -0
  84. data/lib/openid/kvpost.rb +56 -0
  85. data/lib/openid/message.rb +534 -0
  86. data/lib/openid/protocolerror.rb +6 -0
  87. data/lib/openid/server.rb +1215 -666
  88. data/lib/openid/store/filesystem.rb +271 -0
  89. data/lib/openid/store/interface.rb +75 -0
  90. data/lib/openid/store/memory.rb +84 -0
  91. data/lib/openid/store/nonce.rb +68 -0
  92. data/lib/openid/trustroot.rb +314 -87
  93. data/lib/openid/urinorm.rb +37 -34
  94. data/lib/openid/util.rb +42 -220
  95. data/lib/openid/yadis/accept.rb +148 -0
  96. data/lib/openid/yadis/constants.rb +21 -0
  97. data/lib/openid/yadis/discovery.rb +153 -0
  98. data/lib/openid/yadis/filters.rb +205 -0
  99. data/lib/openid/{htmltokenizer.rb → yadis/htmltokenizer.rb} +1 -54
  100. data/lib/openid/yadis/parsehtml.rb +36 -0
  101. data/lib/openid/yadis/services.rb +42 -0
  102. data/lib/openid/yadis/xrds.rb +171 -0
  103. data/lib/openid/yadis/xri.rb +90 -0
  104. data/lib/openid/yadis/xrires.rb +106 -0
  105. data/lib/openid.rb +1 -4
  106. data/test/data/accept.txt +124 -0
  107. data/test/data/dh.txt +29 -0
  108. data/test/data/example-xrds.xml +14 -0
  109. data/test/data/linkparse.txt +587 -0
  110. data/test/data/n2b64 +650 -0
  111. data/test/data/test1-discover.txt +137 -0
  112. data/test/data/test1-parsehtml.txt +128 -0
  113. data/test/data/test_discover/openid.html +11 -0
  114. data/test/data/test_discover/openid2.html +11 -0
  115. data/test/data/test_discover/openid2_xrds.xml +12 -0
  116. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  117. data/test/data/test_discover/openid_1_and_2.html +11 -0
  118. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  119. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  120. data/test/data/test_discover/openid_and_yadis.html +12 -0
  121. data/test/data/test_discover/openid_no_delegate.html +10 -0
  122. data/test/data/test_discover/yadis_0entries.xml +12 -0
  123. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  124. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  125. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  126. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  127. data/test/data/test_discover/yadis_idp.xml +12 -0
  128. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  129. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  130. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  131. data/test/data/test_xrds/README +12 -0
  132. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  133. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  134. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  135. data/test/data/test_xrds/no-xrd.xml +7 -0
  136. data/test/data/test_xrds/not-xrds.xml +2 -0
  137. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  138. data/test/data/test_xrds/ref.xrds +109 -0
  139. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  140. data/test/data/test_xrds/spoof1.xrds +25 -0
  141. data/test/data/test_xrds/spoof2.xrds +25 -0
  142. data/test/data/test_xrds/spoof3.xrds +37 -0
  143. data/test/data/test_xrds/status222.xrds +9 -0
  144. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  145. data/test/data/trustroot.txt +147 -0
  146. data/test/discoverdata.rb +131 -0
  147. data/test/test_accept.rb +170 -0
  148. data/test/test_association.rb +266 -0
  149. data/test/test_associationmanager.rb +899 -0
  150. data/test/test_ax.rb +587 -0
  151. data/test/test_checkid_request.rb +297 -0
  152. data/test/test_consumer.rb +257 -0
  153. data/test/test_cryptutil.rb +117 -0
  154. data/test/test_dh.rb +86 -0
  155. data/test/test_discover.rb +772 -0
  156. data/test/test_discovery_manager.rb +262 -0
  157. data/test/test_extras.rb +35 -0
  158. data/test/test_fetchers.rb +472 -0
  159. data/test/test_filters.rb +270 -0
  160. data/test/test_idres.rb +816 -0
  161. data/test/test_kvform.rb +165 -0
  162. data/test/test_kvpost.rb +65 -0
  163. data/test/test_linkparse.rb +101 -0
  164. data/test/test_message.rb +1058 -0
  165. data/test/test_nonce.rb +89 -0
  166. data/test/test_openid_yadis.rb +178 -0
  167. data/test/test_pape.rb +233 -0
  168. data/test/test_parsehtml.rb +80 -0
  169. data/test/test_responses.rb +63 -0
  170. data/test/test_server.rb +2270 -0
  171. data/test/test_sreg.rb +479 -0
  172. data/test/test_stores.rb +269 -0
  173. data/test/test_trustroot.rb +112 -0
  174. data/test/{urinorm.rb → test_urinorm.rb} +6 -3
  175. data/test/test_util.rb +144 -0
  176. data/test/test_xrds.rb +160 -0
  177. data/test/test_xri.rb +48 -0
  178. data/test/test_xrires.rb +63 -0
  179. data/test/test_yadis_discovery.rb +207 -0
  180. data/test/testutil.rb +116 -0
  181. data/test/util.rb +47 -50
  182. metadata +233 -143
  183. data/examples/consumer.rb +0 -290
  184. data/examples/rails_openid_login_generator/openid_login_generator-0.1.gem +0 -0
  185. data/examples/rails_server/app/controllers/server_controller.rb +0 -190
  186. data/examples/rails_server/app/views/server/decide.rhtml +0 -11
  187. data/examples/rails_server/public/images/rails.png +0 -0
  188. data/lib/hmac-md5.rb +0 -11
  189. data/lib/hmac-rmd160.rb +0 -11
  190. data/lib/openid/discovery.rb +0 -122
  191. data/lib/openid/filestore.rb +0 -315
  192. data/lib/openid/parse.rb +0 -23
  193. data/lib/openid/service.rb +0 -147
  194. data/lib/openid/stores.rb +0 -178
  195. data/test/assoc.rb +0 -38
  196. data/test/consumer.rb +0 -376
  197. data/test/data/brian.xrds +0 -16
  198. data/test/data/brianellin.mylid.xrds +0 -42
  199. data/test/dh.rb +0 -20
  200. data/test/extensions.rb +0 -30
  201. data/test/linkparse.rb +0 -305
  202. data/test/runtests.rb +0 -22
  203. data/test/server2.rb +0 -1053
  204. data/test/service.rb +0 -47
  205. data/test/storetestcase.rb +0 -172
  206. data/test/teststore.rb +0 -47
  207. data/test/trustroot.rb +0 -117
@@ -1,120 +1,124 @@
1
- require "uri"
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"
2
10
 
3
- require "openid/util"
4
- require "openid/dh"
5
- require "openid/fetchers"
6
- require "openid/association"
7
- require "openid/discovery"
8
-
9
-
10
- # Everything in this library exists within the OpenID Module. Users of
11
- # the library should look at OpenID::Consumer and/or OpenID::Server
12
11
  module OpenID
13
-
14
- # ==Overview
12
+ # OpenID support for Relying Parties (aka Consumers).
15
13
  #
16
- # Brief terminology:
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.
17
18
  #
18
- # [+Consumer+]
19
- # The website wanting to verify an OpenID identity URL. Sometimes
20
- # called a "relying party". If you want people to log into your site
21
- # using OpenID, then you are the consumer.
22
- #
23
- # [+Server+]
24
- # The website which makes assertions as to whether or not the user
25
- # at the end of the browser owns the URL they say they do.
19
+ # = OVERVIEW
26
20
  #
27
- # [+Redirect+]
28
- # An HTTP 302 (Temporarily Moved) redirect. When issued as an HTTP
29
- # response from the server, the browser changes it's location to the
30
- # value specified.
31
- #
32
- # The OpenID authentication process requires the following steps,
33
- # as visible to the user of this library:
21
+ # The OpenID identity verification process most commonly uses the
22
+ # following steps, as visible to the user of this library:
34
23
  #
35
24
  # 1. The user enters their OpenID into a field on the consumer's
36
25
  # site, and hits a login button.
37
26
  #
38
- # 2. The consumer site discovers the user's OpenID server information using
39
- # the Yadis protocol (Potentially falling back to OpenID 1.0 "linkrel"
40
- # discovery).
27
+ # 2. The consumer site discovers the user's OpenID provider using
28
+ # the Yadis protocol.
41
29
  #
42
- # 3. The consumer site prepares a URL to be sent to the server
43
- # which contains the OpenID autentication information, and
44
- # issues a redirect user's browser.
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.
45
33
  #
46
- # 4. The server then verifies that the user owns the URL
47
- # provided, and sends the browser a redirect
48
- # back to the consumer. This redirect contains the
49
- # server's response to the authentication request.
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.
50
37
  #
51
38
  # The most important part of the flow to note is the consumer's site
52
39
  # must handle two separate HTTP requests in order to perform the
53
- # full identity check. These two HTTP requests are described in
54
- # steps 1 and 4 above, and are handled by Consumer.begin and
55
- # Consumer.complete respectively.
56
- #
40
+ # full identity check.
57
41
  #
58
- # ==Consumer Library Design
42
+ # = LIBRARY DESIGN
59
43
  #
60
- # The library is designed with the above flow in mind. The
44
+ # This consumer library is designed with that flow in mind. The
61
45
  # goal is to make it as easy as possible to perform the above steps
62
46
  # securely.
63
47
  #
64
48
  # At a high level, there are two important parts in the consumer
65
- # library. The first important part is the OpenID::Consumer class,
66
- # which contains the public interface to the consumer logic.
67
- # The second is the OpenID::Store class, which defines the
68
- # interface needed to store the state the consumer needs to maintain
69
- # between requests.
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.
70
54
  #
71
55
  # In general, the second part is less important for users of the
72
- # library to know about, as several concrete store implementations are
73
- # provided. The user simply needs to choose the store which best fits
74
- # their environment and requirements.
75
- #
76
- #
77
- # ==Stores and Dumb Mode
78
- #
79
- # OpenID is a protocol that works best when the consumer site is
80
- # able to store some state. This is the normal mode of operation
81
- # for the protocol, and is sometimes referred to as smart mode.
82
- # There is also a fallback mode, known as dumb mode, which is
83
- # available when the consumer site is not able to store state. This
84
- # mode should be avoided when possible, as it leaves the
85
- # implementation more vulnerable to replay attacks.
86
- #
87
- # The mode the library works in for normal operation is determined
88
- # by the store that it is given. The store is an abstraction that
89
- # handles the data that the consumer needs to manage between HTTP
90
- # requests in order to operate efficiently and securely.
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.
91
108
  #
92
109
  # Several store implementation are provided, and the interface is
93
- # fully documented so that custom stores can be used as well. The
94
- # implementations that are provided allow the consumer site to store
95
- # data in a couple of different ways: in the filesystem,
96
- # or in an SQL database.
97
- #
98
- # There is an additional concrete store provided that puts the
99
- # consumer in dumb mode. This is not recommended, as it removes the
100
- # library's ability to stop replay attacks reliably. It still uses
101
- # time-based checking to make replay attacks only possible within a
102
- # small window, but they remain possible within that window. This
103
- # store should only be used if the consumer site has no way to
104
- # retain data between requests at all. See DumbStore for more info.
105
- #
106
- # If your ennvironment permits, use of the FilesystemStore
107
- # is recommended.
108
- #
109
- #
110
- # ==Immediate Mode
111
- #
112
- # If you are new to OpenID, it is suggested that you skip this section
113
- # and refer to it later. Immediate mode is an advanced consumer topic.
114
- #
115
- # In the flow described in the overview, the user may need to confirm to the
116
- # identity server that it's ok to authorize his or her identity.
117
- # The server may draw pages asking for information from the user
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
118
122
  # before it redirects the browser back to the consumer's site. This
119
123
  # is generally transparent to the consumer site, so it is typically
120
124
  # ignored as an implementation detail.
@@ -124,794 +128,263 @@ module OpenID
124
128
  # put the library in immediate mode. In immediate mode, there is an
125
129
  # extra response possible from the server, which is essentially the
126
130
  # server reporting that it doesn't have enough information to answer
127
- # the question yet. In addition to saying that, the identity server
128
- # provides a URL to which the user can be sent to provide the needed
129
- # information and let the server finish handling the original
130
- # request.
131
- #
132
- # You may invoke immediate mode when building the redirect URL to the
133
- # OpenID server in the SuccessRequest.redirect_url method. Pass true
134
- # for the +immediate+ paramter. Read the interface for Consumer.complete
135
- # for information about handling the additional response.
131
+ # the question yet.
136
132
  #
137
- # ==Using the Library
133
+ # = USING THIS LIBRARY
138
134
  #
139
- # Integrating this library into an application is a
140
- # relatively straightforward process. The process usually follows this plan:
135
+ # Integrating this library into an application is usually a
136
+ # relatively straightforward process. The process should basically
137
+ # follow this plan:
141
138
  #
142
139
  # Add an OpenID login field somewhere on your site. When an OpenID
143
140
  # is entered in that field and the form is submitted, it should make
144
- # a request to the site which includes that OpenID URL.
145
- #
146
- # When your site receives that request, it should create an
147
- # OpenID::Consumer instance, and call
148
- # OpenID::Consumer.begin. If begin completes successfully,
149
- # it will return a SuccessRequest object. Otherwise it will subclass
150
- # of OpenIDStatus which contains additional information about the
151
- # the failure.
152
- #
153
- # If successful, build a redirect URL to the server by calling
154
- # SuccessRequest.redirect_url and send back an HTTP 302 redirect
155
- # of that URL to the user's browser. The redirect_url accepts a
156
- # return_to parameter, which is the URL to which they will return
157
- # to fininsh the OpenID transaction. This URL is supplied by you,
158
- # and should be able to handle step 4 of the flow described in the
159
- # overview.
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.
160
159
  #
161
160
  # That's the first half of the authentication process. The second
162
- # half of the process is done after the OpenID server sends the
163
- # user's browser a redirect back to your site with the
164
- # authentication response.
165
- #
166
- # When that happens, the browser will make a request to the return_to
167
- # URL you provided to the SuccessRequest.redirect_url
168
- # method. The request will have several query parameters added
169
- # to the URL by the identity server as the information necessary to
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
170
169
  # finish the request.
171
170
  #
172
- # Your job here is to make sure that the action performed at the return_to
173
- # URL creates an instnce of OpenID::Consumer, and calls the Consumer.complete
174
- # method. This call will
175
- # return a SuccessResponse object, or a subclass of OpenIDStatus explaining,
176
- # the failure. See the documentation for Consumer.complete
177
- # for a full explanation of the possible responses.
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 the <tt>return_to</tt> URL mentioned above.
178
174
  #
179
- # If you received a SuccessResponse, you may access the identity URL
180
- # of the user though it's +identity_url+ method.
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.
181
179
  class Consumer
182
-
183
- @@service_key = '_openid_consumer_service'
184
- @@disco_suffix = 'xopenid_services'
185
- attr_accessor :consumer, :session, :fetcher
180
+ attr_accessor :session_key_prefix
186
181
 
187
- # Creates a new OpenID::Consumer instance. You should create a new
188
- # instance of the Consumer object with every HTTP request that handles
189
- # OpenID transactions. Do not store the instance of it in a
190
- # global variable somewhere.
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.
191
186
  #
192
- # [+session+]
193
- # A hash-like object representing the user's session data. This is
194
- # used for keeping state of the OpenID transaction when the user is
195
- # redirected to the server. In a rails application, the controller's
196
- # @session instance variable should be used.
187
+ # session: the session object to use to store request information.
188
+ # The session should behave like a hash.
197
189
  #
198
- # [+store+]
199
- # This must be an object that implements the OpenID::Store interface.
200
- # Several concrete implementations are provided, to cover
201
- # most common use cases. We recommend using the simple file based
202
- # store bundled with the library: OpenID::FilesystemStore.
203
- #
204
- # [+fetcher+]
205
- # Optional. If provided, this must be an instance that implements
206
- # OpenID::Fetcher interface. If no fetcher is provided,
207
- # an OpenID::StandardFetcher instance will be created
208
- # for you automatically. If you need custom fetcher behavior, it
209
- # is probably best to subclass StandardFetcher, and pass your instance
210
- # in here.
211
- #
212
- # This object keeps an internal instance of OpenID::GenericConsumer
213
- # for low level OpenID calls, called +consumer+. You may use a custom
214
- # certificate authority PEM file for veryifying HTTPS server certs
215
- # by calling the GenericConsumer.ca_path= method of the +consumer+
216
- # instance variable.
217
- def initialize(session, store, fetcher=nil)
190
+ # store: an object that implements the interface in Store.
191
+ def initialize(session, store)
218
192
  @session = session
219
- @consumer = GenericConsumer.new(store, fetcher)
193
+ @store = store
194
+ @session_key_prefix = 'OpenID::Consumer::'
220
195
  end
221
196
 
222
- # +begin+ is called to start the OpenID verification process. See steps
223
- # 1-2 in the overview at the top of this file.
224
- #
225
- # ==Parameters
226
- #
227
- # [+user_url+]
228
- # Identity URL given by the user. +begin+ performs a textual
229
- # transformation of the URL to try and make sure it is "normalized",
230
- # for example, a user_url of example.com will be normalized to
231
- # http://example.com/ normalizing and resolving any redirects
232
- # the server might issue.
197
+ # Start the OpenID authentication process. See steps 1-2 in the
198
+ # overview for the Consumer class.
233
199
  #
234
- # ==Return Value
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.
235
205
  #
236
- # +begin+ returns a subclass of OpenIDStatus, which is an object
237
- # that has a +status+ method. The status methodfor this object will either
238
- # return OpenID::SUCCESS, or OpenID::FAILURE. Generally +begin+ will fail
239
- # if the users' OpenID page cannot be retrieved or OpenID server
240
- # information cannot be determined.
241
- #
242
- # ===Success
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."
243
211
  #
244
- # In the case that request.status equals OpenID::SUCCESS, the response
245
- # will be of type OpenID::SuccessRequest. The SuccessRequest object
246
- # may the be used to add simple registration extension arguments,
247
- # using SuccessRequest.add_extension_arg, and build the redirect
248
- # url to the server using SuccessRequest.redirect_url as described
249
- # in step 3 of the overview.
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.
250
217
  #
251
- # The next step in the success case is to actually build the redirect
252
- # URL to the server. Please see the documentation for
253
- # SuccessRequest.redirect_url for details. Once the redirect url
254
- # is created, you should issue an HTTP 302 temporary redirect to the
255
- # user's browser, sending her to the OpenID server. Once the user
256
- # finishes the operations on the server, she will be redirected back to
257
- # the return_to URL you passed to redirect_url, which should invoke
258
- # the Consumer.complete method.
259
- #
260
- # ===Failure
261
- #
262
- # If the library is unable to fetch the +user_url+, or no server
263
- # information can be determined, or if the server information is malformed,
264
- # +begin+ will return a FailureRequest object. The status method of this
265
- # object will return OpenID::FAILURE. FailureRequest objects have a
266
- # +msg+ method which provides more detailed information as to why
267
- # the request failed.
268
- def begin(user_url)
269
- discovery = self.get_discovery(user_url)
270
-
271
- unless discovery
272
- return FailureRequest.new("Don't know how to find services for that identifier")
273
- end
274
-
275
- service = discovery.next_service
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))
276
223
 
277
- unless service
278
- return FailureRequest.new('No service endpoints found.')
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)
279
229
  end
280
-
281
- return self.begin_without_discovery(service)
282
230
  end
283
231
 
284
- # Start the OpenID transaction without doing OpenID server
285
- # discovery. This method is used internally by Consumer.begin after
286
- # discovery is performed, and exists to provide an interface for library
287
- # users needing to perform their own discovery.
288
- #
289
- # ==Parameters
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.
290
236
  #
291
- # +service+ must be an OpenID::OpenIDServiceEnpoint object, or an object
292
- # that implements it's interface. You may produce these objects
293
- # and perform discovery manually using OpenID::OpenIDDiscovery.
237
+ # service: an OpenID service endpoint descriptor. This object and
238
+ # factories for it are found in the openid/consumer/discovery.rb
239
+ # module.
294
240
  #
295
- # ==Return Value
296
- #
297
- # +begin_without_discovery+ always returns an OpenID::SuccessRequest
298
- # object. Please see the success documentation for OpenID::Consumer.begin
299
- # for more information.
300
- def begin_without_discovery(service)
301
- request = @consumer.begin(service)
302
- @session[@@service_key] = service
303
- return request
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
304
256
  end
305
-
306
- # Called to interpret the server's response to an OpenID request. It
307
- # is called in step 4 of the flow described in the consumer overview.
308
- #
309
- # ==Parameters
310
- # [+query+]
311
- # A hash of the query paramters for this HTTP request.
312
- #
313
- # ==Return Value
314
- # Return value is a subclass of OpenIDStatus, and may have a status
315
- # of OpenID::SUCCESS, OpenID::CANCEL, OpenID::FAILURE,
316
- # or OpenID::SETUP_NEEDED. The status may be accessed through the
317
- # +status+ method of the response object.
318
- #
319
- # When OpenID::SUCCESS is returned, the response object will be of
320
- # type SuccessResponse, which has several useful attributes including
321
- # +identity_url+, +service+, and a method +extension_response+ for
322
- # extracting potential signed extension reponses from the server. See
323
- # the documentation for OpenID::SuccessResponse for more information
324
- # about it's interface and methods.
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.
325
261
  #
326
- # In the case of response.status being OpenID::CANCEL, the user cancelled
327
- # the OpenID transaction on the server. The response will be an instance
328
- # of OpenID::CancelResponse, and you may access the originally submitted
329
- # identity URL and service information through that object.
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.
330
267
  #
331
- # When status is OpenID::FAILURE, the object will be an instance of
332
- # OpenID::FailureResponse. If the identity which failed can be determined
333
- # it will be available by accessing the +identity_url+ attribute of the
334
- # response. FailureResponse objects also have a +msg+ attribute
335
- # which may be useful in debugging. If no msg is specified, msg will be
336
- # nil.
268
+ # return_to: The return URL used to invoke the application.
269
+ # Extract the URL from your application's web request framework
270
+ # and specify it here to have it checked against the
271
+ # openid.return_to value in the response. If the return_to URL
272
+ # check fails, the status of the completion will be FAILURE.
337
273
  #
338
- # When OpenID::SETUP_NEEDED is returned, the response object is an
339
- # instance of OpenID::SetupNeededResponse. The useful piece of information
340
- # contained in this response is the +setup_url+ method, which
341
- # should be used to send the user to the server and log in.
342
- # This response is only generated by immediate
343
- # mode requests (openid.mode=immediate). The user should be redirected
344
- # in to the +setup_url+, either in the current window or in a
345
- # new browser window.
346
- def complete(query)
347
- service = @session[@@service_key]
348
-
349
- if service.nil?
350
- resp = FailureResponse.new(nil, 'No session state found.')
351
- else
352
- resp = @consumer.complete(query, service)
274
+ # Returns a subclass of Response. The type of response is
275
+ # indicated by the status attribute, which will be one of
276
+ # SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
277
+ def complete(query, return_to)
278
+ message = Message.from_post_args(query)
279
+ mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
280
+ begin
281
+ meth = method('complete_' + mode)
282
+ rescue NameError
283
+ meth = method(:complete_invalid)
353
284
  end
354
-
355
- # If the response has an non-nil identity_url attribute,
356
- # then we can get te discovery object and finalize the request.
357
- # Otherwise it is a failure or replay, and we set the response
358
- # servce to nil.
359
- if resp.identity_url
360
- disco = self.get_discovery(resp.identity_url)
361
- if [SUCCESS, CANCEL].member?(resp.status)
362
- if resp.identity_url
363
- resp.service = disco.finish
364
- end
365
- else
366
- resp.service = disco.current
367
- end
368
- else
369
- resp.service = nil
285
+ response = meth.call(message, return_to)
286
+ cleanup_last_requested_endpoint
287
+ if [SUCCESS, CANCEL].member?(response.status)
288
+ cleanup_session
370
289
  end
371
-
372
-
373
- # want to delete service unless status is SETUP_NEEDED,
374
- # because we still need the service info when the user returns from
375
- # the server
376
- unless resp.status == SETUP_NEEDED
377
- @session[@@service_key] = nil
378
- end
379
-
380
- return resp
290
+ return response
381
291
  end
382
292
 
383
293
  protected
384
-
385
- # Used internally to create an instnace of the OpenIDDiscovery object.
386
- def get_discovery(url)
387
- if XRI::identifier_scheme(url) == :xri
388
- XRIDiscovery.new(@session, url, @@disco_suffix)
389
- else
390
- url = OpenID::Util.normalize_url(url)
391
- if url
392
- user_url = user_url.to_s
393
- OpenIDDiscovery.new(@session, url, @consumer.fetcher, @@disco_suffix)
394
- else
395
- nil
396
- end
397
- end
398
- end
399
- end
400
294
 
401
- # This class implements the common logic for OpenID consumers. It
402
- # is used by the higher level OpenID::Consumer class. Only advanced
403
- # users with special needs should ever have to look at this class.
404
- #
405
- # The only part of the library which has to be used and isn't
406
- # documented in full here is the store required to create an
407
- # OpenID::Consumer instance. More on the abstract store type and
408
- # concrete implementations of it that are provided in the documentation
409
- # of OpenID::Consumer.new
410
- class GenericConsumer
411
-
412
- # Number of characters to be used in generated nonces
413
- @@NONCE_LEN = 8
414
-
415
- # Nonce character set
416
- @@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
417
- @@D_SUFFIX = 'openid_disco'
418
-
419
- attr_reader :fetcher
420
-
421
- public
422
-
423
- # Creates a new Consumer instance. You should create a new
424
- # instance of the Consumer object with every HTTP request. Do not
425
- # store the instance of it in a global variable somewhere.
426
- #
427
- # [+store+]
428
- # This must be an object that implements the OpenID::Store interface.
429
- # Several concrete implementations are provided, to cover
430
- # most common use cases. We recommend using the simple file based
431
- # store bundled with the library: OpenID::FilesystemStore.
432
- #
433
- # [+fetcher+]
434
- # Optional. If provided, this must be an instance that implements
435
- # Fetcher interface. If no fetcher is provided,
436
- # an instance of OpenID::StandardFetcher will be created for
437
- # you automatically.
438
- def initialize(store, fetcher=nil)
439
- if fetcher.nil?
440
- fetcher = StandardFetcher.new
441
- end
442
- @store = store
443
- @fetcher = fetcher
444
- @ca_path = nil
445
- end
446
-
447
- # Set the path to a pem certificate authority file for verifying
448
- # server certificates during HTTPS. If you are interested in verifying
449
- # certs like the mozilla web browser, have a look at the files here:
450
- #
451
- # http://curl.haxx.se/docs/caextract.html
452
- def ca_path=(ca_path)
453
- ca_path = ca_path.to_s
454
- if File.exists?(ca_path)
455
- @ca_path = ca_path
456
- @fetcher.ca_path = ca_path
457
- else
458
- raise ArgumentError, "#{ca_path} is not a valid file path"
459
- end
295
+ def session_get(name)
296
+ @session[session_key(name)]
460
297
  end
461
298
 
462
- # See the interface documentation for Consumer.begin_without_discovery
463
- # for arugumnets and return values of this method.
464
- # begin_without_discovery is a light wrapper around this method, and the
465
- # has the same interface.
466
- def begin(service)
467
- nonce = self.create_nonce
468
- assoc = self.get_association(service.server_url)
469
- return SuccessRequest.new(assoc, nonce, service)
299
+ def session_set(name, val)
300
+ @session[session_key(name)] = val
470
301
  end
471
302
 
472
- # Please see the interface docs for Consumer.complete. This method accpets
473
- # a +service+ paramter which is provided though the SuccessRequest object
474
- # generated in the +begin+ call. The +service+ should be stored somewhere
475
- # in the user's session or environment and passed into this method
476
- # along with the full query string Hash. Consumer.complete has the
477
- # full list of return values for this method.
478
- def complete(query, service_endpoint)
479
- return FailureResponse.new(nil, msg='no session state found') unless service_endpoint
480
-
481
- consumer_id = service_endpoint.consumer_id
482
- server_id = service_endpoint.server_id
483
- server_url = service_endpoint.server_url
484
-
485
- # get the nonce out of the query
486
- nonce = query['nonce']
487
- if nonce.nil?
488
- return FailureResponse.new(consumer_id, 'could not extract nonce')
489
- end
490
-
491
- mode = query["openid.mode"]
492
-
493
- case mode
494
- when "cancel"
495
- return CancelResponse.new(consumer_id)
496
-
497
- when "error"
498
- error = query["openid.error"]
499
- unless error.nil?
500
- OpenID::Util.log('Error: '+error)
501
- end
502
- return FailureResponse.new(nil, msg=error)
503
-
504
- when "id_res"
505
- return self.do_id_res(nonce, consumer_id, server_id, server_url, query)
506
-
507
- else
508
- return FailureResponse.new(nil, msg="unknown mode #{mode}")
509
- end
510
-
303
+ def session_key(suffix)
304
+ @session_key_prefix + suffix
511
305
  end
512
306
 
513
- # Low level method for handling the OpenID server response.
514
- def do_id_res(nonce, consumer_id, server_id, server_url, query)
515
- user_setup_url = query["openid.user_setup_url"]
516
- if user_setup_url
517
- return SetupNeededResponse.new(user_setup_url)
518
- end
519
-
520
- return_to = query["openid.return_to"]
521
- server_id2 = query["openid.identity"]
522
- assoc_handle = query["openid.assoc_handle"]
523
-
524
- if return_to.nil?
525
- return FailureResponse.new(consumer_id, msg='openid.return_to was nil')
526
- elsif server_id2.nil?
527
- return FailureResponse.new(consumer_id, msg='openid.identity was nil')
528
- elsif assoc_handle.nil?
529
- return FailureResponse.new(consumer_id, msg='openid.assoc_handle was nil')
530
- end
531
-
532
- if server_id != server_id2
533
- return FailureResponse.new(consumer_id, msg='server ids do not match')
534
- end
535
-
536
- assoc = @store.get_association(server_url, assoc_handle)
537
-
538
- if assoc.nil?
539
- # It's not an association we know about. Dumb mode is our
540
- # only possible path for recovery.
541
- code, msg = self.check_auth(nonce, query, server_url)
542
- if code == SUCCESS
543
- return SuccessResponse.new(consumer_id, query)
544
- else
545
- return FailureResponse.new(consumer_id, "check_auth failed: #{msg}")
546
- end
547
- end
548
-
549
- if assoc.expires_in <= 0
550
- OpenID::Util.log("Association with #{server_url} expired")
551
- return FailureResponse.new(consumer_id, 'assoc expired')
552
- end
553
-
554
- # Check the signature
555
- sig = query["openid.sig"]
556
- return FailureResponse.new(consumer_id, 'no sig') if sig.nil?
557
- signed = query["openid.signed"]
558
- return FailureResponse.new(consumer_id, 'no signed') if signed.nil?
559
-
560
- args = OpenID::Util.get_openid_params(query)
561
- signed_list = signed.split(",")
562
- _signed, v_sig = OpenID::Util.sign_reply(args, assoc.secret, signed_list)
563
-
564
- if v_sig != sig
565
- return FailureResponse.new(consumer_id, 'sig mismatch')
566
- end
567
-
568
- unless @store.use_nonce(nonce)
569
- return FailureResponse.new(consumer_id, 'nonce already used')
570
- end
571
-
572
- return SuccessResponse.new(consumer_id, query)
307
+ def last_requested_endpoint
308
+ session_get('last_requested_endpoint')
573
309
  end
574
310
 
575
- # Low level method for performing OpenID check_authenticaion requests
576
- def check_auth(nonce, query, server_url)
577
- check_args = OpenID::Util.get_openid_params(query)
578
- check_args["openid.mode"] = "check_authentication"
579
- post_data = OpenID::Util.urlencode(check_args)
580
-
581
- ret = @fetcher.post(server_url, post_data)
582
- if ret.nil?
583
- return FAILURE, "unable to post to #{server_url}"
584
- else
585
- url, body = ret
586
- end
587
-
588
- results = OpenID::Util.parsekv(body)
589
- is_valid = results.fetch("is_valid", "false")
590
-
591
- if is_valid == "true"
592
-
593
- # we started this request with a bad association,
594
- # falling back to dumb mode, the invalidate_handle tells
595
- # us to handle of the assoc to remove from our store.
596
- invalidate_handle = results["invalidate_handle"]
597
- if invalidate_handle
598
- @store.remove_association(server_url, invalidate_handle)
599
- end
600
-
601
- # make sure response is not getting replayed by checking the nonce
602
- unless @store.use_nonce(nonce)
603
- return FAILURE, "#{server_url}, nonce #{nonce} already used"
604
- end
605
-
606
- # is_valid = true, and we successfully used the nonce.
607
- return SUCCESS, nil
608
- end
609
-
610
- error = results["error"]
611
- if error
612
- msg = "error from server: #{error}"
613
- else
614
- msg = "is_valid was false"
615
- end
616
- return FAILURE, msg
311
+ def last_requested_endpoint=(endpoint)
312
+ session_set('last_requested_endpoint', endpoint)
617
313
  end
618
314
 
619
- # Create a nonce and store it for preventing replace attacks.
620
- def create_nonce
621
- # build the nonce and store it
622
- nonce = OpenID::Util.random_string(@@NONCE_LEN, @@NONCE_CHRS)
623
- @store.store_nonce(nonce)
624
- return nonce
315
+ def cleanup_last_requested_endpoint
316
+ @session[session_key('last_requested_endpoint')] = nil
625
317
  end
626
318
 
627
- # Get existing or create a new association (shared secret) with the
628
- # server at +server_url+.
629
- def get_association(server_url)
630
- return nil if @store.dumb?
631
- assoc = @store.get_association(server_url)
632
- return assoc unless assoc.nil?
633
- return self.associate(server_url)
319
+ def discovery_manager(openid_identifier)
320
+ DiscoveryManager.new(@session, openid_identifier, @session_key_prefix)
634
321
  end
635
-
636
- # Make the openid.associate call to the server.
637
- def associate(server_url)
638
- dh = OpenID::DiffieHellman.new
639
- cpub = OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.public))
640
- args = {
641
- 'openid.mode' => 'associate',
642
- 'openid.assoc_type' =>'HMAC-SHA1',
643
- 'openid.session_type' =>'DH-SHA1',
644
- 'openid.dh_modulus' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.p)),
645
- 'openid.dh_gen' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.g)),
646
- 'openid.dh_consumer_public' => cpub
647
- }
648
- body = OpenID::Util.urlencode(args)
649
-
650
- ret = @fetcher.post(server_url, body)
651
- return nil if ret.nil?
652
- url, data = ret
653
- results = OpenID::Util.parsekv(data)
654
-
655
- assoc_type = results["assoc_type"]
656
- return nil if assoc_type.nil? or assoc_type != "HMAC-SHA1"
657
-
658
- assoc_handle = results["assoc_handle"]
659
- return nil if assoc_handle.nil?
660
-
661
- expires_in = results.fetch("expires_in", "0").to_i
662
322
 
663
- session_type = results["session_type"]
664
- if session_type.nil?
665
- secret = OpenID::Util.from_base64(results["mac_key"])
666
- else
667
- return nil if session_type != "DH-SHA1"
668
-
669
- dh_server_public = results["dh_server_public"]
670
- return nil if dh_server_public.nil?
671
-
672
- spub = OpenID::Util.str_to_num(OpenID::Util.from_base64(dh_server_public))
673
- dh_shared = dh.get_shared_secret(spub)
674
- enc_mac_key = results["enc_mac_key"]
675
- secret = OpenID::Util.strxor(OpenID::Util.from_base64(enc_mac_key),
676
- OpenID::Util.sha1(OpenID::Util.num_to_str(dh_shared)))
677
- end
678
-
679
- assoc = OpenID::Association.from_expires_in(expires_in, assoc_handle,
680
- secret, 'HMAC-SHA1')
681
- @store.store_association(server_url, assoc)
682
- return assoc
323
+ def cleanup_session
324
+ discovery_manager(nil).cleanup(true)
683
325
  end
684
326
 
685
- end
686
-
687
- # Base class for objects returned from Consumer.begin and Consumer.complete
688
- class OpenIDStatus
689
-
690
- attr_reader :status
691
327
 
692
- def initialize(status)
693
- @status = status
328
+ def discover(identifier)
329
+ OpenID.discover(identifier)
694
330
  end
695
331
 
696
- end
697
-
698
- # Returned by Consumer.begin when server information cannot be determined
699
- # from the provided identity URL. The +msg+ method may return a useful
700
- # string for debugging the request.
701
- class FailureRequest < OpenIDStatus
702
-
703
- attr_reader :msg
704
-
705
- def initialize(msg='')
706
- super(FAILURE)
707
- @msg = msg
332
+ def negotiator
333
+ DefaultNegotiator
708
334
  end
709
335
 
710
- end
711
-
712
- # Encapsulates the information the library retrieves and uses during
713
- # Consumer.begin.
714
- class SuccessRequest < OpenIDStatus
715
-
716
- attr_reader :server_id, :server_url, :nonce, :identity_url, \
717
- :service, :return_to_args
718
-
719
- # Creates a new SuccessRequest object. This just stores each
720
- # argument in an appropriately named field.
721
- #
722
- # Users of this library should not create instances of this
723
- # class. Instances of this class are created by Consumer
724
- # during begin.
725
- def initialize(assoc, nonce, service)
726
- super(SUCCESS)
727
- @service = service
728
- @server_id = service.server_id
729
- @server_url = service.server_url
730
- @identity_url = service.consumer_id
731
- @extra_args = {}
732
- @return_to_args = {'nonce' => nonce}
733
-
734
- @assoc = assoc
735
- @nonce = nonce
736
- end
737
-
738
- # Called to construct the redirect URL sent to
739
- # the browser to ask the server to verify its identity. This is
740
- # called in step 3 of the flow described in the overview.
741
- # Please note that you don't need to call this method directly
742
- # unless you need to create a custom redirect, as it is called
743
- # directly during begin. The generated redirect should be
744
- # sent to the browser which initiated the authorization request.
745
- #
746
- # ==Parameters
747
- # [+trust_root+]
748
- # This is a URL that will be sent to the
749
- # server to identify this site. The OpenID spec (
750
- # http://www.openid.net/specs.bml#mode-checkid_immediate )
751
- # has more information on what the trust_root value is for
752
- # and what its form can be. While the trust root is
753
- # officially optional in the OpenID specification, this
754
- # implementation requires that it be set. Nothing is
755
- # actually gained by leaving out the trust root, as you can
756
- # get identical behavior by specifying the return_to URL as
757
- # the trust root.
758
- #
759
- # [+return_to+]
760
- # This is the URL that will be included in the
761
- # generated redirect as the URL the OpenID server will send
762
- # its response to. The URL passed in must handle OpenID
763
- # authentication responses.
764
- #
765
- # [+immediate+]
766
- # Optional. If +immediate+ is true, the request will be made using
767
- # openid.mode=checkid_immediate instead of the standard
768
- # openid.mode=checkid_setup.
769
- #
770
- # ==Return Value
771
- # Return a string which is the URL to which you should redirect the user.
772
- def redirect_url(trust_root, return_to, immediate=false)
773
- # add the nonce into the return_to url
774
- return_to = OpenID::Util.append_args(return_to, @return_to_args)
775
-
776
- redir_args = {
777
- "openid.identity" => @server_id,
778
- "openid.return_to" => return_to,
779
- "openid.trust_root" => trust_root,
780
- "openid.mode" => immediate ? 'checkid_immediate' : 'checkid_setup'
781
- }
782
-
783
- redir_args["openid.assoc_handle"] = @assoc.handle if @assoc
784
- redir_args.update(@extra_args)
785
-
786
- return OpenID::Util.append_args(server_url, redir_args).to_s
336
+ def association_manager(service)
337
+ AssociationManager.new(@store, service.server_url,
338
+ service.compatibility_mode, negotiator)
787
339
  end
788
340
 
789
- # get the return_to URL
790
- def return_to(return_to)
791
- OpenID::Util.append_args(return_to, @return_to_args)
341
+ def handle_idres(message, return_to)
342
+ IdResHandler.new(message, return_to, @store, last_requested_endpoint)
792
343
  end
793
344
 
794
- # Add an openid extension argument to the request. A simple resitration
795
- # request may look something like:
796
- #
797
- # req.add_extension_arg('sreg','required','email')
798
- # req.add_extension_arg('sreg','optional','nickname,gender')
799
- # req.add_extension_arg('sreg','policy_url','http://example.com/policy')
800
- def add_extension_arg(namespace, key, value)
801
- @extra_args['openid.'+namespace+'.'+key] = value
345
+ def complete_invalid(message, unused_return_to)
346
+ mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')
347
+ return FailureResponse.new(last_requested_endpoint,
348
+ "Invalid openid.mode: #{mode}")
802
349
  end
803
350
 
804
- def add_arg(key, value)
805
- @extra_args[key] = value
351
+ def complete_cancel(unused_message, unused_return_to)
352
+ return CancelResponse.new(last_requested_endpoint)
806
353
  end
807
354
 
808
- # Checks to see if the user's OpenID server additionally supports
809
- # the extensions service type url provided.
810
- def uses_extension?(extension_url)
811
- return false unless extension_url
355
+ def complete_error(message, unused_return_to)
356
+ error = message.get_arg(OPENID_NS, 'error')
357
+ contact = message.get_arg(OPENID_NS, 'contact')
358
+ reference = message.get_arg(OPENID_NS, 'reference')
812
359
 
813
- @service.service_types.each do |url|
814
- if OpenID::Util.urls_equal?(url, extension_url)
815
- return true
816
- end
817
- end
818
-
819
- return false
360
+ return FailureResponse.new(last_requested_endpoint,
361
+ error, contact, reference)
820
362
  end
821
363
 
822
- end
823
-
824
- # Encapsulates the information that is useful after a successful
825
- # Consumer.complete call. Verified identity URL and
826
- # signed extension response values are available through this object.
827
- class SuccessResponse < OpenIDStatus
828
-
829
- attr_reader :identity_url
830
- attr_accessor :service
831
-
832
- # Instances of this object will be created for you automatically
833
- # by OpenID::Consumer. You should never have to construct this
834
- # object yourself.
835
- def initialize(identity_url, query)
836
- super(SUCCESS)
837
- @identity_url = identity_url
838
- @query = query
839
- @service = nil
364
+ def complete_setup_needed(message, unused_return_to)
365
+ if message.is_openid1
366
+ return complete_invalid(message, nil)
367
+ else
368
+ return SetupNeededResponse.new(last_requested_endpoint, nil)
369
+ end
840
370
  end
841
371
 
842
- # Returns all the arguments from an extension's namespace. For example
843
- #
844
- # response.extension_response('sreg')
845
- #
846
- # may return something like:
847
- #
848
- # {'email' => 'mayor@example.com', 'nickname' => 'MayorMcCheese'}
849
- #
850
- # The extension namespace is not included in the keys of the returned
851
- # hash. Values returned from this method are guaranteed to be signed.
852
- # Calling this method should be the *only* way you access extension
853
- # response data!
854
- def extension_response(extension_name)
855
- prefix = extension_name
856
-
857
- signed = @query['openid.signed']
858
- return nil if signed.nil?
859
-
860
- signed = signed.split(',')
861
- extension_args = {}
862
- extension_prefix = prefix + '.'
863
-
864
- signed.each do |arg|
865
- if arg.index(extension_prefix) == 0
866
- query_key = 'openid.'+arg
867
- extension_args[arg[(1+prefix.length..-1)]] = @query[query_key]
372
+ def complete_id_res(message, return_to)
373
+ if message.is_openid1
374
+ setup_url = message.get_arg(OPENID1_NS, 'user_setup_url')
375
+ if !setup_url.nil?
376
+ return SetupNeededResponse.new(last_requested_endpoint, setup_url)
868
377
  end
869
378
  end
870
-
871
- return extension_args
872
- end
873
-
874
- end
875
379
 
876
- # Object returned from Consumer.complete when the auth request failed.
877
- # The +identity_url+, +msg+, and +service+ methods may contain useful
878
- # information about the failure if it is available. These methods will
879
- # return nil if no useful info can be determined.
880
- class FailureResponse < OpenIDStatus
881
-
882
- attr_accessor :identity_url, :msg, :service
883
-
884
- def initialize(identity_url=nil, msg=nil)
885
- super(FAILURE)
886
- @identity_url = identity_url
887
- @msg = msg
888
- end
889
-
890
- end
891
-
892
- # Returned by Consumer.begin in immediate mode when the user needs to
893
- # log into the OpenID server. User should be redirected to the value
894
- # returned from the +setup_url+ method.
895
- class SetupNeededResponse < OpenIDStatus
896
-
897
- attr_reader :setup_url
898
- attr_accessor :identity_url, :service
899
-
900
- def initialize(setup_url)
901
- super(SETUP_NEEDED)
902
- @setup_url = setup_url
903
- end
904
-
905
- end
906
-
907
- # Response returned from Consumer.complete when the user cancels the
908
- # OpenID transaction.
909
- class CancelResponse < OpenIDStatus
910
- attr_accessor :identity_url, :service
911
- def initialize(identity_url)
912
- super(CANCEL)
913
- @identity_url = identity_url
380
+ begin
381
+ idres = handle_idres(message, return_to)
382
+ rescue DiscoveryFailure, ProtocolError => why
383
+ return FailureResponse.new(last_requested_endpoint, why.message)
384
+ else
385
+ return SuccessResponse.new(idres.endpoint, message,
386
+ idres.signed_fields)
387
+ end
914
388
  end
915
389
  end
916
-
917
390
  end