entp-ruby-openid 2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/CHANGELOG +215 -0
  2. data/INSTALL +47 -0
  3. data/LICENSE +210 -0
  4. data/NOTICE +2 -0
  5. data/README +85 -0
  6. data/UPGRADE +127 -0
  7. data/admin/runtests.rb +45 -0
  8. data/examples/README +32 -0
  9. data/examples/active_record_openid_store/README +58 -0
  10. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  11. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  12. data/examples/active_record_openid_store/init.rb +8 -0
  13. data/examples/active_record_openid_store/lib/association.rb +10 -0
  14. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  15. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  16. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  17. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  18. data/examples/discover +49 -0
  19. data/examples/rails_openid/README +153 -0
  20. data/examples/rails_openid/Rakefile +10 -0
  21. data/examples/rails_openid/app/controllers/application.rb +4 -0
  22. data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
  23. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  24. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  25. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  26. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  27. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  28. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  29. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  30. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  31. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  32. data/examples/rails_openid/config/boot.rb +19 -0
  33. data/examples/rails_openid/config/database.yml +74 -0
  34. data/examples/rails_openid/config/environment.rb +54 -0
  35. data/examples/rails_openid/config/environments/development.rb +19 -0
  36. data/examples/rails_openid/config/environments/production.rb +19 -0
  37. data/examples/rails_openid/config/environments/test.rb +19 -0
  38. data/examples/rails_openid/config/routes.rb +24 -0
  39. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  40. data/examples/rails_openid/public/404.html +8 -0
  41. data/examples/rails_openid/public/500.html +8 -0
  42. data/examples/rails_openid/public/dispatch.cgi +12 -0
  43. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  44. data/examples/rails_openid/public/dispatch.rb +12 -0
  45. data/examples/rails_openid/public/favicon.ico +0 -0
  46. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  47. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  48. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  49. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  50. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  51. data/examples/rails_openid/public/robots.txt +1 -0
  52. data/examples/rails_openid/script/about +3 -0
  53. data/examples/rails_openid/script/breakpointer +3 -0
  54. data/examples/rails_openid/script/console +3 -0
  55. data/examples/rails_openid/script/destroy +3 -0
  56. data/examples/rails_openid/script/generate +3 -0
  57. data/examples/rails_openid/script/performance/benchmarker +3 -0
  58. data/examples/rails_openid/script/performance/profiler +3 -0
  59. data/examples/rails_openid/script/plugin +3 -0
  60. data/examples/rails_openid/script/process/reaper +3 -0
  61. data/examples/rails_openid/script/process/spawner +3 -0
  62. data/examples/rails_openid/script/process/spinner +3 -0
  63. data/examples/rails_openid/script/runner +3 -0
  64. data/examples/rails_openid/script/server +3 -0
  65. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  66. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  67. data/examples/rails_openid/test/test_helper.rb +28 -0
  68. data/lib/hmac/hmac.rb +112 -0
  69. data/lib/hmac/sha1.rb +11 -0
  70. data/lib/hmac/sha2.rb +25 -0
  71. data/lib/openid.rb +22 -0
  72. data/lib/openid/association.rb +249 -0
  73. data/lib/openid/consumer.rb +395 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +497 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +150 -0
  81. data/lib/openid/cryptutil.rb +115 -0
  82. data/lib/openid/dh.rb +89 -0
  83. data/lib/openid/extension.rb +39 -0
  84. data/lib/openid/extensions/ax.rb +539 -0
  85. data/lib/openid/extensions/oauth.rb +91 -0
  86. data/lib/openid/extensions/pape.rb +179 -0
  87. data/lib/openid/extensions/sreg.rb +277 -0
  88. data/lib/openid/extras.rb +11 -0
  89. data/lib/openid/fetchers.rb +258 -0
  90. data/lib/openid/kvform.rb +136 -0
  91. data/lib/openid/kvpost.rb +58 -0
  92. data/lib/openid/message.rb +553 -0
  93. data/lib/openid/protocolerror.rb +12 -0
  94. data/lib/openid/server.rb +1544 -0
  95. data/lib/openid/store.rb +10 -0
  96. data/lib/openid/store/filesystem.rb +272 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +109 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +119 -0
  104. data/lib/openid/version.rb +3 -0
  105. data/lib/openid/yadis.rb +15 -0
  106. data/lib/openid/yadis/accept.rb +148 -0
  107. data/lib/openid/yadis/constants.rb +21 -0
  108. data/lib/openid/yadis/discovery.rb +153 -0
  109. data/lib/openid/yadis/filters.rb +205 -0
  110. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  111. data/lib/openid/yadis/parsehtml.rb +45 -0
  112. data/lib/openid/yadis/services.rb +42 -0
  113. data/lib/openid/yadis/xrds.rb +155 -0
  114. data/lib/openid/yadis/xri.rb +90 -0
  115. data/lib/openid/yadis/xrires.rb +91 -0
  116. data/test/data/test_discover/openid_utf8.html +11 -0
  117. data/test/support/test_data_mixin.rb +127 -0
  118. data/test/support/test_util.rb +53 -0
  119. data/test/support/yadis_data.rb +131 -0
  120. data/test/support/yadis_data/accept.txt +124 -0
  121. data/test/support/yadis_data/dh.txt +29 -0
  122. data/test/support/yadis_data/example-xrds.xml +14 -0
  123. data/test/support/yadis_data/linkparse.txt +587 -0
  124. data/test/support/yadis_data/n2b64 +650 -0
  125. data/test/support/yadis_data/test1-discover.txt +137 -0
  126. data/test/support/yadis_data/test1-parsehtml.txt +152 -0
  127. data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/support/yadis_data/test_discover/openid.html +11 -0
  129. data/test/support/yadis_data/test_discover/openid2.html +11 -0
  130. data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
  138. data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
  144. data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/support/yadis_data/test_xrds/README +12 -0
  148. data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
  152. data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
  153. data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
  155. data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
  157. data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
  158. data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
  159. data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
  160. data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
  161. data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/support/yadis_data/trustroot.txt +153 -0
  163. data/test/support/yadis_data/urinorm.txt +79 -0
  164. data/test/test_accept.rb +170 -0
  165. data/test/test_association.rb +268 -0
  166. data/test/test_associationmanager.rb +918 -0
  167. data/test/test_ax.rb +690 -0
  168. data/test/test_checkid_request.rb +293 -0
  169. data/test/test_consumer.rb +260 -0
  170. data/test/test_cryptutil.rb +119 -0
  171. data/test/test_dh.rb +85 -0
  172. data/test/test_discover.rb +848 -0
  173. data/test/test_discovery_manager.rb +259 -0
  174. data/test/test_extension.rb +46 -0
  175. data/test/test_extras.rb +35 -0
  176. data/test/test_fetchers.rb +554 -0
  177. data/test/test_filters.rb +269 -0
  178. data/test/test_helper.rb +4 -0
  179. data/test/test_idres.rb +961 -0
  180. data/test/test_kvform.rb +164 -0
  181. data/test/test_kvpost.rb +64 -0
  182. data/test/test_linkparse.rb +100 -0
  183. data/test/test_message.rb +1115 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +176 -0
  186. data/test/test_openid_yadis.rb +177 -0
  187. data/test/test_pape.rb +248 -0
  188. data/test/test_parsehtml.rb +79 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2455 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +292 -0
  193. data/test/test_trustroot.rb +111 -0
  194. data/test/test_urinorm.rb +34 -0
  195. data/test/test_util.rb +145 -0
  196. data/test/test_xrds.rb +167 -0
  197. data/test/test_xri.rb +48 -0
  198. data/test/test_xrires.rb +67 -0
  199. data/test/test_yadis_discovery.rb +218 -0
  200. metadata +268 -0
@@ -0,0 +1,10 @@
1
+ require 'openid/store/interface'
2
+ require 'openid/store/filesystem'
3
+ require 'openid/store/memcache'
4
+ require 'openid/store/memory'
5
+ require 'openid/store/nonce'
6
+
7
+ module OpenID
8
+ module Store
9
+ end
10
+ end
@@ -0,0 +1,272 @@
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("*").to_s]
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("*").to_s]
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
+ n = c.respond_to?(:ord) ? c.ord : c[0]
240
+ filename_chunks << sprintf("_%02X", n)
241
+ end
242
+ end
243
+ filename_chunks.join("")
244
+ end
245
+
246
+ def safe64(s)
247
+ s = OpenID::CryptUtil.sha1(s)
248
+ s = OpenID::Util.to_base64(s)
249
+ s.gsub!('+', '_')
250
+ s.gsub!('/', '.')
251
+ s.gsub!('=', '')
252
+ return s
253
+ end
254
+
255
+ # remove file if present in filesystem
256
+ def remove_if_present(filename)
257
+ begin
258
+ File.unlink(filename)
259
+ rescue Errno::ENOENT
260
+ return false
261
+ end
262
+ return true
263
+ end
264
+
265
+ # ensure that a path exists
266
+ def ensure_dir(dir_name)
267
+ FileUtils::mkdir_p(dir_name)
268
+ end
269
+ end
270
+ end
271
+ end
272
+
@@ -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,109 @@
1
+ require 'openid/util'
2
+ require 'openid/store/interface'
3
+ require 'openid/store/nonce'
4
+ require 'time'
5
+
6
+ module OpenID
7
+ module Store
8
+ class Memcache < Interface
9
+ attr_accessor :key_prefix
10
+
11
+ def initialize(cache_client, key_prefix='openid-store:')
12
+ @cache_client = cache_client
13
+ self.key_prefix = key_prefix
14
+ end
15
+
16
+ # Put a Association object into storage.
17
+ # When implementing a store, don't assume that there are any limitations
18
+ # on the character set of the server_url. In particular, expect to see
19
+ # unescaped non-url-safe characters in the server_url field.
20
+ def store_association(server_url, association)
21
+ serialized = serialize(association)
22
+ [nil, association.handle].each do |handle|
23
+ key = assoc_key(server_url, handle)
24
+ @cache_client.write(key, serialized, :expires_in => association.lifetime.seconds.to_i)
25
+ end
26
+ end
27
+
28
+ # Returns a Association object from storage that matches
29
+ # the server_url. Returns nil if no such association is found or if
30
+ # the one matching association is expired. (Is allowed to GC expired
31
+ # associations when found.)
32
+ def get_association(server_url, handle=nil)
33
+ serialized = @cache_client.read(assoc_key(server_url, handle))
34
+ if serialized
35
+ return deserialize(serialized)
36
+ else
37
+ return nil
38
+ end
39
+ end
40
+
41
+ # If there is a matching association, remove it from the store and
42
+ # return true, otherwise return false.
43
+ def remove_association(server_url, handle)
44
+ deleted = delete(assoc_key(server_url, handle))
45
+ server_assoc = get_association(server_url)
46
+ if server_assoc && server_assoc.handle == handle
47
+ deleted = delete(assoc_key(server_url)) | deleted
48
+ end
49
+ return deleted
50
+ end
51
+
52
+ # Return true if the nonce has not been used before, and store it
53
+ # for a while to make sure someone doesn't try to use the same value
54
+ # again. Return false if the nonce has already been used or if the
55
+ # timestamp is not current.
56
+ # You can use OpenID::Store::Nonce::SKEW for your timestamp window.
57
+ # server_url: URL of the server from which the nonce originated
58
+ # timestamp: time the nonce was created in seconds since unix epoch
59
+ # salt: A random string that makes two nonces issued by a server in
60
+ # the same second unique
61
+ def use_nonce(server_url, timestamp, salt)
62
+ return false if (timestamp - Time.now.to_i).abs > Nonce.skew
63
+ ts = timestamp.to_s # base 10 seconds since epoch
64
+ nonce_key = key_prefix + 'N' + server_url + '|' + ts + '|' + salt
65
+ result = @cache_client.read(nonce_key)
66
+ @cache_client.write(nonce_key, nonce_key, :expires_in => (Nonce.skew() + 5))
67
+ result.nil?
68
+ end
69
+
70
+ def assoc_key(server_url, assoc_handle=nil)
71
+ key = key_prefix + 'A' + server_url
72
+ if assoc_handle
73
+ key += '|' + assoc_handle
74
+ end
75
+ return key
76
+ end
77
+
78
+ def cleanup_nonces
79
+ end
80
+
81
+ def cleanup
82
+ end
83
+
84
+ def cleanup_associations
85
+ end
86
+
87
+ protected
88
+
89
+ def delete(key)
90
+ # result = @cache_client.delete(key) # memcached delete seems to be broken
91
+ # return !!(result =~ /^DELETED/)
92
+ @cache_client.write(key, nil, :expires_in => 0)
93
+ end
94
+
95
+ def serialize(assoc)
96
+ Marshal.dump(assoc)
97
+ end
98
+
99
+ def deserialize(assoc_str)
100
+ Marshal.load(assoc_str)
101
+ end
102
+
103
+ # Convert a lifetime in seconds into a memcache expiry value
104
+ def expiry(t)
105
+ Time.now.to_i + t
106
+ end
107
+ end
108
+ end
109
+ end