ruby-openid 1.0

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 (114) hide show
  1. data/COPYING +21 -0
  2. data/INSTALL +34 -0
  3. data/README +67 -0
  4. data/TODO +9 -0
  5. data/examples/README +54 -0
  6. data/examples/cacert.pem +7815 -0
  7. data/examples/consumer.rb +285 -0
  8. data/examples/openid-store/associations/http-localhost_3A3000_2Fserver-EMQbAy3NnHVzA.s0u5KAcplKGzo +6 -0
  9. data/examples/openid-store/auth_key +1 -0
  10. data/examples/rails_active_record_store/README +59 -0
  11. data/examples/rails_active_record_store/XX_add_openidstore.rb +30 -0
  12. data/examples/rails_active_record_store/models/openid_association.rb +12 -0
  13. data/examples/rails_active_record_store/models/openid_nonce.rb +3 -0
  14. data/examples/rails_active_record_store/models/openid_setting.rb +2 -0
  15. data/examples/rails_active_record_store/openid_helper.rb +91 -0
  16. data/examples/rails_active_record_store/openidstore_test.rb +15 -0
  17. data/examples/rails_active_record_store/schema.mysql.sql +22 -0
  18. data/examples/rails_active_record_store/schema.postgresql.sql +21 -0
  19. data/examples/rails_active_record_store/schema.sqlite.sql +21 -0
  20. data/examples/rails_openid_login_generator/USAGE +23 -0
  21. data/examples/rails_openid_login_generator/openid_login_generator.rb +36 -0
  22. data/examples/rails_openid_login_generator/templates/README +116 -0
  23. data/examples/rails_openid_login_generator/templates/controller.rb +116 -0
  24. data/examples/rails_openid_login_generator/templates/controller_test.rb +0 -0
  25. data/examples/rails_openid_login_generator/templates/helper.rb +2 -0
  26. data/examples/rails_openid_login_generator/templates/openid_login_system.rb +87 -0
  27. data/examples/rails_openid_login_generator/templates/user.rb +14 -0
  28. data/examples/rails_openid_login_generator/templates/user_test.rb +0 -0
  29. data/examples/rails_openid_login_generator/templates/users.yml +0 -0
  30. data/examples/rails_openid_login_generator/templates/view_login.rhtml +15 -0
  31. data/examples/rails_openid_login_generator/templates/view_logout.rhtml +10 -0
  32. data/examples/rails_openid_login_generator/templates/view_welcome.rhtml +9 -0
  33. data/examples/rails_server/README +153 -0
  34. data/examples/rails_server/Rakefile +10 -0
  35. data/examples/rails_server/app/controllers/application.rb +4 -0
  36. data/examples/rails_server/app/controllers/login_controller.rb +35 -0
  37. data/examples/rails_server/app/controllers/server_controller.rb +185 -0
  38. data/examples/rails_server/app/helpers/application_helper.rb +3 -0
  39. data/examples/rails_server/app/helpers/login_helper.rb +2 -0
  40. data/examples/rails_server/app/helpers/server_helper.rb +9 -0
  41. data/examples/rails_server/app/views/layouts/server.rhtml +61 -0
  42. data/examples/rails_server/app/views/login/index.rhtml +32 -0
  43. data/examples/rails_server/app/views/server/decide.rhtml +11 -0
  44. data/examples/rails_server/config/boot.rb +19 -0
  45. data/examples/rails_server/config/database.yml +85 -0
  46. data/examples/rails_server/config/environment.rb +53 -0
  47. data/examples/rails_server/config/environments/development.rb +19 -0
  48. data/examples/rails_server/config/environments/production.rb +19 -0
  49. data/examples/rails_server/config/environments/test.rb +19 -0
  50. data/examples/rails_server/config/routes.rb +23 -0
  51. data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-YU.tkND1J4fEZhnuAoT5Zc0yCA0 +6 -0
  52. data/examples/rails_server/doc/README_FOR_APP +2 -0
  53. data/examples/rails_server/log/development.log +6059 -0
  54. data/examples/rails_server/log/production.log +0 -0
  55. data/examples/rails_server/log/server.log +0 -0
  56. data/examples/rails_server/log/test.log +0 -0
  57. data/examples/rails_server/public/404.html +8 -0
  58. data/examples/rails_server/public/500.html +8 -0
  59. data/examples/rails_server/public/dispatch.cgi +12 -0
  60. data/examples/rails_server/public/dispatch.fcgi +26 -0
  61. data/examples/rails_server/public/dispatch.rb +12 -0
  62. data/examples/rails_server/public/favicon.ico +0 -0
  63. data/examples/rails_server/public/images/rails.png +0 -0
  64. data/examples/rails_server/public/javascripts/controls.js +750 -0
  65. data/examples/rails_server/public/javascripts/dragdrop.js +584 -0
  66. data/examples/rails_server/public/javascripts/effects.js +854 -0
  67. data/examples/rails_server/public/javascripts/prototype.js +1785 -0
  68. data/examples/rails_server/public/robots.txt +1 -0
  69. data/examples/rails_server/script/about +3 -0
  70. data/examples/rails_server/script/breakpointer +3 -0
  71. data/examples/rails_server/script/console +3 -0
  72. data/examples/rails_server/script/destroy +3 -0
  73. data/examples/rails_server/script/generate +3 -0
  74. data/examples/rails_server/script/performance/benchmarker +3 -0
  75. data/examples/rails_server/script/performance/profiler +3 -0
  76. data/examples/rails_server/script/plugin +3 -0
  77. data/examples/rails_server/script/process/reaper +3 -0
  78. data/examples/rails_server/script/process/spawner +3 -0
  79. data/examples/rails_server/script/process/spinner +3 -0
  80. data/examples/rails_server/script/runner +3 -0
  81. data/examples/rails_server/script/server +3 -0
  82. data/examples/rails_server/test/functional/login_controller_test.rb +18 -0
  83. data/examples/rails_server/test/functional/server_controller_test.rb +18 -0
  84. data/examples/rails_server/test/test_helper.rb +28 -0
  85. data/lib/hmac-md5.rb +11 -0
  86. data/lib/hmac-rmd160.rb +11 -0
  87. data/lib/hmac-sha1.rb +11 -0
  88. data/lib/hmac-sha2.rb +25 -0
  89. data/lib/hmac.rb +112 -0
  90. data/lib/openid/association.rb +109 -0
  91. data/lib/openid/consumer.rb +928 -0
  92. data/lib/openid/dh.rb +48 -0
  93. data/lib/openid/discovery.rb +89 -0
  94. data/lib/openid/fetchers.rb +119 -0
  95. data/lib/openid/filestore.rb +315 -0
  96. data/lib/openid/htmltokenizer.rb +355 -0
  97. data/lib/openid/parse.rb +23 -0
  98. data/lib/openid/server.rb +951 -0
  99. data/lib/openid/service.rb +135 -0
  100. data/lib/openid/stores.rb +178 -0
  101. data/lib/openid/trustroot.rb +100 -0
  102. data/lib/openid/util.rb +273 -0
  103. data/test/assoc.rb +38 -0
  104. data/test/consumer.rb +384 -0
  105. data/test/dh.rb +20 -0
  106. data/test/extensions.rb +30 -0
  107. data/test/linkparse.rb +305 -0
  108. data/test/runtests.rb +11 -0
  109. data/test/server2.rb +1053 -0
  110. data/test/storetestcase.rb +172 -0
  111. data/test/teststore.rb +23 -0
  112. data/test/trustroot.rb +113 -0
  113. data/test/util.rb +56 -0
  114. metadata +218 -0
data/lib/openid/dh.rb ADDED
@@ -0,0 +1,48 @@
1
+ require "openid/util"
2
+
3
+ module OpenID
4
+
5
+ # Encapsulates a Diffie-Hellman key exchange. This class is used
6
+ # internally by both the consumer and server objects.
7
+ #
8
+ # Read more about Diffie-Hellman on wikipedia:
9
+ # http://en.wikipedia.org/wiki/Diffie-Hellman
10
+ class DiffieHellman
11
+
12
+ @@DEFAULT_MOD = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443
13
+ @@DEFAULT_GEN = 2
14
+
15
+ attr_reader :p, :g, :public
16
+
17
+ def DiffieHellman.from_base64(p=nil, g=nil)
18
+ unless p.nil?
19
+ p = OpenID::Util.base64_to_num(p)
20
+ end
21
+ unless g.nil?
22
+ g = OpenID::Util.base64_to_num(g)
23
+ end
24
+ DiffieHellman.new(p, g)
25
+ end
26
+
27
+ def initialize(p=nil, g=nil)
28
+ @p = p.nil? ? @@DEFAULT_MOD : p
29
+ @g = g.nil? ? @@DEFAULT_GEN : g
30
+
31
+ @private = OpenID::Util.rand(@p-2) + 1
32
+ @public = OpenID::Util.powermod(@g, @private, @p)
33
+ end
34
+
35
+ def get_shared_secret(composite)
36
+ OpenID::Util.powermod(composite, @private, @p)
37
+ end
38
+
39
+ def xor_secrect(composite, secret)
40
+ dh_shared = get_shared_secret(composite)
41
+ sha1_dh_shared = OpenID::Util.sha1( \
42
+ OpenID::Util.num_to_str(dh_shared))
43
+ return OpenID::Util.strxor(secret, sha1_dh_shared)
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,89 @@
1
+ require "openid/util"
2
+ require "openid/service"
3
+ require "openid/parse"
4
+
5
+ # try and use the yadis gem, falling back to system yadis
6
+ begin
7
+ require 'rubygems'
8
+ require_gem 'ruby-yadis', ">=0.2.3"
9
+ rescue LoadError
10
+ require "yadis"
11
+ end
12
+
13
+ module OpenID
14
+
15
+ # OpenID::Discovery encapsulates the logic for doing Yadis and OpenID 1.0
16
+ # style server discovery. This class uses a session object to manage
17
+ # a list of tried OpenID servers for implemeting server fallback. This is
18
+ # useful the case when a user's primary server(s) is not available, and
19
+ # will allow then to try again with one of their alternates.
20
+ class OpenIDDiscovery < Discovery
21
+
22
+ def initialize(session, url, fetcher, suffix=nil)
23
+ super(session, url, suffix)
24
+ @fetcher = fetcher
25
+ end
26
+
27
+ # Pass in a custom filter here if you like. Otherwise you'll get all
28
+ # OpenID sso services. filter should produce objects or subclasses of
29
+ # OpenIDServiceEndpoint.
30
+ def discover(filter=nil)
31
+ unless filter
32
+ filter = lambda {|s| OpenIDServiceEndpoint.from_endpoint(s)}
33
+ end
34
+
35
+ begin
36
+ # do yadis discover, filtering out OpenID services
37
+ return super(filter)
38
+ rescue YADISParseError, YADISHTTPError
39
+
40
+ # Couldn't do Yadis discovery, fall back on OpenID 1.0 disco
41
+ status, service = self.openid_discovery(@url)
42
+ if status == SUCCESS
43
+ return [service.consumer_id, [service]]
44
+ end
45
+ end
46
+
47
+ return [nil, []]
48
+ end
49
+
50
+ # Perform OpenID 1.0 style link rel discovery. No string normalization
51
+ # will be done on +url+. See Util.normalize_url for information on
52
+ # textual URL transformations.
53
+ def openid_discovery(url)
54
+ ret = @fetcher.get(url)
55
+ return [HTTP_FAILURE, nil] if ret.nil?
56
+
57
+ consumer_id, data = ret
58
+ server = nil
59
+ delegate = nil
60
+ parse_link_attrs(data) do |attrs|
61
+ rel = attrs["rel"]
62
+ if rel == "openid.server" and server.nil?
63
+ href = attrs["href"]
64
+ server = href unless href.nil?
65
+ end
66
+
67
+ if rel == "openid.delegate" and delegate.nil?
68
+ href = attrs["href"]
69
+ delegate = href unless href.nil?
70
+ end
71
+ end
72
+
73
+ return [PARSE_ERROR, nil] if server.nil?
74
+
75
+ server_id = delegate.nil? ? consumer_id : delegate
76
+
77
+ consumer_id = OpenID::Util.normalize_url(consumer_id)
78
+ server_id = OpenID::Util.normalize_url(server_id)
79
+ server_url = OpenID::Util.normalize_url(server)
80
+
81
+ service = OpenID::FakeOpenIDServiceEndpoint.new(consumer_id,
82
+ server_id,
83
+ server_url)
84
+ return [SUCCESS, service]
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,119 @@
1
+ require "uri"
2
+ require "openid/util"
3
+
4
+ # Try to use net/https, falling back to no SSL support if it is not available
5
+ begin
6
+ require 'net/https'
7
+ rescue LoadError
8
+ OpenID::Util.log('WARNING: unable no SSL support found. Will not be able to fetch HTTPS URLs!')
9
+ HAS_OPENSSL = false
10
+ require 'net/http'
11
+ else
12
+ HAS_OPENSSL = true
13
+ end
14
+
15
+ module OpenID
16
+
17
+ # Base Object used by consumer to send http messages
18
+ class Fetcher
19
+
20
+ # Fetch the content of url, following redirects, and return the
21
+ # final url and page data. Return nil on failure.
22
+ def get(url)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Post the body string to url. Return the resulting url and page data.
27
+ # Return nil on failure
28
+ def post(url, body)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ end
33
+
34
+ # Implemetation of OpenID::Fetcher that uses ruby's Net::HTTP
35
+ class StandardFetcher < Fetcher
36
+
37
+ attr_accessor :ca_path
38
+
39
+ def initialize(read_timeout=20, open_timeout=20)
40
+ @read_timeout = read_timeout
41
+ @open_timeout = open_timeout
42
+ @ca_path = nil
43
+ end
44
+
45
+ def get(url)
46
+ resp, final_url = do_get(url)
47
+ if resp.nil?
48
+ nil
49
+ else
50
+ [final_url, resp.body]
51
+ end
52
+ end
53
+
54
+ def post(url, body)
55
+ begin
56
+ uri = URI.parse(url)
57
+ http = get_http_obj(uri)
58
+ resp = http.post(uri.request_uri, body,
59
+ {"Content-type"=>"application/x-www-form-urlencoded"})
60
+ rescue
61
+ nil
62
+ else
63
+ [uri.to_s, resp.body]
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ # return a Net::HTTP object ready for use
70
+ def get_http_obj(uri)
71
+ http = Net::HTTP.new(uri.host, uri.port)
72
+ http.read_timeout = @read_timeout
73
+ http.open_timeout = @open_timeout
74
+
75
+ if uri.scheme == 'https'
76
+
77
+ if HAS_OPENSSL
78
+ http.use_ssl = true
79
+
80
+ if @ca_path
81
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
82
+ http.ca_file = @ca_path
83
+ else
84
+ OpenID::Util.log('WARNING: making https request without verifying server certificate.')
85
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
92
+ return http
93
+ end
94
+
95
+ # do a GET following redirects limit deep
96
+
97
+ def do_get(url, limit=5)
98
+ if limit == 0
99
+ return nil
100
+ end
101
+ begin
102
+ u = URI.parse(url)
103
+ http = get_http_obj(u)
104
+ resp = http.get(u.request_uri)
105
+ rescue
106
+ nil
107
+ else
108
+ case resp
109
+ when Net::HTTPSuccess then [resp, URI.parse(url).to_s]
110
+ when Net::HTTPRedirection then do_get(resp["location"], limit-1)
111
+ else
112
+ nil
113
+ end
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,315 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'tempfile'
4
+
5
+ require 'openid/util'
6
+ require 'openid/stores'
7
+ require 'openid/association'
8
+
9
+ module OpenID
10
+
11
+ # Filesystem-based store for OpenID associations and nonces.
12
+ #
13
+ # Methods of this object may raise SystemCallError if filestystem
14
+ # related errors are encountered.
15
+ class FilesystemStore < Store
16
+
17
+ @@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
18
+
19
+ # Create a FilesystemStore instance, putting all data in +directory+.
20
+ def initialize(directory)
21
+ p_dir = Pathname.new(directory)
22
+ @nonce_dir = p_dir.join('nonces')
23
+ @association_dir = p_dir.join('associations')
24
+ @temp_dir = p_dir.join('temp')
25
+ @auth_key_name = p_dir.join('auth_key')
26
+ @max_nonce_age = 6 * 60 * 60
27
+
28
+ self.ensure_dir(@nonce_dir)
29
+ self.ensure_dir(@association_dir)
30
+ self.ensure_dir(@temp_dir)
31
+ self.ensure_dir(File.dirname(@auth_key_name))
32
+ end
33
+
34
+ # Read the auth key from the auth key file. Returns nil if there
35
+ # is currently no auth key.
36
+ def read_auth_key
37
+ f = nil
38
+ begin
39
+ f = File.open(@auth_key_name)
40
+ rescue Errno::ENOENT
41
+ return nil
42
+ else
43
+ return f.read
44
+ ensure
45
+ f.close unless f.nil?
46
+ end
47
+ end
48
+
49
+ # Generate a new random auth key and safely store it in the location
50
+ # specified by @auth_key_name
51
+ def create_auth_key
52
+ auth_key = OpenID::Util.random_string(@@AUTH_KEY_LEN)
53
+ f, tmp = mktemp
54
+ begin
55
+ begin
56
+ f.write(auth_key)
57
+ f.fsync
58
+ ensure
59
+ f.close
60
+ end
61
+ begin
62
+ begin
63
+ File.link(tmp, @auth_key_name)
64
+ rescue NotImplementedError # no link under windows
65
+ File.rename(tmp, @auth_key_name)
66
+ end
67
+ rescue Errno::EEXIST
68
+ raise if read_auth_key.nil?
69
+ end
70
+ ensure
71
+ self.remove_if_present(tmp)
72
+ end
73
+
74
+ auth_key
75
+ end
76
+
77
+ # Retrieve the auth key from the file specified by
78
+ # @auth_key_file, creating it if it does not exist
79
+ def get_auth_key
80
+ auth_key = read_auth_key
81
+ if auth_key.nil?
82
+ auth_key = create_auth_key
83
+ end
84
+
85
+ if auth_key.length != @@AUTH_KEY_LEN
86
+ raise StandardError.new("Bad auth key - wrong length")
87
+ end
88
+
89
+ auth_key
90
+ end
91
+
92
+ # Create a unique filename for a given server url and handle. The
93
+ # filename that is returned will contain the domain name from the
94
+ # server URL for ease of human inspection of the data dir.
95
+ def get_association_filename(server_url, handle)
96
+ filename = self.filename_from_url(server_url)
97
+ filename += '-' + safe64(handle)
98
+ @association_dir.join(filename)
99
+ end
100
+
101
+ # Store an association in the assoc directory
102
+ def store_association(server_url, association)
103
+ assoc_s = OpenID::Association.serialize(association)
104
+ filename = get_association_filename(server_url, association.handle)
105
+ f, tmp = mktemp
106
+
107
+ begin
108
+ begin
109
+ f.write(assoc_s)
110
+ f.fsync
111
+ ensure
112
+ f.close
113
+ end
114
+
115
+ begin
116
+ File.rename(tmp, filename)
117
+ rescue Errno::EEXIST
118
+
119
+ begin
120
+ File.unlink(filename)
121
+ rescue Errno::ENOENT
122
+ # do nothing
123
+ end
124
+
125
+ File.rename(tmp, filename)
126
+ end
127
+
128
+ rescue
129
+ self.remove_if_present(tmp)
130
+ raise
131
+ end
132
+ end
133
+
134
+ # Retrieve an association
135
+ def get_association(server_url, handle=nil)
136
+ unless handle.nil?
137
+ filename = get_association_filename(server_url, handle)
138
+ return _get_association(filename)
139
+ end
140
+
141
+ # search though existing files looking for a match
142
+ prefix = filename_from_url(server_url)
143
+ assoc_filenames = Dir.entries(@association_dir)
144
+ assoc_filenames = assoc_filenames.find_all { |f| f.index(prefix) == 0 }
145
+
146
+ assocs = assoc_filenames.collect do |f|
147
+ _get_association(@association_dir.join(f))
148
+ end
149
+
150
+ assocs = assocs.find_all { |a| not a.nil? }
151
+ assocs = assocs.sort_by { |a| a.issued }
152
+
153
+ return nil if assocs.empty?
154
+ return assocs[-1]
155
+ end
156
+
157
+ def _get_association(filename)
158
+ begin
159
+ assoc_file = File.open(filename, "r")
160
+ rescue Errno::ENOENT
161
+ return nil
162
+ else
163
+ begin
164
+ assoc_s = assoc_file.read
165
+ ensure
166
+ assoc_file.close
167
+ end
168
+
169
+ begin
170
+ association = OpenID::Association.deserialize(assoc_s)
171
+ rescue
172
+ self.remove_if_present(filename)
173
+ return nil
174
+ end
175
+
176
+ # clean up expired associations
177
+ if association.expires_in == 0
178
+ self.remove_if_present(filename)
179
+ return nil
180
+ else
181
+ return association
182
+ end
183
+ end
184
+ end
185
+
186
+ # Remove an association if it exists, otherwise do nothing.
187
+ def remove_association(server_url, handle)
188
+ assoc = get_association(server_url, handle)
189
+
190
+ if assoc.nil?
191
+ return false
192
+ else
193
+ filename = get_association_filename(server_url, handle)
194
+ return self.remove_if_present(filename)
195
+ end
196
+ end
197
+
198
+ # Mark this nonce as present
199
+ def store_nonce(nonce)
200
+ filename = @nonce_dir.join(nonce)
201
+ File.open(filename, "w").close
202
+ end
203
+
204
+ # Return whether this nonce is present. As a side-effect, mark it
205
+ # as no longer present.
206
+ def use_nonce(nonce)
207
+ filename = @nonce_dir.join(nonce)
208
+ begin
209
+ st = File.stat(filename)
210
+ rescue Errno::ENOENT
211
+ return false
212
+ else
213
+ begin
214
+ File.unlink(filename)
215
+ rescue Errno::ENOENT
216
+ return false
217
+ end
218
+ nonce_age = Time.now.to_f - st.mtime.to_f
219
+ nonce_age <= @max_nonce_age
220
+ end
221
+ end
222
+
223
+ # Garbage collection routine. Clean up old associations and nonces.
224
+ def clean
225
+ nonces = Dir[@nonce_dir.join("*")]
226
+ now = Time.now
227
+
228
+ nonces.each do |nonce|
229
+ filename = nonce_dir.join(nonce)
230
+ begin
231
+ st = File.stat(filename)
232
+ rescue Errno::ENOENT
233
+ next
234
+ else
235
+ nonce_age = now - st.mtime
236
+ self.remove_if_present(filename) if nonce_age > @max_nonce_age
237
+ end
238
+ end
239
+
240
+ association_filenames = Dir[@association_dir.join("*")]
241
+ association_filenames.each do |af|
242
+ begin
243
+ f = File.open(af, 'r')
244
+ rescue Errno::ENOENT
245
+ next
246
+ else
247
+ begin
248
+ assoc_s = f.read
249
+ ensure
250
+ f.close
251
+ end
252
+ begin
253
+ association = OpenID::Association.deserialize(assoc_s)
254
+ rescue "VersionError"
255
+ self.remove_if_present(af)
256
+ next
257
+ else
258
+ self.remove_if_present(af) if association.expires_in == 0
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ protected
265
+
266
+ # Create a temporary file and return the File object and filename.
267
+ def mktemp
268
+ f = Tempfile.new('tmp', @temp_dir)
269
+ [f, f.path]
270
+ end
271
+
272
+ # create a safe filename from a url
273
+ def filename_from_url(url)
274
+ filename = []
275
+ url.sub('://','-').split("").each do |c|
276
+ if @@FILENAME_ALLOWED.index(c)
277
+ filename << c
278
+ else
279
+ filename << sprintf("_%02X", c[0])
280
+ end
281
+ end
282
+ filename.join("")
283
+ end
284
+
285
+ def safe64(s)
286
+ s = OpenID::Util.sha1(s)
287
+ s = OpenID::Util.to_base64(s)
288
+ s.gsub!('+', '_')
289
+ s.gsub!('/', '.')
290
+ s.gsub!('=', '')
291
+ return s
292
+ end
293
+
294
+ # remove file if present in filesystem
295
+ def remove_if_present(filename)
296
+ begin
297
+ File.unlink(filename)
298
+ rescue Errno::ENOENT
299
+ return false
300
+ end
301
+ return true
302
+ end
303
+
304
+ # ensure that a path exists
305
+
306
+ def ensure_dir(dir_name)
307
+ FileUtils::mkdir_p(dir_name)
308
+ end
309
+
310
+ end
311
+
312
+ end
313
+
314
+
315
+