ruby-openid 1.0

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