ruby-openid 1.0.2 → 1.1.1
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.
- data/examples/cacert.pem +7815 -0
- data/examples/consumer.rb +2 -2
- data/examples/consumerd.rb +290 -0
- data/examples/openid-store/associations/http-localhost_3A3000_2Fserver-LQl7HUNueJIJcpPoAGiHEHNdJMc +6 -0
- data/examples/openid-store/associations/http-www.myopenid.com_2Fserver-ZFp96P4qV1FjqgGt2rtZBvRJWic +6 -0
- data/examples/openid-store/auth_key +1 -0
- data/examples/openid-store/nonces/PNiw86rQ +0 -0
- data/examples/openid-store/nonces/hdZo7WC9 +0 -0
- data/examples/openid-store/nonces/uHhMdi1i +0 -0
- data/examples/rails_openid_login_generator/templates/controller.rb +1 -1
- data/examples/rails_server/app/controllers/login_controller.rb~ +35 -0
- data/examples/rails_server/app/controllers/server_controller.rb~ +190 -0
- data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-YU.tkND1J4fEZhnuAoT5Zc0yCA0 +6 -0
- data/examples/rails_server/db/openid-store/associations/http-localhost_2F_7Cnormal-jRS20gc5OzJ5pkpjy9BjqvTj3B0 +6 -0
- data/examples/rails_server/log/development.log +6459 -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/tmp/sessions/ruby_sess.1b2e9635e0f69c0d +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.1b3584d2b3784c97 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.20ed70e0e63d7e31 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.30cf5b98539677d5 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.3910508c0c857695 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.472170ef38098672 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.5406e21ba5b1c7bb +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.5d2bd2b7086f12d5 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.968757c6d12af322 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.a87a5045744b3abf +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.ca9f0a416be0be57 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.cd269e6040645b5b +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.cf2acf62b93dbc88 +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.d2ef8fe29591ef9b +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.e23240e097e2c83d +0 -0
- data/examples/rails_server/tmp/sessions/ruby_sess.fb154d2f7c286aba +0 -0
- data/lib/openid/consumer.rb +40 -71
- data/lib/openid/discovery.rb +34 -1
- data/lib/openid/discovery.rb~ +122 -0
- data/lib/openid/fetchers.rb +41 -0
- data/lib/openid/server.rb +1 -1
- data/lib/openid/service.rb +9 -2
- data/lib/openid/stores.rb~ +178 -0
- data/lib/openid/trustroot.rb +23 -10
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +3 -3
- data/test/consumer.rb +0 -8
- data/test/data/urinorm.txt +79 -0
- data/test/runtests.rb +1 -0
- data/test/service.rb +18 -1
- data/test/teststore.rb~ +47 -0
- data/test/trustroot.rb +5 -1
- data/test/urinorm.rb +32 -0
- metadata +93 -41
- data/examples/rails_openid_login_generator/templates/controller.rb~ +0 -111
- data/test/runtests.rb~ +0 -21
data/lib/openid/server.rb
CHANGED
@@ -362,7 +362,7 @@ module OpenID
|
|
362
362
|
raise MalformedReturnURL.new(query, return_to)
|
363
363
|
end
|
364
364
|
|
365
|
-
if trust_root and not OpenID::TrustRoot.parse(
|
365
|
+
if trust_root and not OpenID::TrustRoot.parse(trust_root)
|
366
366
|
raise MalformedTrustRoot.new(query, trust_root)
|
367
367
|
end
|
368
368
|
|
data/lib/openid/service.rb
CHANGED
@@ -29,7 +29,7 @@ module OpenID
|
|
29
29
|
'xrdns' => 'xri://$xrd*($v*2.0)',
|
30
30
|
'openidns' => 'http://openid.net/xmlns/1.0'
|
31
31
|
}
|
32
|
-
attr_accessor :service_types, :uri, :yadis_url, :delegate_url
|
32
|
+
attr_accessor :service_types, :uri, :yadis_url, :delegate_url, :xrds_uri, :canonical_id
|
33
33
|
|
34
34
|
# Class method to produce OpenIDService objects. Call with a Yadis Service
|
35
35
|
# object. Will return nil if the Service object does not represent an
|
@@ -40,7 +40,9 @@ module OpenID
|
|
40
40
|
s = new
|
41
41
|
s.service_types = service.service_types
|
42
42
|
s.uri = service.uri
|
43
|
-
s.yadis_url = service.yadis.uri
|
43
|
+
s.yadis_url = service.yadis.uri if service.yadis
|
44
|
+
s.xrds_uri = service.yadis.xrds_uri if service.yadis
|
45
|
+
s.canonical_id = service.canonical_id
|
44
46
|
|
45
47
|
s.delegate_url = nil
|
46
48
|
REXML::XPath.each(service.element, 'openidns:Delegate',
|
@@ -110,6 +112,10 @@ module OpenID
|
|
110
112
|
def consumer_id
|
111
113
|
@yadis_url
|
112
114
|
end
|
115
|
+
|
116
|
+
def canonical_id
|
117
|
+
@canonical_id or self.consumer_id
|
118
|
+
end
|
113
119
|
end
|
114
120
|
|
115
121
|
|
@@ -125,6 +131,7 @@ module OpenID
|
|
125
131
|
@yadis_url = consumer_id
|
126
132
|
@service_types = ['http://openid.net/signon/1.0']
|
127
133
|
@yadis = nil
|
134
|
+
@xrds_uri = nil
|
128
135
|
end
|
129
136
|
|
130
137
|
def delegate
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require "openid/util"
|
2
|
+
|
3
|
+
module OpenID
|
4
|
+
|
5
|
+
# Interface for the abstract Store
|
6
|
+
class Store
|
7
|
+
|
8
|
+
@@AUTH_KEY_LEN = 20
|
9
|
+
|
10
|
+
# Put a Association object into storace
|
11
|
+
def store_association(association)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a Association object from storage that matches
|
16
|
+
# the server_url. Returns nil if no such association is found or if
|
17
|
+
# the one matching association is expired. (Is allowed to GC expired
|
18
|
+
# associations when found.)
|
19
|
+
def get_association(server_url)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
# If there is a matching association, remove it from the store and
|
24
|
+
# return true, otherwise return false.
|
25
|
+
def removeAssociation(server_url, handle)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Stores a nonce (which is passed in as a string).
|
30
|
+
def store_nonce(nonce)
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
# If the nonce is in the store, remove it and return true. Otherwise
|
35
|
+
# return false.
|
36
|
+
def use_nonce(nonce)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a 20-byte auth key used to sign the tokens, to ensure
|
41
|
+
# that they haven't been tampered with in transit. It must return
|
42
|
+
# the same key every time it is called.
|
43
|
+
def get_auth_key
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
# Method return true if the store is dumb-mode-style store.
|
48
|
+
def dumb?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class DumbStore < Store
|
56
|
+
|
57
|
+
def initialize(secret_phrase)
|
58
|
+
require "digest/sha1"
|
59
|
+
@auth_key = Digest::SHA1.hexdigest(secret_phrase)
|
60
|
+
end
|
61
|
+
|
62
|
+
def store_association(assoc)
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_association(server_url)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def remove_association(server_url, handle)
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def store_nonce(nonce)
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def use_nonce(nonce)
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_auth_key
|
83
|
+
@auth_key
|
84
|
+
end
|
85
|
+
|
86
|
+
def dumb?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class ServerAssocs
|
93
|
+
def initialize
|
94
|
+
@assocs = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def set(assoc)
|
98
|
+
@assocs[assoc.handle] = assoc
|
99
|
+
end
|
100
|
+
|
101
|
+
def get(handle)
|
102
|
+
@assocs[handle]
|
103
|
+
end
|
104
|
+
|
105
|
+
def remove(handle)
|
106
|
+
return @assocs.delete(handle)
|
107
|
+
end
|
108
|
+
|
109
|
+
def best
|
110
|
+
best = nil
|
111
|
+
@assocs.each do |k, assoc|
|
112
|
+
if best.nil? or best.issued < assoc.issued
|
113
|
+
best = assoc
|
114
|
+
end
|
115
|
+
end
|
116
|
+
return best
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# An in-memory implementation of Store. This class is mainly used
|
121
|
+
# for testing, though it may be useful for long-running single process apps.
|
122
|
+
#
|
123
|
+
# You should probably be looking at OpenID::FilesystemStore
|
124
|
+
class MemoryStore < Store
|
125
|
+
|
126
|
+
def initialize
|
127
|
+
@server_assocs = {}
|
128
|
+
@nonces = {}
|
129
|
+
@auth_key = OpenID::Util.random_string(@@AUTH_KEY_LEN)
|
130
|
+
end
|
131
|
+
|
132
|
+
def dumb?
|
133
|
+
false
|
134
|
+
end
|
135
|
+
|
136
|
+
def store_association(server_url, assoc)
|
137
|
+
assocs = _get_server_assocs(server_url)
|
138
|
+
assocs.set(self.deepcopy(assoc))
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_association(server_url, handle=nil)
|
142
|
+
assocs = _get_server_assocs(server_url)
|
143
|
+
return assocs.best if handle.nil?
|
144
|
+
return assocs.get(handle)
|
145
|
+
end
|
146
|
+
|
147
|
+
def remove_association(server_url, handle)
|
148
|
+
assocs = _get_server_assocs(server_url)
|
149
|
+
return assocs.remove(handle)
|
150
|
+
end
|
151
|
+
|
152
|
+
def use_nonce(nonce)
|
153
|
+
return true if @nonces.delete(nonce)
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
|
157
|
+
def store_nonce(nonce)
|
158
|
+
@nonces[nonce] = true
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_auth_key
|
162
|
+
@auth_key
|
163
|
+
end
|
164
|
+
|
165
|
+
def _get_server_assocs(server_url)
|
166
|
+
unless @server_assocs.has_key?(server_url)
|
167
|
+
@server_assocs[server_url] = ServerAssocs.new
|
168
|
+
end
|
169
|
+
return @server_assocs[server_url]
|
170
|
+
end
|
171
|
+
|
172
|
+
def deepcopy(o)
|
173
|
+
Marshal.load(Marshal.dump(o))
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/lib/openid/trustroot.rb
CHANGED
@@ -63,19 +63,32 @@ module OpenID
|
|
63
63
|
return true if @host == 'localhost'
|
64
64
|
|
65
65
|
host_parts = @host.split('.')
|
66
|
-
|
66
|
+
# a note: ruby string split does not put an empty string
|
67
|
+
# at the end of the list if the split element is last. for example,
|
68
|
+
# 'foo.com.'.split('.') => ['foo','com']. Mentioned because the python
|
69
|
+
# code differs here.
|
67
70
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
return false if host_parts.length == 0
|
72
|
+
|
73
|
+
# no adjacent dots
|
74
|
+
return false if host_parts.member?('')
|
75
|
+
|
76
|
+
# last part must be a tld
|
77
|
+
tld = host_parts[-1]
|
78
|
+
return false unless TOP_LEVEL_DOMAINS.member?(tld)
|
79
|
+
|
80
|
+
return false if host_parts.length == 1
|
81
|
+
|
82
|
+
if @wildcard
|
83
|
+
if tld.length == 2 and host_parts[-2].length <= 3
|
84
|
+
# It's a 2-letter tld with a short second to last segment
|
85
|
+
# so there needs to be more than two segments specified
|
86
|
+
# (e.g. *.co.uk is insane)
|
87
|
+
return host_parts.length > 2
|
73
88
|
end
|
74
|
-
elsif host_parts[-1].length == 3
|
75
|
-
host = host_parts[0...-1]
|
76
89
|
end
|
77
|
-
|
78
|
-
return
|
90
|
+
|
91
|
+
return true
|
79
92
|
end
|
80
93
|
|
81
94
|
def validate_url(url)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
UNRESERVED = [false]*256
|
4
|
+
('A'[0]..'Z'[0]).each {|i| UNRESERVED[i] = true}
|
5
|
+
('a'[0]..'z'[0]).each {|i| UNRESERVED[i] = true}
|
6
|
+
('0'[0]..'9'[0]).each {|i| UNRESERVED[i] = true}
|
7
|
+
%W(- . _ ~).each {|i| UNRESERVED[i[0]] = true}
|
8
|
+
|
9
|
+
module OpenID
|
10
|
+
|
11
|
+
module Util
|
12
|
+
|
13
|
+
def Util._remove_dot_segments(path)
|
14
|
+
result_segments = []
|
15
|
+
|
16
|
+
while path.length > 0
|
17
|
+
if path.starts_with?('../')
|
18
|
+
path = path[3..-1]
|
19
|
+
elsif path.starts_with?('./')
|
20
|
+
path = path[2..-1]
|
21
|
+
elsif path.starts_with?('/./')
|
22
|
+
path = path[2..-1]
|
23
|
+
elsif path == '/.'
|
24
|
+
path = '/'
|
25
|
+
elsif path.starts_with?('/../')
|
26
|
+
path = path[3..-1]
|
27
|
+
result_segments.pop if result_segments.length > 0
|
28
|
+
elsif path == '/..'
|
29
|
+
path = '/'
|
30
|
+
result_segments.pop if result_segments.length > 0
|
31
|
+
elsif path == '..' or path == '.'
|
32
|
+
path = ''
|
33
|
+
else
|
34
|
+
i = 0
|
35
|
+
i = 1 if path[0].chr == '/'
|
36
|
+
i = path.index('/', i)
|
37
|
+
i = path.length if i.nil?
|
38
|
+
result_segments << path[0...i]
|
39
|
+
path = path[i..-1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
return result_segments.join('')
|
44
|
+
end
|
45
|
+
|
46
|
+
def Util.urinorm(uri)
|
47
|
+
uri = URI.parse(uri)
|
48
|
+
|
49
|
+
raise URI::InvalidURIError.new('no scheme') unless uri.scheme
|
50
|
+
uri.scheme = uri.scheme.downcase
|
51
|
+
unless ['http','https'].member?(uri.scheme)
|
52
|
+
raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI')
|
53
|
+
end
|
54
|
+
|
55
|
+
raise URI::InvalidURIError.new('no host') unless uri.host
|
56
|
+
uri.host = uri.host.downcase
|
57
|
+
|
58
|
+
uri.path = _remove_dot_segments(uri.path)
|
59
|
+
uri.path = '/' if uri.path.length == 0
|
60
|
+
|
61
|
+
uri = uri.normalize.to_s
|
62
|
+
uri = uri.gsub(/%[0-9a-zA-Z]{2}/) {
|
63
|
+
i = $&[1..2].upcase.to_i(16)
|
64
|
+
UNRESERVED[i] ? i.chr : $&.upcase
|
65
|
+
}
|
66
|
+
|
67
|
+
return uri
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/openid/util.rb
CHANGED
@@ -4,6 +4,8 @@ require "digest/sha1"
|
|
4
4
|
require "hmac-sha1"
|
5
5
|
require "uri"
|
6
6
|
|
7
|
+
require "openid/urinorm"
|
8
|
+
|
7
9
|
srand(Time.now.to_f)
|
8
10
|
|
9
11
|
class Object
|
@@ -254,11 +256,9 @@ module OpenID
|
|
254
256
|
end
|
255
257
|
|
256
258
|
begin
|
257
|
-
|
259
|
+
return Util.urinorm(url)
|
258
260
|
rescue URI::InvalidURIError
|
259
261
|
return nil
|
260
|
-
else
|
261
|
-
return parsed.normalize.to_s
|
262
262
|
end
|
263
263
|
end
|
264
264
|
|
data/test/consumer.rb
CHANGED
@@ -243,12 +243,7 @@ class TestIdRes < Test::Unit::TestCase
|
|
243
243
|
'openid.user_setup_url' => setup_url
|
244
244
|
}
|
245
245
|
|
246
|
-
# this isn't really necessary, but we'll make sure the token
|
247
|
-
# generation and extraction stuff works
|
248
|
-
token = consumer.gen_token(consumer_id, server_id, server_url)
|
249
|
-
consumer_id, server_id, server_url = consumer.split_token(token)
|
250
246
|
nonce = consumer.create_nonce
|
251
|
-
|
252
247
|
ret = consumer.do_id_res(nonce, consumer_id, server_id, server_url, query)
|
253
248
|
|
254
249
|
assert_equal(OpenID::SETUP_NEEDED, ret.status)
|
@@ -278,9 +273,6 @@ class TestCheckAuth < Test::Unit::TestCase
|
|
278
273
|
@server_url = "http://server.com/url"
|
279
274
|
@consumer_id = "consu"
|
280
275
|
@nonce = @consumer.create_nonce
|
281
|
-
@token = @consumer.gen_token(@consumer_id,
|
282
|
-
@server_id,
|
283
|
-
@server_url)
|
284
276
|
@setup_url = "http://example.com/setup-here"
|
285
277
|
end
|
286
278
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
Already normal form
|
2
|
+
http://example.com/
|
3
|
+
http://example.com/
|
4
|
+
|
5
|
+
Add a trailing slash
|
6
|
+
http://example.com
|
7
|
+
http://example.com/
|
8
|
+
|
9
|
+
Remove an empty port segment
|
10
|
+
http://example.com:/
|
11
|
+
http://example.com/
|
12
|
+
|
13
|
+
Remove a default port segment
|
14
|
+
http://example.com:80/
|
15
|
+
http://example.com/
|
16
|
+
|
17
|
+
Capitalization in host names
|
18
|
+
http://wWw.exaMPLE.COm/
|
19
|
+
http://www.example.com/
|
20
|
+
|
21
|
+
Capitalization in scheme names
|
22
|
+
htTP://example.com/
|
23
|
+
http://example.com/
|
24
|
+
|
25
|
+
Capitalization in percent-escaped reserved characters
|
26
|
+
http://example.com/foo%2cbar
|
27
|
+
http://example.com/foo%2Cbar
|
28
|
+
|
29
|
+
Unescape percent-encoded unreserved characters
|
30
|
+
http://example.com/foo%2Dbar%2dbaz
|
31
|
+
http://example.com/foo-bar-baz
|
32
|
+
|
33
|
+
remove_dot_segments example 1
|
34
|
+
http://example.com/a/b/c/./../../g
|
35
|
+
http://example.com/a/g
|
36
|
+
|
37
|
+
remove_dot_segments example 2
|
38
|
+
http://example.com/mid/content=5/../6
|
39
|
+
http://example.com/mid/6
|
40
|
+
|
41
|
+
remove_dot_segments: single-dot
|
42
|
+
http://example.com/a/./b
|
43
|
+
http://example.com/a/b
|
44
|
+
|
45
|
+
remove_dot_segments: double-dot
|
46
|
+
http://example.com/a/../b
|
47
|
+
http://example.com/b
|
48
|
+
|
49
|
+
remove_dot_segments: leading double-dot
|
50
|
+
http://example.com/../b
|
51
|
+
http://example.com/b
|
52
|
+
|
53
|
+
remove_dot_segments: trailing single-dot
|
54
|
+
http://example.com/a/.
|
55
|
+
http://example.com/a/
|
56
|
+
|
57
|
+
remove_dot_segments: trailing double-dot
|
58
|
+
http://example.com/a/..
|
59
|
+
http://example.com/
|
60
|
+
|
61
|
+
remove_dot_segments: trailing single-dot-slash
|
62
|
+
http://example.com/a/./
|
63
|
+
http://example.com/a/
|
64
|
+
|
65
|
+
remove_dot_segments: trailing double-dot-slash
|
66
|
+
http://example.com/a/../
|
67
|
+
http://example.com/
|
68
|
+
|
69
|
+
Test of all kinds of syntax-based normalization
|
70
|
+
hTTPS://a/./b/../b/%63/%7bfoo%7d
|
71
|
+
https://a/b/c/%7Bfoo%7D
|
72
|
+
|
73
|
+
Unsupported scheme
|
74
|
+
ftp://example.com/
|
75
|
+
fail
|
76
|
+
|
77
|
+
Non-absolute URI
|
78
|
+
http:/foo
|
79
|
+
fail
|