ruby-openid 1.1.4 → 2.0.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/INSTALL +0 -9
- data/README +21 -22
- data/UPGRADE +117 -0
- data/admin/runtests.rb +36 -0
- data/examples/README +13 -21
- data/examples/active_record_openid_store/README +8 -3
- data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +4 -8
- data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
- data/examples/active_record_openid_store/lib/association.rb +2 -0
- data/examples/active_record_openid_store/lib/openid_ar_store.rb +22 -47
- data/examples/active_record_openid_store/test/store_test.rb +78 -48
- data/examples/discover +46 -0
- data/examples/{rails_server → rails_openid}/README +0 -0
- data/examples/{rails_server → rails_openid}/Rakefile +0 -0
- data/examples/{rails_server → rails_openid}/app/controllers/application.rb +0 -0
- data/examples/rails_openid/app/controllers/consumer_controller.rb +115 -0
- data/examples/{rails_server → rails_openid}/app/controllers/login_controller.rb +10 -2
- data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
- data/examples/{rails_server → rails_openid}/app/helpers/application_helper.rb +0 -0
- data/examples/{rails_server → rails_openid}/app/helpers/login_helper.rb +0 -0
- data/examples/{rails_server → rails_openid}/app/helpers/server_helper.rb +0 -0
- data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
- data/examples/rails_openid/app/views/consumer/start.rhtml +8 -0
- data/examples/{rails_server → rails_openid}/app/views/layouts/server.rhtml +0 -0
- data/examples/{rails_server → rails_openid}/app/views/login/index.rhtml +1 -1
- data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
- data/examples/{rails_server → rails_openid}/config/boot.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/database.yml +0 -0
- data/examples/{rails_server → rails_openid}/config/environment.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/development.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/production.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/environments/test.rb +0 -0
- data/examples/{rails_server → rails_openid}/config/routes.rb +2 -1
- data/examples/{rails_server → rails_openid}/doc/README_FOR_APP +0 -0
- data/examples/{rails_server → rails_openid}/public/404.html +0 -0
- data/examples/{rails_server → rails_openid}/public/500.html +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.cgi +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.fcgi +0 -0
- data/examples/{rails_server → rails_openid}/public/dispatch.rb +0 -0
- data/examples/{rails_server → rails_openid}/public/favicon.ico +0 -0
- data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/controls.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/dragdrop.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/effects.js +0 -0
- data/examples/{rails_server → rails_openid}/public/javascripts/prototype.js +0 -0
- data/examples/{rails_server → rails_openid}/public/robots.txt +0 -0
- data/examples/{rails_server → rails_openid}/script/about +0 -0
- data/examples/{rails_server → rails_openid}/script/breakpointer +0 -0
- data/examples/{rails_server → rails_openid}/script/console +0 -0
- data/examples/{rails_server → rails_openid}/script/destroy +0 -0
- data/examples/{rails_server → rails_openid}/script/generate +0 -0
- data/examples/{rails_server → rails_openid}/script/performance/benchmarker +0 -0
- data/examples/{rails_server → rails_openid}/script/performance/profiler +0 -0
- data/examples/{rails_server → rails_openid}/script/plugin +0 -0
- data/examples/{rails_server → rails_openid}/script/process/reaper +0 -0
- data/examples/{rails_server → rails_openid}/script/process/spawner +0 -0
- data/examples/{rails_server → rails_openid}/script/process/spinner +0 -0
- data/examples/{rails_server → rails_openid}/script/runner +0 -0
- data/examples/{rails_server → rails_openid}/script/server +0 -0
- data/examples/{rails_server → rails_openid}/test/functional/login_controller_test.rb +0 -0
- data/examples/{rails_server → rails_openid}/test/functional/server_controller_test.rb +0 -0
- data/examples/{rails_server → rails_openid}/test/test_helper.rb +0 -0
- data/lib/{hmac.rb → hmac/hmac.rb} +0 -0
- data/lib/{hmac-sha1.rb → hmac/sha1.rb} +1 -1
- data/lib/{hmac-sha2.rb → hmac/sha2.rb} +1 -1
- data/lib/openid/association.rb +213 -73
- data/lib/openid/consumer/associationmanager.rb +338 -0
- data/lib/openid/consumer/checkid_request.rb +175 -0
- data/lib/openid/consumer/discovery.rb +480 -0
- data/lib/openid/consumer/discovery_manager.rb +123 -0
- data/lib/openid/consumer/html_parse.rb +136 -0
- data/lib/openid/consumer/idres.rb +525 -0
- data/lib/openid/consumer/responses.rb +133 -0
- data/lib/openid/consumer.rb +280 -807
- data/lib/openid/cryptutil.rb +85 -0
- data/lib/openid/dh.rb +60 -23
- data/lib/openid/extension.rb +31 -0
- data/lib/openid/extensions/ax.rb +506 -0
- data/lib/openid/extensions/pape.rb +182 -0
- data/lib/openid/extensions/sreg.rb +275 -0
- data/lib/openid/extras.rb +11 -0
- data/lib/openid/fetchers.rb +132 -93
- data/lib/openid/kvform.rb +133 -0
- data/lib/openid/kvpost.rb +56 -0
- data/lib/openid/message.rb +534 -0
- data/lib/openid/protocolerror.rb +6 -0
- data/lib/openid/server.rb +1215 -666
- data/lib/openid/store/filesystem.rb +271 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memory.rb +84 -0
- data/lib/openid/store/nonce.rb +68 -0
- data/lib/openid/trustroot.rb +314 -87
- data/lib/openid/urinorm.rb +37 -34
- data/lib/openid/util.rb +42 -220
- 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/{htmltokenizer.rb → yadis/htmltokenizer.rb} +1 -54
- data/lib/openid/yadis/parsehtml.rb +36 -0
- data/lib/openid/yadis/services.rb +42 -0
- data/lib/openid/yadis/xrds.rb +171 -0
- data/lib/openid/yadis/xri.rb +90 -0
- data/lib/openid/yadis/xrires.rb +106 -0
- data/lib/openid.rb +1 -4
- data/test/data/accept.txt +124 -0
- data/test/data/dh.txt +29 -0
- data/test/data/example-xrds.xml +14 -0
- data/test/data/linkparse.txt +587 -0
- data/test/data/n2b64 +650 -0
- data/test/data/test1-discover.txt +137 -0
- data/test/data/test1-parsehtml.txt +128 -0
- data/test/data/test_discover/openid.html +11 -0
- data/test/data/test_discover/openid2.html +11 -0
- data/test/data/test_discover/openid2_xrds.xml +12 -0
- data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
- data/test/data/test_discover/openid_1_and_2.html +11 -0
- data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
- data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
- data/test/data/test_discover/openid_and_yadis.html +12 -0
- data/test/data/test_discover/openid_no_delegate.html +10 -0
- data/test/data/test_discover/yadis_0entries.xml +12 -0
- data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
- data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
- data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
- data/test/data/test_discover/yadis_another_delegate.xml +14 -0
- data/test/data/test_discover/yadis_idp.xml +12 -0
- data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
- data/test/data/test_discover/yadis_no_delegate.xml +11 -0
- data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
- data/test/data/test_xrds/README +12 -0
- data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
- data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
- data/test/data/test_xrds/delegated-20060809.xrds +34 -0
- data/test/data/test_xrds/no-xrd.xml +7 -0
- data/test/data/test_xrds/not-xrds.xml +2 -0
- data/test/data/test_xrds/prefixsometimes.xrds +34 -0
- data/test/data/test_xrds/ref.xrds +109 -0
- data/test/data/test_xrds/sometimesprefix.xrds +34 -0
- data/test/data/test_xrds/spoof1.xrds +25 -0
- data/test/data/test_xrds/spoof2.xrds +25 -0
- data/test/data/test_xrds/spoof3.xrds +37 -0
- data/test/data/test_xrds/status222.xrds +9 -0
- data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/data/trustroot.txt +147 -0
- data/test/discoverdata.rb +131 -0
- data/test/test_accept.rb +170 -0
- data/test/test_association.rb +266 -0
- data/test/test_associationmanager.rb +899 -0
- data/test/test_ax.rb +587 -0
- data/test/test_checkid_request.rb +297 -0
- data/test/test_consumer.rb +257 -0
- data/test/test_cryptutil.rb +117 -0
- data/test/test_dh.rb +86 -0
- data/test/test_discover.rb +772 -0
- data/test/test_discovery_manager.rb +262 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +472 -0
- data/test/test_filters.rb +270 -0
- data/test/test_idres.rb +816 -0
- data/test/test_kvform.rb +165 -0
- data/test/test_kvpost.rb +65 -0
- data/test/test_linkparse.rb +101 -0
- data/test/test_message.rb +1058 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_openid_yadis.rb +178 -0
- data/test/test_pape.rb +233 -0
- data/test/test_parsehtml.rb +80 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2270 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +269 -0
- data/test/test_trustroot.rb +112 -0
- data/test/{urinorm.rb → test_urinorm.rb} +6 -3
- data/test/test_util.rb +144 -0
- data/test/test_xrds.rb +160 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +63 -0
- data/test/test_yadis_discovery.rb +207 -0
- data/test/testutil.rb +116 -0
- data/test/util.rb +47 -50
- metadata +233 -143
- data/examples/consumer.rb +0 -290
- data/examples/rails_openid_login_generator/openid_login_generator-0.1.gem +0 -0
- data/examples/rails_server/app/controllers/server_controller.rb +0 -190
- data/examples/rails_server/app/views/server/decide.rhtml +0 -11
- data/examples/rails_server/public/images/rails.png +0 -0
- data/lib/hmac-md5.rb +0 -11
- data/lib/hmac-rmd160.rb +0 -11
- data/lib/openid/discovery.rb +0 -122
- data/lib/openid/filestore.rb +0 -315
- data/lib/openid/parse.rb +0 -23
- data/lib/openid/service.rb +0 -147
- data/lib/openid/stores.rb +0 -178
- data/test/assoc.rb +0 -38
- data/test/consumer.rb +0 -376
- data/test/data/brian.xrds +0 -16
- data/test/data/brianellin.mylid.xrds +0 -42
- data/test/dh.rb +0 -20
- data/test/extensions.rb +0 -30
- data/test/linkparse.rb +0 -305
- data/test/runtests.rb +0 -22
- data/test/server2.rb +0 -1053
- data/test/service.rb +0 -47
- data/test/storetestcase.rb +0 -172
- data/test/teststore.rb +0 -47
- data/test/trustroot.rb +0 -117
@@ -0,0 +1,136 @@
|
|
1
|
+
require "openid/yadis/htmltokenizer"
|
2
|
+
|
3
|
+
module OpenID
|
4
|
+
|
5
|
+
REFLAGS = Regexp::MULTILINE | Regexp::IGNORECASE | Regexp::EXTENDED
|
6
|
+
|
7
|
+
# Stuff to remove before we start looking for tags
|
8
|
+
REMOVED_RE = Regexp.compile('
|
9
|
+
# Comments
|
10
|
+
<!--.*?-->
|
11
|
+
|
12
|
+
# CDATA blocks
|
13
|
+
| <!\[CDATA\[.*?\]\]>
|
14
|
+
|
15
|
+
# script blocks
|
16
|
+
| <script\b
|
17
|
+
|
18
|
+
# make sure script is not an XML namespace
|
19
|
+
(?!:)
|
20
|
+
|
21
|
+
[^>]*>.*?</script>
|
22
|
+
|
23
|
+
', REFLAGS, 'u')
|
24
|
+
|
25
|
+
def OpenID.openid_unescape(s)
|
26
|
+
s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"')
|
27
|
+
end
|
28
|
+
|
29
|
+
def OpenID.unescape_hash(h)
|
30
|
+
newh = {}
|
31
|
+
h.map{|k,v|
|
32
|
+
newh[k]=openid_unescape(v)
|
33
|
+
}
|
34
|
+
newh
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def OpenID.parse_link_attrs(html)
|
39
|
+
stripped = html.gsub(REMOVED_RE,'')
|
40
|
+
parser = HTMLTokenizer.new(stripped)
|
41
|
+
|
42
|
+
links = []
|
43
|
+
# to keep track of whether or not we are in the head element
|
44
|
+
in_head = false
|
45
|
+
in_html = false
|
46
|
+
saw_head = false
|
47
|
+
|
48
|
+
begin
|
49
|
+
while el = parser.getTag('head', '/head', 'link', 'body', '/body',
|
50
|
+
'html', '/html')
|
51
|
+
|
52
|
+
# we are leaving head or have reached body, so we bail
|
53
|
+
return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)
|
54
|
+
|
55
|
+
# enforce html > head > link
|
56
|
+
if el.tag_name == 'html'
|
57
|
+
in_html = true
|
58
|
+
end
|
59
|
+
next unless in_html
|
60
|
+
if el.tag_name == 'head'
|
61
|
+
if saw_head
|
62
|
+
return links #only allow one head
|
63
|
+
end
|
64
|
+
saw_head = true
|
65
|
+
unless el.to_s[-2] == 47 # tag ends with a /: a short tag
|
66
|
+
in_head = true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
next unless in_head
|
70
|
+
|
71
|
+
return links if el.tag_name == 'html'
|
72
|
+
|
73
|
+
if el.tag_name == 'link'
|
74
|
+
links << unescape_hash(el.attr_hash)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
rescue RuntimeError # just stop parsing if there's an error
|
79
|
+
end
|
80
|
+
return links
|
81
|
+
end
|
82
|
+
|
83
|
+
def OpenID.rel_matches(rel_attr, target_rel)
|
84
|
+
# Does this target_rel appear in the rel_str?
|
85
|
+
# XXX: TESTME
|
86
|
+
rels = rel_attr.strip().split()
|
87
|
+
rels.each { |rel|
|
88
|
+
rel = rel.downcase
|
89
|
+
if rel == target_rel
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
def OpenID.link_has_rel(link_attrs, target_rel)
|
98
|
+
# Does this link have target_rel as a relationship?
|
99
|
+
|
100
|
+
# XXX: TESTME
|
101
|
+
rel_attr = link_attrs['rel']
|
102
|
+
return (rel_attr and rel_matches(rel_attr, target_rel))
|
103
|
+
end
|
104
|
+
|
105
|
+
def OpenID.find_links_rel(link_attrs_list, target_rel)
|
106
|
+
# Filter the list of link attributes on whether it has target_rel
|
107
|
+
# as a relationship.
|
108
|
+
|
109
|
+
# XXX: TESTME
|
110
|
+
matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
|
111
|
+
result = []
|
112
|
+
|
113
|
+
link_attrs_list.each { |item|
|
114
|
+
if matchesTarget.call(item)
|
115
|
+
result << item
|
116
|
+
end
|
117
|
+
}
|
118
|
+
|
119
|
+
return result
|
120
|
+
end
|
121
|
+
|
122
|
+
def OpenID.find_first_href(link_attrs_list, target_rel)
|
123
|
+
# Return the value of the href attribute for the first link tag in
|
124
|
+
# the list that has target_rel as a relationship.
|
125
|
+
|
126
|
+
# XXX: TESTME
|
127
|
+
matches = find_links_rel(link_attrs_list, target_rel)
|
128
|
+
if !matches or matches.empty?
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
|
132
|
+
first = matches[0]
|
133
|
+
return first['href']
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
@@ -0,0 +1,525 @@
|
|
1
|
+
require "openid/message"
|
2
|
+
require "openid/protocolerror"
|
3
|
+
require "openid/kvpost"
|
4
|
+
require "openid/consumer/discovery"
|
5
|
+
|
6
|
+
module OpenID
|
7
|
+
class TypeURIMismatch < ProtocolError
|
8
|
+
attr_reader :type_uri, :endpoint
|
9
|
+
|
10
|
+
def initialize(type_uri, endpoint)
|
11
|
+
@type_uri = type_uri
|
12
|
+
@endpoint = endpoint
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Consumer
|
17
|
+
@openid1_return_to_nonce_name = 'rp_nonce'
|
18
|
+
@openid1_return_to_claimed_id_name = 'openid1_claimed_id'
|
19
|
+
|
20
|
+
# Set the name of the query parameter that this library will use
|
21
|
+
# to thread a nonce through an OpenID 1 transaction. It will be
|
22
|
+
# appended to the return_to URL.
|
23
|
+
def self.openid1_return_to_nonce_name=(query_arg_name)
|
24
|
+
@openid1_return_to_nonce_name = query_arg_name
|
25
|
+
end
|
26
|
+
|
27
|
+
# See openid1_return_to_nonce_name= documentation
|
28
|
+
def self.openid1_return_to_nonce_name
|
29
|
+
@openid1_return_to_nonce_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set the name of the query parameter that this library will use
|
33
|
+
# to thread the requested URL through an OpenID 1 transaction (for
|
34
|
+
# use when verifying discovered information). It will be appended
|
35
|
+
# to the return_to URL.
|
36
|
+
def self.openid1_return_to_claimed_id_name=(query_arg_name)
|
37
|
+
@openid1_return_to_claimed_id_name = query_arg_name
|
38
|
+
end
|
39
|
+
|
40
|
+
# See openid1_return_to_claimed_id_name=
|
41
|
+
def self.openid1_return_to_claimed_id_name
|
42
|
+
@openid1_return_to_claimed_id_name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Handles an openid.mode=id_res response. This object is
|
46
|
+
# instantiated and used by the Consumer.
|
47
|
+
class IdResHandler
|
48
|
+
attr_reader :endpoint, :message
|
49
|
+
|
50
|
+
def initialize(message, return_to, store=nil, endpoint=nil)
|
51
|
+
@store = store # Fer the nonce and invalidate_handle
|
52
|
+
@message = message
|
53
|
+
@endpoint = endpoint
|
54
|
+
@return_to = return_to
|
55
|
+
@signed_list = nil
|
56
|
+
|
57
|
+
# Start the verification process
|
58
|
+
id_res
|
59
|
+
end
|
60
|
+
|
61
|
+
def signed_fields
|
62
|
+
signed_list.map {|x| 'openid.' + x}
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# This method will raise ProtocolError unless the request is a
|
68
|
+
# valid id_res response. Once it has been verified, the methods
|
69
|
+
# 'endpoint', 'message', and 'signed_fields' contain the
|
70
|
+
# verified information.
|
71
|
+
def id_res
|
72
|
+
check_for_fields
|
73
|
+
verify_return_to
|
74
|
+
verify_discovery_results
|
75
|
+
check_signature
|
76
|
+
check_nonce
|
77
|
+
end
|
78
|
+
|
79
|
+
def server_url
|
80
|
+
@endpoint.nil? ? nil : @endpoint.server_url
|
81
|
+
end
|
82
|
+
|
83
|
+
def openid_namespace
|
84
|
+
@message.get_openid_namespace
|
85
|
+
end
|
86
|
+
|
87
|
+
def fetch(field, default=NO_DEFAULT)
|
88
|
+
@message.get_arg(OPENID_NS, field, default)
|
89
|
+
end
|
90
|
+
|
91
|
+
def signed_list
|
92
|
+
if @signed_list.nil?
|
93
|
+
signed_list_str = fetch('signed', nil)
|
94
|
+
if signed_list_str.nil?
|
95
|
+
raise ProtocolError, 'Response missing signed list'
|
96
|
+
end
|
97
|
+
|
98
|
+
@signed_list = signed_list_str.split(',', -1)
|
99
|
+
end
|
100
|
+
@signed_list
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_for_fields
|
104
|
+
# XXX: if a field is missing, we should not have to explicitly
|
105
|
+
# check that it's present, just make sure that the fields are
|
106
|
+
# actually being used by the rest of the code in
|
107
|
+
# tests. Although, which fields are signed does need to be
|
108
|
+
# checked somewhere.
|
109
|
+
basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
|
110
|
+
basic_sig_fields = ['return_to', 'identity']
|
111
|
+
|
112
|
+
case openid_namespace
|
113
|
+
when OPENID2_NS
|
114
|
+
require_fields = basic_fields + ['op_endpoint']
|
115
|
+
require_sigs = basic_sig_fields +
|
116
|
+
['response_nonce', 'claimed_id', 'assoc_handle',]
|
117
|
+
when OPENID1_NS
|
118
|
+
require_fields = basic_fields + ['identity']
|
119
|
+
require_sigs = basic_sig_fields
|
120
|
+
else
|
121
|
+
raise RuntimeError, "check_for_fields doesn't know about "\
|
122
|
+
"namespace #{openid_namespace.inspect}"
|
123
|
+
end
|
124
|
+
|
125
|
+
require_fields.each do |field|
|
126
|
+
if !@message.has_key?(OPENID_NS, field)
|
127
|
+
raise ProtocolError, "Missing required field #{field}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
require_sigs.each do |field|
|
132
|
+
# Field is present and not in signed list
|
133
|
+
if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
|
134
|
+
raise ProtocolError, "#{field.inspect} not signed"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def verify_return_to
|
140
|
+
begin
|
141
|
+
msg_return_to = URI.parse(fetch('return_to'))
|
142
|
+
rescue URI::InvalidURIError
|
143
|
+
raise ProrocolError("return_to is not a valid URI")
|
144
|
+
end
|
145
|
+
|
146
|
+
verify_return_to_args(msg_return_to)
|
147
|
+
if !@return_to.nil?
|
148
|
+
verify_return_to_base(msg_return_to)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def verify_return_to_args(msg_return_to)
|
153
|
+
return_to_parsed_query = {}
|
154
|
+
if !msg_return_to.query.nil?
|
155
|
+
CGI.parse(msg_return_to.query).each_pair do |k, vs|
|
156
|
+
return_to_parsed_query[k] = vs[0]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
query = @message.to_post_args
|
160
|
+
return_to_parsed_query.each_pair do |rt_key, rt_val|
|
161
|
+
msg_val = query[rt_key]
|
162
|
+
if msg_val.nil?
|
163
|
+
raise ProtocolError, "Message missing return_to argument #{rt_key}"
|
164
|
+
elsif msg_val != rt_val
|
165
|
+
raise ProtocolError, ("Parameter #{rt_key} value "\
|
166
|
+
"#{msg_val.inspect} does not match "\
|
167
|
+
"return_to's value #{rt_val.inspect}")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
@message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
|
171
|
+
if return_to_parsed_query[bare_key] != bare_val
|
172
|
+
raise ProtocolError, ("Parameter #{bare_key} does not match "\
|
173
|
+
"return_to URL")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def verify_return_to_base(msg_return_to)
|
179
|
+
begin
|
180
|
+
app_parsed = URI.parse(@return_to)
|
181
|
+
rescue URI::InvalidURIError
|
182
|
+
raise ProtocolError, "return_to is not a valid URI"
|
183
|
+
end
|
184
|
+
|
185
|
+
[:scheme, :host, :port, :path].each do |meth|
|
186
|
+
if msg_return_to.send(meth) != app_parsed.send(meth)
|
187
|
+
raise ProtocolError, "return_to #{meth.to_s} does not match"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Raises ProtocolError if the signature is bad
|
193
|
+
def check_signature
|
194
|
+
if @store.nil?
|
195
|
+
assoc = nil
|
196
|
+
else
|
197
|
+
assoc = @store.get_association(server_url, fetch('assoc_handle'))
|
198
|
+
end
|
199
|
+
|
200
|
+
if assoc.nil?
|
201
|
+
check_auth
|
202
|
+
else
|
203
|
+
if assoc.expires_in <= 0
|
204
|
+
# XXX: It might be a good idea sometimes to re-start the
|
205
|
+
# authentication with a new association. Doing it
|
206
|
+
# automatically opens the possibility for
|
207
|
+
# denial-of-service by a server that just returns expired
|
208
|
+
# associations (or really short-lived associations)
|
209
|
+
raise ProtocolError, "Association with #{server_url} expired"
|
210
|
+
elsif !assoc.check_message_signature(@message)
|
211
|
+
raise ProtocolError, "Bad signature in response from #{server_url}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def check_auth
|
217
|
+
Util.log("Using 'check_authentication' with #{server_url}")
|
218
|
+
begin
|
219
|
+
request = create_check_auth_request
|
220
|
+
rescue Message::KeyNotFound => why
|
221
|
+
raise ProtocolError, "Could not generate 'check_authentication' "\
|
222
|
+
"request: #{why.message}"
|
223
|
+
end
|
224
|
+
|
225
|
+
begin
|
226
|
+
response = OpenID.make_kv_post(request, server_url)
|
227
|
+
rescue ServerError => why
|
228
|
+
raise ProtocolError, "Error from #{server_url} during "\
|
229
|
+
"check_authentication: #{why.message}"
|
230
|
+
end
|
231
|
+
|
232
|
+
process_check_auth_response(response)
|
233
|
+
end
|
234
|
+
|
235
|
+
def create_check_auth_request
|
236
|
+
check_args = {}
|
237
|
+
|
238
|
+
# Arguments that are always passed to the server and not
|
239
|
+
# included in the signature.
|
240
|
+
for k in ['assoc_handle', 'sig', 'signed', 'invalidate_handle']
|
241
|
+
val = fetch(k, nil)
|
242
|
+
if !val.nil?
|
243
|
+
check_args[k] = val
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
for k in signed_list
|
248
|
+
val = @message.get_aliased_arg(k, NO_DEFAULT)
|
249
|
+
check_args[k] = val
|
250
|
+
end
|
251
|
+
|
252
|
+
check_args['mode'] = 'check_authentication'
|
253
|
+
return Message.from_openid_args(check_args)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Process the response message from a check_authentication
|
257
|
+
# request, invalidating associations if requested.
|
258
|
+
def process_check_auth_response(response)
|
259
|
+
is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')
|
260
|
+
|
261
|
+
invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')
|
262
|
+
if !invalidate_handle.nil?
|
263
|
+
Util.log("Received 'invalidate_handle' from server #{server_url}")
|
264
|
+
if @store.nil?
|
265
|
+
Util.log('Unexpectedly got "invalidate_handle" without a store!')
|
266
|
+
else
|
267
|
+
@store.remove_association(server_url, invalidate_handle)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
if is_valid != 'true'
|
272
|
+
raise ProtocolError, ("Server #{server_url} responds that the "\
|
273
|
+
"'check_authentication' call is not valid")
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def check_nonce
|
278
|
+
case openid_namespace
|
279
|
+
when OPENID1_NS
|
280
|
+
nonce =
|
281
|
+
@message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)
|
282
|
+
|
283
|
+
# We generated the nonce, so it uses the empty string as the
|
284
|
+
# server URL
|
285
|
+
server_url = ''
|
286
|
+
when OPENID2_NS
|
287
|
+
nonce = @message.get_arg(OPENID2_NS, 'response_nonce')
|
288
|
+
server_url = self.server_url
|
289
|
+
else
|
290
|
+
raise StandardError, 'Not reached'
|
291
|
+
end
|
292
|
+
|
293
|
+
if nonce.nil?
|
294
|
+
raise ProtocolError, 'Nonce missing from response'
|
295
|
+
end
|
296
|
+
|
297
|
+
begin
|
298
|
+
time, extra = Nonce.split_nonce(nonce)
|
299
|
+
rescue ArgumentError => why
|
300
|
+
raise ProtocolError, "Malformed nonce: #{nonce.inspect}"
|
301
|
+
end
|
302
|
+
|
303
|
+
if !@store.nil? && !@store.use_nonce(server_url, time, extra)
|
304
|
+
raise ProtocolError, ("Nonce already used or out of range: "\
|
305
|
+
"#{nonce.inspect}")
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def verify_discovery_results
|
310
|
+
begin
|
311
|
+
case openid_namespace
|
312
|
+
when OPENID1_NS
|
313
|
+
verify_discovery_results_openid1
|
314
|
+
when OPENID2_NS
|
315
|
+
verify_discovery_results_openid2
|
316
|
+
else
|
317
|
+
raise StandardError, "Not reached: #{openid_namespace}"
|
318
|
+
end
|
319
|
+
rescue Message::KeyNotFound => why
|
320
|
+
raise ProtocolError, "Missing required field: #{why.message}"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def verify_discovery_results_openid2
|
325
|
+
to_match = OpenIDServiceEndpoint.new
|
326
|
+
to_match.type_uris = [OPENID_2_0_TYPE]
|
327
|
+
to_match.claimed_id = fetch('claimed_id', nil)
|
328
|
+
to_match.local_id = fetch('identity', nil)
|
329
|
+
to_match.server_url = fetch('op_endpoint')
|
330
|
+
|
331
|
+
if to_match.claimed_id.nil? && !to_match.local_id.nil?
|
332
|
+
raise ProtocolError, ('openid.identity is present without '\
|
333
|
+
'openid.claimed_id')
|
334
|
+
elsif !to_match.claimed_id.nil? && to_match.local_id.nil?
|
335
|
+
raise ProtocolError, ('openid.claimed_id is present without '\
|
336
|
+
'openid.identity')
|
337
|
+
|
338
|
+
# This is a response without identifiers, so there's really no
|
339
|
+
# checking that we can do, so return an endpoint that's for
|
340
|
+
# the specified `openid.op_endpoint'
|
341
|
+
elsif to_match.claimed_id.nil?
|
342
|
+
@endpoint =
|
343
|
+
OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)
|
344
|
+
return
|
345
|
+
end
|
346
|
+
|
347
|
+
if @endpoint.nil?
|
348
|
+
Util.log('No pre-discovered information supplied')
|
349
|
+
discover_and_verify(to_match)
|
350
|
+
else
|
351
|
+
begin
|
352
|
+
verify_discovery_single(@endpoint, to_match)
|
353
|
+
rescue ProtocolError => why
|
354
|
+
Util.log("Error attempting to use stored discovery "\
|
355
|
+
"information: #{why.message}")
|
356
|
+
Util.log("Attempting discovery to verify endpoint")
|
357
|
+
discover_and_verify(to_match)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
if @endpoint.claimed_id != to_match.claimed_id
|
362
|
+
@endpoint = @endpoint.dup
|
363
|
+
@endpoint.claimed_id = to_match.claimed_id
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def verify_discovery_results_openid1
|
368
|
+
claimed_id =
|
369
|
+
@message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)
|
370
|
+
|
371
|
+
if claimed_id.nil?
|
372
|
+
if @endpoint.nil?
|
373
|
+
raise StandardError, ("When using OpenID 1, the claimed ID must "\
|
374
|
+
"be supplied, either by passing it through "\
|
375
|
+
"as a return_to parameter or by using a "\
|
376
|
+
"session, and supplied to the IdResHandler "\
|
377
|
+
"when it is constructed.")
|
378
|
+
else
|
379
|
+
claimed_id = @endpoint.claimed_id
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
to_match = OpenIDServiceEndpoint.new
|
384
|
+
to_match.type_uris = [OPENID_1_1_TYPE]
|
385
|
+
to_match.local_id = fetch('identity')
|
386
|
+
# Restore delegate information from the initiation phase
|
387
|
+
to_match.claimed_id = claimed_id
|
388
|
+
|
389
|
+
to_match_1_0 = to_match.dup
|
390
|
+
to_match_1_0.type_uris = [OPENID_1_0_TYPE]
|
391
|
+
|
392
|
+
if !@endpoint.nil?
|
393
|
+
begin
|
394
|
+
begin
|
395
|
+
verify_discovery_single(@endpoint, to_match)
|
396
|
+
rescue TypeURIMismatch
|
397
|
+
verify_discovery_single(@endpoint, to_match_1_0)
|
398
|
+
end
|
399
|
+
rescue ProtocolError => why
|
400
|
+
Util.log('Error attempting to use stored discovery information: ' +
|
401
|
+
why.message)
|
402
|
+
Util.log('Attempting discovery to verify endpoint')
|
403
|
+
else
|
404
|
+
return @endpoint
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Either no endpoint was supplied or OpenID 1.x verification
|
409
|
+
# of the information that's in the message failed on that
|
410
|
+
# endpoint.
|
411
|
+
begin
|
412
|
+
discover_and_verify(to_match)
|
413
|
+
rescue TypeURIMismatch
|
414
|
+
discover_and_verify(to_match_1_0)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Given an endpoint object created from the information in an
|
419
|
+
# OpenID response, perform discovery and verify the discovery
|
420
|
+
# results, returning the matching endpoint that is the result of
|
421
|
+
# doing that discovery.
|
422
|
+
def discover_and_verify(to_match)
|
423
|
+
Util.log("Performing discovery on #{to_match.claimed_id}")
|
424
|
+
_, services = OpenID.discover(to_match.claimed_id)
|
425
|
+
if services.length == 0
|
426
|
+
# XXX: this might want to be something other than
|
427
|
+
# ProtocolError. In Python, it's DiscoveryFailure
|
428
|
+
raise ProtocolError("No OpenID information found at "\
|
429
|
+
"#{to_match.claimed_id}")
|
430
|
+
end
|
431
|
+
verify_discovered_services(services, to_match)
|
432
|
+
end
|
433
|
+
|
434
|
+
|
435
|
+
def verify_discovered_services(services, to_match)
|
436
|
+
# Search the services resulting from discovery to find one
|
437
|
+
# that matches the information from the assertion
|
438
|
+
failure_messages = []
|
439
|
+
for endpoint in services
|
440
|
+
begin
|
441
|
+
verify_discovery_single(endpoint, to_match)
|
442
|
+
rescue ProtocolError => why
|
443
|
+
failure_messages << why.message
|
444
|
+
else
|
445
|
+
# It matches, so discover verification has
|
446
|
+
# succeeded. Return this endpoint.
|
447
|
+
@endpoint = endpoint
|
448
|
+
return
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
Util.log("Discovery verification failure for #{to_match.claimed_id}")
|
453
|
+
failure_messages.each do |failure_message|
|
454
|
+
Util.log(" * Endpoint mismatch: " + failure_message)
|
455
|
+
end
|
456
|
+
|
457
|
+
# XXX: is DiscoveryFailure in Python OpenID
|
458
|
+
raise ProtocolError("No matching endpoint found after "\
|
459
|
+
"discovering #{to_match.claimed_id}")
|
460
|
+
end
|
461
|
+
|
462
|
+
def verify_discovery_single(endpoint, to_match)
|
463
|
+
# Every type URI that's in the to_match endpoint has to be
|
464
|
+
# present in the discovered endpoint.
|
465
|
+
for type_uri in to_match.type_uris
|
466
|
+
if !endpoint.uses_extension(type_uri)
|
467
|
+
raise TypeURIMismatch.new(type_uri, endpoint)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Fragments do not influence discovery, so we can't compare a
|
472
|
+
# claimed identifier with a fragment to discovered information.
|
473
|
+
defragged_claimed_id =
|
474
|
+
case Yadis::XRI.identifier_scheme(endpoint.claimed_id)
|
475
|
+
when :xri
|
476
|
+
endpoint.claimed_id
|
477
|
+
when :uri
|
478
|
+
begin
|
479
|
+
parsed = URI.parse(endpoint.claimed_id)
|
480
|
+
rescue URI::InvalidURIError
|
481
|
+
endpoint.claimed_id
|
482
|
+
else
|
483
|
+
parsed.fragment = nil
|
484
|
+
parsed.to_s
|
485
|
+
end
|
486
|
+
else
|
487
|
+
raise StandardError, 'Not reached'
|
488
|
+
end
|
489
|
+
|
490
|
+
if defragged_claimed_id != endpoint.claimed_id
|
491
|
+
raise ProtocolError, ("Claimed ID does not match (different "\
|
492
|
+
"subjects!), Expected "\
|
493
|
+
"#{defragged_claimed_id}, got "\
|
494
|
+
"#{endpoint.claimed_id}")
|
495
|
+
end
|
496
|
+
|
497
|
+
if to_match.get_local_id != endpoint.get_local_id
|
498
|
+
raise ProtocolError, ("local_id mismatch. Expected "\
|
499
|
+
"#{to_match.get_local_id}, got "\
|
500
|
+
"#{endpoint.get_local_id}")
|
501
|
+
end
|
502
|
+
|
503
|
+
# If the server URL is nil, this must be an OpenID 1
|
504
|
+
# response, because op_endpoint is a required parameter in
|
505
|
+
# OpenID 2. In that case, we don't actually care what the
|
506
|
+
# discovered server_url is, because signature checking or
|
507
|
+
# check_auth should take care of that check for us.
|
508
|
+
if to_match.server_url.nil?
|
509
|
+
if to_match.preferred_namespace != OPENID1_NS
|
510
|
+
raise StandardError,
|
511
|
+
"The code calling this must ensure that OpenID 2 "\
|
512
|
+
"responses have a non-none `openid.op_endpoint' and "\
|
513
|
+
"that it is set as the `server_url' attribute of the "\
|
514
|
+
"`to_match' endpoint."
|
515
|
+
end
|
516
|
+
elsif to_match.server_url != endpoint.server_url
|
517
|
+
raise ProtocolError, ("OP Endpoint mismatch. Expected"\
|
518
|
+
"#{to_match.server_url}, got "\
|
519
|
+
"#{endpoint.server_url}")
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|