entp-ruby-openid 2.2

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 (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