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.
- data/COPYING +21 -0
- data/INSTALL +34 -0
- data/README +67 -0
- data/TODO +9 -0
- data/examples/README +54 -0
- data/examples/cacert.pem +7815 -0
- data/examples/consumer.rb +285 -0
- data/examples/openid-store/associations/http-localhost_3A3000_2Fserver-EMQbAy3NnHVzA.s0u5KAcplKGzo +6 -0
- data/examples/openid-store/auth_key +1 -0
- data/examples/rails_active_record_store/README +59 -0
- data/examples/rails_active_record_store/XX_add_openidstore.rb +30 -0
- data/examples/rails_active_record_store/models/openid_association.rb +12 -0
- data/examples/rails_active_record_store/models/openid_nonce.rb +3 -0
- data/examples/rails_active_record_store/models/openid_setting.rb +2 -0
- data/examples/rails_active_record_store/openid_helper.rb +91 -0
- data/examples/rails_active_record_store/openidstore_test.rb +15 -0
- data/examples/rails_active_record_store/schema.mysql.sql +22 -0
- data/examples/rails_active_record_store/schema.postgresql.sql +21 -0
- data/examples/rails_active_record_store/schema.sqlite.sql +21 -0
- data/examples/rails_openid_login_generator/USAGE +23 -0
- data/examples/rails_openid_login_generator/openid_login_generator.rb +36 -0
- data/examples/rails_openid_login_generator/templates/README +116 -0
- data/examples/rails_openid_login_generator/templates/controller.rb +116 -0
- data/examples/rails_openid_login_generator/templates/controller_test.rb +0 -0
- data/examples/rails_openid_login_generator/templates/helper.rb +2 -0
- data/examples/rails_openid_login_generator/templates/openid_login_system.rb +87 -0
- data/examples/rails_openid_login_generator/templates/user.rb +14 -0
- data/examples/rails_openid_login_generator/templates/user_test.rb +0 -0
- data/examples/rails_openid_login_generator/templates/users.yml +0 -0
- data/examples/rails_openid_login_generator/templates/view_login.rhtml +15 -0
- data/examples/rails_openid_login_generator/templates/view_logout.rhtml +10 -0
- data/examples/rails_openid_login_generator/templates/view_welcome.rhtml +9 -0
- data/examples/rails_server/README +153 -0
- data/examples/rails_server/Rakefile +10 -0
- data/examples/rails_server/app/controllers/application.rb +4 -0
- data/examples/rails_server/app/controllers/login_controller.rb +35 -0
- data/examples/rails_server/app/controllers/server_controller.rb +185 -0
- data/examples/rails_server/app/helpers/application_helper.rb +3 -0
- data/examples/rails_server/app/helpers/login_helper.rb +2 -0
- data/examples/rails_server/app/helpers/server_helper.rb +9 -0
- data/examples/rails_server/app/views/layouts/server.rhtml +61 -0
- data/examples/rails_server/app/views/login/index.rhtml +32 -0
- data/examples/rails_server/app/views/server/decide.rhtml +11 -0
- data/examples/rails_server/config/boot.rb +19 -0
- data/examples/rails_server/config/database.yml +85 -0
- data/examples/rails_server/config/environment.rb +53 -0
- data/examples/rails_server/config/environments/development.rb +19 -0
- data/examples/rails_server/config/environments/production.rb +19 -0
- data/examples/rails_server/config/environments/test.rb +19 -0
- data/examples/rails_server/config/routes.rb +23 -0
- data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-YU.tkND1J4fEZhnuAoT5Zc0yCA0 +6 -0
- data/examples/rails_server/doc/README_FOR_APP +2 -0
- data/examples/rails_server/log/development.log +6059 -0
- data/examples/rails_server/log/production.log +0 -0
- data/examples/rails_server/log/server.log +0 -0
- data/examples/rails_server/log/test.log +0 -0
- data/examples/rails_server/public/404.html +8 -0
- data/examples/rails_server/public/500.html +8 -0
- data/examples/rails_server/public/dispatch.cgi +12 -0
- data/examples/rails_server/public/dispatch.fcgi +26 -0
- data/examples/rails_server/public/dispatch.rb +12 -0
- data/examples/rails_server/public/favicon.ico +0 -0
- data/examples/rails_server/public/images/rails.png +0 -0
- data/examples/rails_server/public/javascripts/controls.js +750 -0
- data/examples/rails_server/public/javascripts/dragdrop.js +584 -0
- data/examples/rails_server/public/javascripts/effects.js +854 -0
- data/examples/rails_server/public/javascripts/prototype.js +1785 -0
- data/examples/rails_server/public/robots.txt +1 -0
- data/examples/rails_server/script/about +3 -0
- data/examples/rails_server/script/breakpointer +3 -0
- data/examples/rails_server/script/console +3 -0
- data/examples/rails_server/script/destroy +3 -0
- data/examples/rails_server/script/generate +3 -0
- data/examples/rails_server/script/performance/benchmarker +3 -0
- data/examples/rails_server/script/performance/profiler +3 -0
- data/examples/rails_server/script/plugin +3 -0
- data/examples/rails_server/script/process/reaper +3 -0
- data/examples/rails_server/script/process/spawner +3 -0
- data/examples/rails_server/script/process/spinner +3 -0
- data/examples/rails_server/script/runner +3 -0
- data/examples/rails_server/script/server +3 -0
- data/examples/rails_server/test/functional/login_controller_test.rb +18 -0
- data/examples/rails_server/test/functional/server_controller_test.rb +18 -0
- data/examples/rails_server/test/test_helper.rb +28 -0
- data/lib/hmac-md5.rb +11 -0
- data/lib/hmac-rmd160.rb +11 -0
- data/lib/hmac-sha1.rb +11 -0
- data/lib/hmac-sha2.rb +25 -0
- data/lib/hmac.rb +112 -0
- data/lib/openid/association.rb +109 -0
- data/lib/openid/consumer.rb +928 -0
- data/lib/openid/dh.rb +48 -0
- data/lib/openid/discovery.rb +89 -0
- data/lib/openid/fetchers.rb +119 -0
- data/lib/openid/filestore.rb +315 -0
- data/lib/openid/htmltokenizer.rb +355 -0
- data/lib/openid/parse.rb +23 -0
- data/lib/openid/server.rb +951 -0
- data/lib/openid/service.rb +135 -0
- data/lib/openid/stores.rb +178 -0
- data/lib/openid/trustroot.rb +100 -0
- data/lib/openid/util.rb +273 -0
- data/test/assoc.rb +38 -0
- data/test/consumer.rb +384 -0
- data/test/dh.rb +20 -0
- data/test/extensions.rb +30 -0
- data/test/linkparse.rb +305 -0
- data/test/runtests.rb +11 -0
- data/test/server2.rb +1053 -0
- data/test/storetestcase.rb +172 -0
- data/test/teststore.rb +23 -0
- data/test/trustroot.rb +113 -0
- data/test/util.rb +56 -0
- 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
|
+
|