ruby-openid 1.1.4 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-openid might be problematic. Click here for more details.

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
@@ -0,0 +1,271 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'tempfile'
4
+
5
+ require 'openid/util'
6
+ require 'openid/store/interface'
7
+ require 'openid/association'
8
+
9
+ module OpenID
10
+ module Store
11
+ class Filesystem < Interface
12
+ @@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
13
+
14
+ # Create a Filesystem store instance, putting all data in +directory+.
15
+ def initialize(directory)
16
+ p_dir = Pathname.new(directory)
17
+ @nonce_dir = p_dir.join('nonces')
18
+ @association_dir = p_dir.join('associations')
19
+ @temp_dir = p_dir.join('temp')
20
+
21
+ self.ensure_dir(@nonce_dir)
22
+ self.ensure_dir(@association_dir)
23
+ self.ensure_dir(@temp_dir)
24
+ end
25
+
26
+ # Create a unique filename for a given server url and handle. The
27
+ # filename that is returned will contain the domain name from the
28
+ # server URL for ease of human inspection of the data dir.
29
+ def get_association_filename(server_url, handle)
30
+ unless server_url.index('://')
31
+ raise ArgumentError, "Bad server URL: #{server_url}"
32
+ end
33
+
34
+ proto, rest = server_url.split('://', 2)
35
+ domain = filename_escape(rest.split('/',2)[0])
36
+ url_hash = safe64(server_url)
37
+ if handle
38
+ handle_hash = safe64(handle)
39
+ else
40
+ handle_hash = ''
41
+ end
42
+ filename = [proto,domain,url_hash,handle_hash].join('-')
43
+ @association_dir.join(filename)
44
+ end
45
+
46
+ # Store an association in the assoc directory
47
+ def store_association(server_url, association)
48
+ assoc_s = association.serialize
49
+ filename = get_association_filename(server_url, association.handle)
50
+ f, tmp = mktemp
51
+
52
+ begin
53
+ begin
54
+ f.write(assoc_s)
55
+ f.fsync
56
+ ensure
57
+ f.close
58
+ end
59
+
60
+ begin
61
+ File.rename(tmp, filename)
62
+ rescue Errno::EEXIST
63
+
64
+ begin
65
+ File.unlink(filename)
66
+ rescue Errno::ENOENT
67
+ # do nothing
68
+ end
69
+
70
+ File.rename(tmp, filename)
71
+ end
72
+
73
+ rescue
74
+ self.remove_if_present(tmp)
75
+ raise
76
+ end
77
+ end
78
+
79
+ # Retrieve an association
80
+ def get_association(server_url, handle=nil)
81
+ # the filename with empty handle is the prefix for the associations
82
+ # for a given server url
83
+ filename = get_association_filename(server_url, handle)
84
+ if handle
85
+ return _get_association(filename)
86
+ end
87
+ assoc_filenames = Dir.glob(filename.to_s + '*')
88
+
89
+ assocs = assoc_filenames.collect do |f|
90
+ _get_association(f)
91
+ end
92
+
93
+ assocs = assocs.find_all { |a| not a.nil? }
94
+ assocs = assocs.sort_by { |a| a.issued }
95
+
96
+ return nil if assocs.empty?
97
+ return assocs[-1]
98
+ end
99
+
100
+ def _get_association(filename)
101
+ begin
102
+ assoc_file = File.open(filename, "r")
103
+ rescue Errno::ENOENT
104
+ return nil
105
+ else
106
+ begin
107
+ assoc_s = assoc_file.read
108
+ ensure
109
+ assoc_file.close
110
+ end
111
+
112
+ begin
113
+ association = Association.deserialize(assoc_s)
114
+ rescue
115
+ self.remove_if_present(filename)
116
+ return nil
117
+ end
118
+
119
+ # clean up expired associations
120
+ if association.expires_in == 0
121
+ self.remove_if_present(filename)
122
+ return nil
123
+ else
124
+ return association
125
+ end
126
+ end
127
+ end
128
+
129
+ # Remove an association if it exists, otherwise do nothing.
130
+ def remove_association(server_url, handle)
131
+ assoc = get_association(server_url, handle)
132
+
133
+ if assoc.nil?
134
+ return false
135
+ else
136
+ filename = get_association_filename(server_url, handle)
137
+ return self.remove_if_present(filename)
138
+ end
139
+ end
140
+
141
+ # Return whether the nonce is valid
142
+ def use_nonce(server_url, timestamp, salt)
143
+ return false if (timestamp - Time.now.to_i).abs > Nonce.skew
144
+
145
+ if server_url and !server_url.empty?
146
+ proto, rest = server_url.split('://',2)
147
+ else
148
+ proto, rest = '',''
149
+ end
150
+ raise "Bad server URL" unless proto && rest
151
+
152
+ domain = filename_escape(rest.split('/',2)[0])
153
+ url_hash = safe64(server_url)
154
+ salt_hash = safe64(salt)
155
+
156
+ nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]
157
+
158
+ filename = @nonce_dir.join(nonce_fn)
159
+
160
+ begin
161
+ fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)
162
+ fd.close
163
+ return true
164
+ rescue Errno::EEXIST
165
+ return false
166
+ end
167
+ end
168
+
169
+ # Remove expired entries from the database. This is potentially expensive,
170
+ # so only run when it is acceptable to take time.
171
+ def cleanup
172
+ cleanup_associations
173
+ cleanup_nonces
174
+ end
175
+
176
+ def cleanup_associations
177
+ association_filenames = Dir[@association_dir.join("*")]
178
+ count = 0
179
+ association_filenames.each do |af|
180
+ begin
181
+ f = File.open(af, 'r')
182
+ rescue Errno::ENOENT
183
+ next
184
+ else
185
+ begin
186
+ assoc_s = f.read
187
+ ensure
188
+ f.close
189
+ end
190
+ begin
191
+ association = OpenID::Association.deserialize(assoc_s)
192
+ rescue StandardError
193
+ self.remove_if_present(af)
194
+ next
195
+ else
196
+ if association.expires_in == 0
197
+ self.remove_if_present(af)
198
+ count += 1
199
+ end
200
+ end
201
+ end
202
+ end
203
+ return count
204
+ end
205
+
206
+ def cleanup_nonces
207
+ nonces = Dir[@nonce_dir.join("*")]
208
+ now = Time.now.to_i
209
+
210
+ count = 0
211
+ nonces.each do |filename|
212
+ nonce = filename.split('/')[-1]
213
+ timestamp = nonce.split('-', 2)[0].to_i(16)
214
+ nonce_age = (timestamp - now).abs
215
+ if nonce_age > Nonce.skew
216
+ self.remove_if_present(filename)
217
+ count += 1
218
+ end
219
+ end
220
+ return count
221
+ end
222
+
223
+ protected
224
+
225
+ # Create a temporary file and return the File object and filename.
226
+ def mktemp
227
+ f = Tempfile.new('tmp', @temp_dir)
228
+ [f, f.path]
229
+ end
230
+
231
+ # create a safe filename from a url
232
+ def filename_escape(s)
233
+ s = '' if s.nil?
234
+ filename_chunks = []
235
+ s.split('').each do |c|
236
+ if @@FILENAME_ALLOWED.index(c)
237
+ filename_chunks << c
238
+ else
239
+ filename_chunks << sprintf("_%02X", c[0])
240
+ end
241
+ end
242
+ filename_chunks.join("")
243
+ end
244
+
245
+ def safe64(s)
246
+ s = OpenID::CryptUtil.sha1(s)
247
+ s = OpenID::Util.to_base64(s)
248
+ s.gsub!('+', '_')
249
+ s.gsub!('/', '.')
250
+ s.gsub!('=', '')
251
+ return s
252
+ end
253
+
254
+ # remove file if present in filesystem
255
+ def remove_if_present(filename)
256
+ begin
257
+ File.unlink(filename)
258
+ rescue Errno::ENOENT
259
+ return false
260
+ end
261
+ return true
262
+ end
263
+
264
+ # ensure that a path exists
265
+ def ensure_dir(dir_name)
266
+ FileUtils::mkdir_p(dir_name)
267
+ end
268
+ end
269
+ end
270
+ end
271
+
@@ -0,0 +1,75 @@
1
+ require 'openid/util'
2
+
3
+ module OpenID
4
+
5
+ # Stores for Associations and nonces. Used by both the Consumer and
6
+ # the Server. If you have a database abstraction layer or other
7
+ # state storage in your application or framework already, you can
8
+ # implement the store interface.
9
+ module Store
10
+ # Abstract Store
11
+ # Changes in 2.0:
12
+ # * removed store_nonce, get_auth_key, is_dumb
13
+ # * changed use_nonce to support one-way nonces
14
+ # * added cleanup_nonces, cleanup_associations, cleanup
15
+ class Interface < Object
16
+
17
+ # Put a Association object into storage.
18
+ # When implementing a store, don't assume that there are any limitations
19
+ # on the character set of the server_url. In particular, expect to see
20
+ # unescaped non-url-safe characters in the server_url field.
21
+ def store_association(server_url, association)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Returns a Association object from storage that matches
26
+ # the server_url. Returns nil if no such association is found or if
27
+ # the one matching association is expired. (Is allowed to GC expired
28
+ # associations when found.)
29
+ def get_association(server_url, handle=nil)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # If there is a matching association, remove it from the store and
34
+ # return true, otherwise return false.
35
+ def remove_association(server_url, handle)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Return true if the nonce has not been used before, and store it
40
+ # for a while to make sure someone doesn't try to use the same value
41
+ # again. Return false if the nonce has already been used or if the
42
+ # timestamp is not current.
43
+ # You can use OpenID::Store::Nonce::SKEW for your timestamp window.
44
+ # server_url: URL of the server from which the nonce originated
45
+ # timestamp: time the nonce was created in seconds since unix epoch
46
+ # salt: A random string that makes two nonces issued by a server in
47
+ # the same second unique
48
+ def use_nonce(server_url, timestamp, salt)
49
+ raise NotImplementedError
50
+ end
51
+
52
+ # Remove expired nonces from the store
53
+ # Discards any nonce that is old enough that it wouldn't pass use_nonce
54
+ # Not called during normal library operation, this method is for store
55
+ # admins to keep their storage from filling up with expired data
56
+ def cleanup_nonces
57
+ raise NotImplementedError
58
+ end
59
+
60
+ # Remove expired associations from the store
61
+ # Not called during normal library operation, this method is for store
62
+ # admins to keep their storage from filling up with expired data
63
+ def cleanup_associations
64
+ raise NotImplementedError
65
+ end
66
+
67
+ # Remove expired nonces and associations from the store
68
+ # Not called during normal library operation, this method is for store
69
+ # admins to keep their storage from filling up with expired data
70
+ def cleanup
71
+ return cleanup_nonces, cleanup_associations
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,84 @@
1
+ require 'openid/store/interface'
2
+ module OpenID
3
+ module Store
4
+ # An in-memory implementation of Store. This class is mainly used
5
+ # for testing, though it may be useful for long-running single
6
+ # process apps. Note that this store is NOT thread-safe.
7
+ #
8
+ # You should probably be looking at OpenID::Store::Filesystem
9
+ class Memory < Interface
10
+
11
+ def initialize
12
+ @associations = {}
13
+ @associations.default = {}
14
+ @nonces = {}
15
+ end
16
+
17
+ def store_association(server_url, assoc)
18
+ assocs = @associations[server_url]
19
+ @associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)})
20
+ end
21
+
22
+ def get_association(server_url, handle=nil)
23
+ assocs = @associations[server_url]
24
+ assoc = nil
25
+ if handle
26
+ assoc = assocs[handle]
27
+ else
28
+ assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1]
29
+ end
30
+
31
+ return assoc
32
+ end
33
+
34
+ def remove_association(server_url, handle)
35
+ assocs = @associations[server_url]
36
+ if assocs.delete(handle)
37
+ return true
38
+ else
39
+ return false
40
+ end
41
+ end
42
+
43
+ def use_nonce(server_url, timestamp, salt)
44
+ return false if (timestamp - Time.now.to_i).abs > Nonce.skew
45
+ nonce = [server_url, timestamp, salt].join('')
46
+ return false if @nonces[nonce]
47
+ @nonces[nonce] = timestamp
48
+ return true
49
+ end
50
+
51
+ def cleanup_associations
52
+ count = 0
53
+ @associations.each{|server_url, assocs|
54
+ assocs.each{|handle, assoc|
55
+ if assoc.expires_in == 0
56
+ assocs.delete(handle)
57
+ count += 1
58
+ end
59
+ }
60
+ }
61
+ return count
62
+ end
63
+
64
+ def cleanup_nonces
65
+ count = 0
66
+ now = Time.now.to_i
67
+ @nonces.each{|nonce, timestamp|
68
+ if (timestamp - now).abs > Nonce.skew
69
+ @nonces.delete(nonce)
70
+ count += 1
71
+ end
72
+ }
73
+ return count
74
+ end
75
+
76
+ protected
77
+
78
+ def deepcopy(o)
79
+ Marshal.load(Marshal.dump(o))
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,68 @@
1
+ require 'openid/cryptutil'
2
+ require 'date'
3
+ require 'time'
4
+
5
+ module OpenID
6
+ module Nonce
7
+ DEFAULT_SKEW = 60*60*5
8
+ TIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
9
+ TIME_STR_LEN = '0000-00-00T00:00:00Z'.size
10
+ @@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
11
+ TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
12
+
13
+ @skew = DEFAULT_SKEW
14
+
15
+ # The allowed nonce time skew in seconds. Defaults to 5 hours.
16
+ # Used for checking nonce validity, and by stores' cleanup methods.
17
+ def Nonce.skew
18
+ @skew
19
+ end
20
+
21
+ def Nonce.skew=(new_skew)
22
+ @skew = new_skew
23
+ end
24
+
25
+ # Extract timestamp from a nonce string
26
+ def Nonce.split_nonce(nonce_str)
27
+ timestamp_str = nonce_str[0...TIME_STR_LEN]
28
+ raise ArgumentError if timestamp_str.size < TIME_STR_LEN
29
+ raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR)
30
+ ts = Time.parse(timestamp_str).to_i
31
+ raise ArgumentError if ts < 0
32
+ return ts, nonce_str[TIME_STR_LEN..-1]
33
+ end
34
+
35
+ # Is the timestamp that is part of the specified nonce string
36
+ # within the allowed clock-skew of the current time?
37
+ def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil)
38
+ allowed_skew = skew if allowed_skew.nil?
39
+ begin
40
+ stamp, foo = split_nonce(nonce_str)
41
+ rescue ArgumentError # bad timestamp
42
+ return false
43
+ end
44
+ now = Time.now.to_i unless now
45
+
46
+ # times before this are too old
47
+ past = now - allowed_skew
48
+
49
+ # times newer than this are too far in the future
50
+ future = now + allowed_skew
51
+
52
+ return (past <= stamp and stamp <= future)
53
+ end
54
+
55
+ # generate a nonce with the specified timestamp (defaults to now)
56
+ def Nonce.mk_nonce(time = nil)
57
+ salt = CryptUtil::random_string(6, @@NONCE_CHRS)
58
+ if time.nil?
59
+ t = Time.now.getutc
60
+ else
61
+ t = Time.at(time).getutc
62
+ end
63
+ time_str = t.strftime(TIME_FMT)
64
+ return time_str + salt
65
+ end
66
+
67
+ end
68
+ end