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.
- data/CHANGELOG +215 -0
- data/INSTALL +47 -0
- data/LICENSE +210 -0
- data/NOTICE +2 -0
- data/README +85 -0
- data/UPGRADE +127 -0
- data/admin/runtests.rb +45 -0
- data/examples/README +32 -0
- data/examples/active_record_openid_store/README +58 -0
- data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
- data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
- data/examples/active_record_openid_store/init.rb +8 -0
- data/examples/active_record_openid_store/lib/association.rb +10 -0
- data/examples/active_record_openid_store/lib/nonce.rb +3 -0
- data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
- data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
- data/examples/active_record_openid_store/test/store_test.rb +212 -0
- data/examples/discover +49 -0
- data/examples/rails_openid/README +153 -0
- data/examples/rails_openid/Rakefile +10 -0
- data/examples/rails_openid/app/controllers/application.rb +4 -0
- data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
- data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
- data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
- data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
- data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
- data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
- data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
- data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
- data/examples/rails_openid/app/views/login/index.rhtml +56 -0
- data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
- data/examples/rails_openid/config/boot.rb +19 -0
- data/examples/rails_openid/config/database.yml +74 -0
- data/examples/rails_openid/config/environment.rb +54 -0
- data/examples/rails_openid/config/environments/development.rb +19 -0
- data/examples/rails_openid/config/environments/production.rb +19 -0
- data/examples/rails_openid/config/environments/test.rb +19 -0
- data/examples/rails_openid/config/routes.rb +24 -0
- data/examples/rails_openid/doc/README_FOR_APP +2 -0
- data/examples/rails_openid/public/404.html +8 -0
- data/examples/rails_openid/public/500.html +8 -0
- data/examples/rails_openid/public/dispatch.cgi +12 -0
- data/examples/rails_openid/public/dispatch.fcgi +26 -0
- data/examples/rails_openid/public/dispatch.rb +12 -0
- data/examples/rails_openid/public/favicon.ico +0 -0
- data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
- data/examples/rails_openid/public/javascripts/controls.js +750 -0
- data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
- data/examples/rails_openid/public/javascripts/effects.js +854 -0
- data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
- data/examples/rails_openid/public/robots.txt +1 -0
- data/examples/rails_openid/script/about +3 -0
- data/examples/rails_openid/script/breakpointer +3 -0
- data/examples/rails_openid/script/console +3 -0
- data/examples/rails_openid/script/destroy +3 -0
- data/examples/rails_openid/script/generate +3 -0
- data/examples/rails_openid/script/performance/benchmarker +3 -0
- data/examples/rails_openid/script/performance/profiler +3 -0
- data/examples/rails_openid/script/plugin +3 -0
- data/examples/rails_openid/script/process/reaper +3 -0
- data/examples/rails_openid/script/process/spawner +3 -0
- data/examples/rails_openid/script/process/spinner +3 -0
- data/examples/rails_openid/script/runner +3 -0
- data/examples/rails_openid/script/server +3 -0
- data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
- data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
- data/examples/rails_openid/test/test_helper.rb +28 -0
- data/lib/hmac/hmac.rb +112 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid.rb +22 -0
- data/lib/openid/association.rb +249 -0
- data/lib/openid/consumer.rb +395 -0
- data/lib/openid/consumer/associationmanager.rb +344 -0
- data/lib/openid/consumer/checkid_request.rb +186 -0
- data/lib/openid/consumer/discovery.rb +497 -0
- data/lib/openid/consumer/discovery_manager.rb +123 -0
- data/lib/openid/consumer/html_parse.rb +134 -0
- data/lib/openid/consumer/idres.rb +523 -0
- data/lib/openid/consumer/responses.rb +150 -0
- data/lib/openid/cryptutil.rb +115 -0
- data/lib/openid/dh.rb +89 -0
- data/lib/openid/extension.rb +39 -0
- data/lib/openid/extensions/ax.rb +539 -0
- data/lib/openid/extensions/oauth.rb +91 -0
- data/lib/openid/extensions/pape.rb +179 -0
- data/lib/openid/extensions/sreg.rb +277 -0
- data/lib/openid/extras.rb +11 -0
- data/lib/openid/fetchers.rb +258 -0
- data/lib/openid/kvform.rb +136 -0
- data/lib/openid/kvpost.rb +58 -0
- data/lib/openid/message.rb +553 -0
- data/lib/openid/protocolerror.rb +12 -0
- data/lib/openid/server.rb +1544 -0
- data/lib/openid/store.rb +10 -0
- data/lib/openid/store/filesystem.rb +272 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +84 -0
- data/lib/openid/store/nonce.rb +68 -0
- data/lib/openid/trustroot.rb +349 -0
- data/lib/openid/urinorm.rb +75 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +3 -0
- data/lib/openid/yadis.rb +15 -0
- data/lib/openid/yadis/accept.rb +148 -0
- data/lib/openid/yadis/constants.rb +21 -0
- data/lib/openid/yadis/discovery.rb +153 -0
- data/lib/openid/yadis/filters.rb +205 -0
- data/lib/openid/yadis/htmltokenizer.rb +305 -0
- data/lib/openid/yadis/parsehtml.rb +45 -0
- data/lib/openid/yadis/services.rb +42 -0
- data/lib/openid/yadis/xrds.rb +155 -0
- data/lib/openid/yadis/xri.rb +90 -0
- data/lib/openid/yadis/xrires.rb +91 -0
- data/test/data/test_discover/openid_utf8.html +11 -0
- data/test/support/test_data_mixin.rb +127 -0
- data/test/support/test_util.rb +53 -0
- data/test/support/yadis_data.rb +131 -0
- data/test/support/yadis_data/accept.txt +124 -0
- data/test/support/yadis_data/dh.txt +29 -0
- data/test/support/yadis_data/example-xrds.xml +14 -0
- data/test/support/yadis_data/linkparse.txt +587 -0
- data/test/support/yadis_data/n2b64 +650 -0
- data/test/support/yadis_data/test1-discover.txt +137 -0
- data/test/support/yadis_data/test1-parsehtml.txt +152 -0
- data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
- data/test/support/yadis_data/test_discover/openid.html +11 -0
- data/test/support/yadis_data/test_discover/openid2.html +11 -0
- data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
- data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
- data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
- data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
- data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
- data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
- data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
- data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
- data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
- data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
- data/test/support/yadis_data/test_xrds/README +12 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
- data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
- data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
- data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
- data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
- data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
- data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
- data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
- data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
- data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/support/yadis_data/trustroot.txt +153 -0
- data/test/support/yadis_data/urinorm.txt +79 -0
- data/test/test_accept.rb +170 -0
- data/test/test_association.rb +268 -0
- data/test/test_associationmanager.rb +918 -0
- data/test/test_ax.rb +690 -0
- data/test/test_checkid_request.rb +293 -0
- data/test/test_consumer.rb +260 -0
- data/test/test_cryptutil.rb +119 -0
- data/test/test_dh.rb +85 -0
- data/test/test_discover.rb +848 -0
- data/test/test_discovery_manager.rb +259 -0
- data/test/test_extension.rb +46 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +554 -0
- data/test/test_filters.rb +269 -0
- data/test/test_helper.rb +4 -0
- data/test/test_idres.rb +961 -0
- data/test/test_kvform.rb +164 -0
- data/test/test_kvpost.rb +64 -0
- data/test/test_linkparse.rb +100 -0
- data/test/test_message.rb +1115 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_oauth.rb +176 -0
- data/test/test_openid_yadis.rb +177 -0
- data/test/test_pape.rb +248 -0
- data/test/test_parsehtml.rb +79 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2455 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +292 -0
- data/test/test_trustroot.rb +111 -0
- data/test/test_urinorm.rb +34 -0
- data/test/test_util.rb +145 -0
- data/test/test_xrds.rb +167 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +67 -0
- data/test/test_yadis_discovery.rb +218 -0
- metadata +268 -0
data/lib/openid/store.rb
ADDED
|
@@ -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
|