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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'openid/store/interface'
|
|
2
|
+
module OpenID
|
|
3
|
+
module Store
|
|
4
|
+
# An in-memory implementation of Store. This class is mainly used
|
|
5
|
+
# for testing, though it may be useful for long-running single
|
|
6
|
+
# process apps. Note that this store is NOT thread-safe.
|
|
7
|
+
#
|
|
8
|
+
# You should probably be looking at OpenID::Store::Filesystem
|
|
9
|
+
class Memory < Interface
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@associations = {}
|
|
13
|
+
@associations.default = {}
|
|
14
|
+
@nonces = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def store_association(server_url, assoc)
|
|
18
|
+
assocs = @associations[server_url]
|
|
19
|
+
@associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_association(server_url, handle=nil)
|
|
23
|
+
assocs = @associations[server_url]
|
|
24
|
+
assoc = nil
|
|
25
|
+
if handle
|
|
26
|
+
assoc = assocs[handle]
|
|
27
|
+
else
|
|
28
|
+
assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return assoc
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def remove_association(server_url, handle)
|
|
35
|
+
assocs = @associations[server_url]
|
|
36
|
+
if assocs.delete(handle)
|
|
37
|
+
return true
|
|
38
|
+
else
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def use_nonce(server_url, timestamp, salt)
|
|
44
|
+
return false if (timestamp - Time.now.to_i).abs > Nonce.skew
|
|
45
|
+
nonce = [server_url, timestamp, salt].join('')
|
|
46
|
+
return false if @nonces[nonce]
|
|
47
|
+
@nonces[nonce] = timestamp
|
|
48
|
+
return true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cleanup_associations
|
|
52
|
+
count = 0
|
|
53
|
+
@associations.each{|server_url, assocs|
|
|
54
|
+
assocs.each{|handle, assoc|
|
|
55
|
+
if assoc.expires_in == 0
|
|
56
|
+
assocs.delete(handle)
|
|
57
|
+
count += 1
|
|
58
|
+
end
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return count
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def cleanup_nonces
|
|
65
|
+
count = 0
|
|
66
|
+
now = Time.now.to_i
|
|
67
|
+
@nonces.each{|nonce, timestamp|
|
|
68
|
+
if (timestamp - now).abs > Nonce.skew
|
|
69
|
+
@nonces.delete(nonce)
|
|
70
|
+
count += 1
|
|
71
|
+
end
|
|
72
|
+
}
|
|
73
|
+
return count
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
protected
|
|
77
|
+
|
|
78
|
+
def deepcopy(o)
|
|
79
|
+
Marshal.load(Marshal.dump(o))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'openid/cryptutil'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module OpenID
|
|
6
|
+
module Nonce
|
|
7
|
+
DEFAULT_SKEW = 60*60*5
|
|
8
|
+
TIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
|
|
9
|
+
TIME_STR_LEN = '0000-00-00T00:00:00Z'.size
|
|
10
|
+
@@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
11
|
+
TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
|
|
12
|
+
|
|
13
|
+
@skew = DEFAULT_SKEW
|
|
14
|
+
|
|
15
|
+
# The allowed nonce time skew in seconds. Defaults to 5 hours.
|
|
16
|
+
# Used for checking nonce validity, and by stores' cleanup methods.
|
|
17
|
+
def Nonce.skew
|
|
18
|
+
@skew
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def Nonce.skew=(new_skew)
|
|
22
|
+
@skew = new_skew
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Extract timestamp from a nonce string
|
|
26
|
+
def Nonce.split_nonce(nonce_str)
|
|
27
|
+
timestamp_str = nonce_str[0...TIME_STR_LEN]
|
|
28
|
+
raise ArgumentError if timestamp_str.size < TIME_STR_LEN
|
|
29
|
+
raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR)
|
|
30
|
+
ts = Time.parse(timestamp_str).to_i
|
|
31
|
+
raise ArgumentError if ts < 0
|
|
32
|
+
return ts, nonce_str[TIME_STR_LEN..-1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Is the timestamp that is part of the specified nonce string
|
|
36
|
+
# within the allowed clock-skew of the current time?
|
|
37
|
+
def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil)
|
|
38
|
+
allowed_skew = skew if allowed_skew.nil?
|
|
39
|
+
begin
|
|
40
|
+
stamp, foo = split_nonce(nonce_str)
|
|
41
|
+
rescue ArgumentError # bad timestamp
|
|
42
|
+
return false
|
|
43
|
+
end
|
|
44
|
+
now = Time.now.to_i unless now
|
|
45
|
+
|
|
46
|
+
# times before this are too old
|
|
47
|
+
past = now - allowed_skew
|
|
48
|
+
|
|
49
|
+
# times newer than this are too far in the future
|
|
50
|
+
future = now + allowed_skew
|
|
51
|
+
|
|
52
|
+
return (past <= stamp and stamp <= future)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# generate a nonce with the specified timestamp (defaults to now)
|
|
56
|
+
def Nonce.mk_nonce(time = nil)
|
|
57
|
+
salt = CryptUtil::random_string(6, @@NONCE_CHRS)
|
|
58
|
+
if time.nil?
|
|
59
|
+
t = Time.now.getutc
|
|
60
|
+
else
|
|
61
|
+
t = Time.at(time).getutc
|
|
62
|
+
end
|
|
63
|
+
time_str = t.strftime(TIME_FMT)
|
|
64
|
+
return time_str + salt
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'openid/urinorm'
|
|
3
|
+
|
|
4
|
+
module OpenID
|
|
5
|
+
|
|
6
|
+
class RealmVerificationRedirected < Exception
|
|
7
|
+
# Attempting to verify this realm resulted in a redirect.
|
|
8
|
+
def initialize(relying_party_url, rp_url_after_redirects)
|
|
9
|
+
@relying_party_url = relying_party_url
|
|
10
|
+
@rp_url_after_redirects = rp_url_after_redirects
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
return "Attempting to verify #{@relying_party_url} resulted in " +
|
|
15
|
+
"redirect to #{@rp_url_after_redirects}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module TrustRoot
|
|
20
|
+
TOP_LEVEL_DOMAINS = %w'
|
|
21
|
+
ac ad ae aero af ag ai al am an ao aq ar arpa as asia at
|
|
22
|
+
au aw ax az ba bb bd be bf bg bh bi biz bj bm bn bo br bs bt
|
|
23
|
+
bv bw by bz ca cat cc cd cf cg ch ci ck cl cm cn co com coop
|
|
24
|
+
cr cu cv cx cy cz de dj dk dm do dz ec edu ee eg er es et eu
|
|
25
|
+
fi fj fk fm fo fr ga gb gd ge gf gg gh gi gl gm gn gov gp gq
|
|
26
|
+
gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in info int
|
|
27
|
+
io iq ir is it je jm jo jobs jp ke kg kh ki km kn kp kr kw
|
|
28
|
+
ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mil
|
|
29
|
+
mk ml mm mn mo mobi mp mq mr ms mt mu museum mv mw mx my mz
|
|
30
|
+
na name nc ne net nf ng ni nl no np nr nu nz om org pa pe pf
|
|
31
|
+
pg ph pk pl pm pn pr pro ps pt pw py qa re ro rs ru rw sa sb
|
|
32
|
+
sc sd se sg sh si sj sk sl sm sn so sr st su sv sy sz tc td
|
|
33
|
+
tel tf tg th tj tk tl tm tn to tp tr travel tt tv tw tz ua
|
|
34
|
+
ug uk us uy uz va vc ve vg vi vn vu wf ws xn--0zwm56d
|
|
35
|
+
xn--11b5bs3a9aj6g xn--80akhbyknj4f xn--9t4b11yi5a
|
|
36
|
+
xn--deba0ad xn--g6w251d xn--hgbk6aj7f53bba
|
|
37
|
+
xn--hlcj6aya9esc7a xn--jxalpdlp xn--kgbechtv xn--zckzah ye
|
|
38
|
+
yt yu za zm zw'
|
|
39
|
+
|
|
40
|
+
ALLOWED_PROTOCOLS = ['http', 'https']
|
|
41
|
+
|
|
42
|
+
# The URI for relying party discovery, used in realm verification.
|
|
43
|
+
#
|
|
44
|
+
# XXX: This should probably live somewhere else (like in
|
|
45
|
+
# OpenID or OpenID::Yadis somewhere)
|
|
46
|
+
RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to'
|
|
47
|
+
|
|
48
|
+
# If the endpoint is a relying party OpenID return_to endpoint,
|
|
49
|
+
# return the endpoint URL. Otherwise, return None.
|
|
50
|
+
#
|
|
51
|
+
# This function is intended to be used as a filter for the Yadis
|
|
52
|
+
# filtering interface.
|
|
53
|
+
#
|
|
54
|
+
# endpoint: An XRDS BasicServiceEndpoint, as returned by
|
|
55
|
+
# performing Yadis dicovery.
|
|
56
|
+
#
|
|
57
|
+
# returns the endpoint URL or None if the endpoint is not a
|
|
58
|
+
# relying party endpoint.
|
|
59
|
+
def TrustRoot._extract_return_url(endpoint)
|
|
60
|
+
if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])
|
|
61
|
+
return endpoint.uri
|
|
62
|
+
else
|
|
63
|
+
return nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Is the return_to URL under one of the supplied allowed
|
|
68
|
+
# return_to URLs?
|
|
69
|
+
def TrustRoot.return_to_matches(allowed_return_to_urls, return_to)
|
|
70
|
+
allowed_return_to_urls.each { |allowed_return_to|
|
|
71
|
+
# A return_to pattern works the same as a realm, except that
|
|
72
|
+
# it's not allowed to use a wildcard. We'll model this by
|
|
73
|
+
# parsing it as a realm, and not trying to match it if it has
|
|
74
|
+
# a wildcard.
|
|
75
|
+
|
|
76
|
+
return_realm = TrustRoot.parse(allowed_return_to)
|
|
77
|
+
if (# Parses as a trust root
|
|
78
|
+
!return_realm.nil? and
|
|
79
|
+
|
|
80
|
+
# Does not have a wildcard
|
|
81
|
+
!return_realm.wildcard and
|
|
82
|
+
|
|
83
|
+
# Matches the return_to that we passed in with it
|
|
84
|
+
return_realm.validate_url(return_to)
|
|
85
|
+
)
|
|
86
|
+
return true
|
|
87
|
+
end
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# No URL in the list matched
|
|
91
|
+
return false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Given a relying party discovery URL return a list of return_to
|
|
95
|
+
# URLs.
|
|
96
|
+
def TrustRoot.get_allowed_return_urls(relying_party_url)
|
|
97
|
+
rp_url_after_redirects, return_to_urls = services.get_service_endpoints(
|
|
98
|
+
relying_party_url, _extract_return_url)
|
|
99
|
+
|
|
100
|
+
if rp_url_after_redirects != relying_party_url
|
|
101
|
+
# Verification caused a redirect
|
|
102
|
+
raise RealmVerificationRedirected.new(
|
|
103
|
+
relying_party_url, rp_url_after_redirects)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return return_to_urls
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Verify that a return_to URL is valid for the given realm.
|
|
110
|
+
#
|
|
111
|
+
# This function builds a discovery URL, performs Yadis discovery
|
|
112
|
+
# on it, makes sure that the URL does not redirect, parses out
|
|
113
|
+
# the return_to URLs, and finally checks to see if the current
|
|
114
|
+
# return_to URL matches the return_to.
|
|
115
|
+
#
|
|
116
|
+
# raises DiscoveryFailure when Yadis discovery fails returns
|
|
117
|
+
# true if the return_to URL is valid for the realm
|
|
118
|
+
def TrustRoot.verify_return_to(realm_str, return_to, _vrfy=nil)
|
|
119
|
+
# _vrfy parameter is there to make testing easier
|
|
120
|
+
if _vrfy.nil?
|
|
121
|
+
_vrfy = self.method('get_allowed_return_urls')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if !(_vrfy.is_a?(Proc) or _vrfy.is_a?(Method))
|
|
125
|
+
raise ArgumentError, "_vrfy must be a Proc or Method"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
realm = TrustRoot.parse(realm_str)
|
|
129
|
+
if realm.nil?
|
|
130
|
+
# The realm does not parse as a URL pattern
|
|
131
|
+
return false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
begin
|
|
135
|
+
allowable_urls = _vrfy.call(realm.build_discovery_url())
|
|
136
|
+
rescue RealmVerificationRedirected => err
|
|
137
|
+
Util.log(err.to_s)
|
|
138
|
+
return false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if return_to_matches(allowable_urls, return_to)
|
|
142
|
+
return true
|
|
143
|
+
else
|
|
144
|
+
Util.log("Failed to validate return_to #{return_to} for " +
|
|
145
|
+
"realm #{realm_str}, was not in #{allowable_urls}")
|
|
146
|
+
return false
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class TrustRoot
|
|
151
|
+
|
|
152
|
+
attr_reader :unparsed, :proto, :wildcard, :host, :port, :path
|
|
153
|
+
|
|
154
|
+
@@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
|
|
155
|
+
|
|
156
|
+
def TrustRoot._build_path(path, query=nil, frag=nil)
|
|
157
|
+
s = path.dup
|
|
158
|
+
|
|
159
|
+
frag = nil if frag == ''
|
|
160
|
+
query = nil if query == ''
|
|
161
|
+
|
|
162
|
+
if query
|
|
163
|
+
s << "?" << query
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if frag
|
|
167
|
+
s << "#" << frag
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def TrustRoot._parse_url(url)
|
|
174
|
+
begin
|
|
175
|
+
url = URINorm.urinorm(url)
|
|
176
|
+
rescue URI::InvalidURIError => err
|
|
177
|
+
nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
parsed = URI::parse(url)
|
|
182
|
+
rescue URI::InvalidURIError
|
|
183
|
+
return nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
path = TrustRoot._build_path(parsed.path,
|
|
187
|
+
parsed.query,
|
|
188
|
+
parsed.fragment)
|
|
189
|
+
|
|
190
|
+
return [parsed.scheme || '', parsed.host || '',
|
|
191
|
+
parsed.port || '', path || '']
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def TrustRoot.parse(trust_root)
|
|
195
|
+
trust_root = trust_root.dup
|
|
196
|
+
unparsed = trust_root.dup
|
|
197
|
+
|
|
198
|
+
# look for wildcard
|
|
199
|
+
wildcard = (not trust_root.index('://*.').nil?)
|
|
200
|
+
trust_root.sub!('*.', '') if wildcard
|
|
201
|
+
|
|
202
|
+
# handle http://*/ case
|
|
203
|
+
if not wildcard and @@empty_re.match(trust_root)
|
|
204
|
+
proto = trust_root.split(':')[0]
|
|
205
|
+
port = proto == 'http' ? 80 : 443
|
|
206
|
+
return new(unparsed, proto, true, '', port, '/')
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
parts = TrustRoot._parse_url(trust_root)
|
|
210
|
+
return nil if parts.nil?
|
|
211
|
+
|
|
212
|
+
proto, host, port, path = parts
|
|
213
|
+
|
|
214
|
+
# check for URI fragment
|
|
215
|
+
if path and !path.index('#').nil?
|
|
216
|
+
return nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
return nil unless ['http', 'https'].member?(proto)
|
|
220
|
+
return new(unparsed, proto, wildcard, host, port, path)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def TrustRoot.check_sanity(trust_root_string)
|
|
224
|
+
trust_root = TrustRoot.parse(trust_root_string)
|
|
225
|
+
if trust_root.nil?
|
|
226
|
+
return false
|
|
227
|
+
else
|
|
228
|
+
return trust_root.sane?
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# quick func for validating a url against a trust root. See the
|
|
233
|
+
# TrustRoot class if you need more control.
|
|
234
|
+
def self.check_url(trust_root, url)
|
|
235
|
+
tr = self.parse(trust_root)
|
|
236
|
+
return (!tr.nil? and tr.validate_url(url))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Return a discovery URL for this realm.
|
|
240
|
+
#
|
|
241
|
+
# This function does not check to make sure that the realm is
|
|
242
|
+
# valid. Its behaviour on invalid inputs is undefined.
|
|
243
|
+
#
|
|
244
|
+
# return_to:: The relying party return URL of the OpenID
|
|
245
|
+
# authentication request
|
|
246
|
+
#
|
|
247
|
+
# Returns the URL upon which relying party discovery should be
|
|
248
|
+
# run in order to verify the return_to URL
|
|
249
|
+
def build_discovery_url
|
|
250
|
+
if self.wildcard
|
|
251
|
+
# Use "www." in place of the star
|
|
252
|
+
www_domain = 'www.' + @host
|
|
253
|
+
port = (!@port.nil? and ![80, 443].member?(@port)) ? (":" + @port.to_s) : ''
|
|
254
|
+
return "#{@proto}://#{www_domain}#{port}#{@path}"
|
|
255
|
+
else
|
|
256
|
+
return @unparsed
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def initialize(unparsed, proto, wildcard, host, port, path)
|
|
261
|
+
@unparsed = unparsed
|
|
262
|
+
@proto = proto
|
|
263
|
+
@wildcard = wildcard
|
|
264
|
+
@host = host
|
|
265
|
+
@port = port
|
|
266
|
+
@path = path
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def sane?
|
|
270
|
+
return true if @host == 'localhost'
|
|
271
|
+
|
|
272
|
+
host_parts = @host.split('.')
|
|
273
|
+
|
|
274
|
+
# a note: ruby string split does not put an empty string at
|
|
275
|
+
# the end of the list if the split element is last. for
|
|
276
|
+
# example, 'foo.com.'.split('.') => ['foo','com']. Mentioned
|
|
277
|
+
# because the python code differs here.
|
|
278
|
+
|
|
279
|
+
return false if host_parts.length == 0
|
|
280
|
+
|
|
281
|
+
# no adjacent dots
|
|
282
|
+
return false if host_parts.member?('')
|
|
283
|
+
|
|
284
|
+
# last part must be a tld
|
|
285
|
+
tld = host_parts[-1]
|
|
286
|
+
return false unless TOP_LEVEL_DOMAINS.member?(tld)
|
|
287
|
+
|
|
288
|
+
return false if host_parts.length == 1
|
|
289
|
+
|
|
290
|
+
if @wildcard
|
|
291
|
+
if tld.length == 2 and host_parts[-2].length <= 3
|
|
292
|
+
# It's a 2-letter tld with a short second to last segment
|
|
293
|
+
# so there needs to be more than two segments specified
|
|
294
|
+
# (e.g. *.co.uk is insane)
|
|
295
|
+
return host_parts.length > 2
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
return true
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def validate_url(url)
|
|
303
|
+
parts = TrustRoot._parse_url(url)
|
|
304
|
+
return false if parts.nil?
|
|
305
|
+
|
|
306
|
+
proto, host, port, path = parts
|
|
307
|
+
|
|
308
|
+
return false unless proto == @proto
|
|
309
|
+
return false unless port == @port
|
|
310
|
+
return false unless host.index('*').nil?
|
|
311
|
+
|
|
312
|
+
if !@wildcard
|
|
313
|
+
if host != @host
|
|
314
|
+
return false
|
|
315
|
+
end
|
|
316
|
+
elsif ((@host != '') and
|
|
317
|
+
(!host.ends_with?('.' + @host)) and
|
|
318
|
+
(host != @host))
|
|
319
|
+
return false
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if path != @path
|
|
323
|
+
path_len = @path.length
|
|
324
|
+
trust_prefix = @path[0...path_len]
|
|
325
|
+
url_prefix = path[0...path_len]
|
|
326
|
+
|
|
327
|
+
# must be equal up to the length of the path, at least
|
|
328
|
+
if trust_prefix != url_prefix
|
|
329
|
+
return false
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# These characters must be on the boundary between the end
|
|
333
|
+
# of the trust root's path and the start of the URL's path.
|
|
334
|
+
if !@path.index('?').nil?
|
|
335
|
+
allowed = '&'
|
|
336
|
+
else
|
|
337
|
+
allowed = '?/'
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
return (!allowed.index(@path[-1]).nil? or
|
|
341
|
+
!allowed.index(path[path_len]).nil?)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
return true
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|