nov-ruby-openid 2.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +215 -0
- data/CHANGES-2.1.0 +36 -0
- data/INSTALL +47 -0
- data/LICENSE +210 -0
- data/NOTICE +2 -0
- data/README +81 -0
- data/Rakefile +98 -0
- data/UPGRADE +127 -0
- data/VERSION +1 -0
- data/contrib/google/ruby-openid-apps-discovery-1.0.gem +0 -0
- data/contrib/google/ruby-openid-apps-discovery-1.01.gem +0 -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 +122 -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/.htaccess +40 -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 +20 -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 +148 -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/extensions/ui.rb +53 -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 +8 -0
- data/lib/openid/server.rb +1544 -0
- data/lib/openid/store/filesystem.rb +271 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memcache.rb +107 -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 +110 -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 +99 -0
- data/setup.rb +1551 -0
- 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 +152 -0
- data/test/data/test_discover/malformed_meta_tag.html +19 -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/openid_utf8.html +11 -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/subsegments.xrds +58 -0
- data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/data/trustroot.txt +153 -0
- data/test/data/urinorm.txt +79 -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 +917 -0
- data/test/test_ax.rb +690 -0
- data/test/test_checkid_request.rb +294 -0
- data/test/test_consumer.rb +257 -0
- data/test/test_cryptutil.rb +119 -0
- data/test/test_dh.rb +86 -0
- data/test/test_discover.rb +852 -0
- data/test/test_discovery_manager.rb +262 -0
- data/test/test_extension.rb +46 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +565 -0
- data/test/test_filters.rb +270 -0
- data/test/test_idres.rb +963 -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 +1116 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_oauth.rb +175 -0
- data/test/test_openid_yadis.rb +178 -0
- data/test/test_pape.rb +247 -0
- data/test/test_parsehtml.rb +80 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2457 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +298 -0
- data/test/test_trustroot.rb +113 -0
- data/test/test_ui.rb +93 -0
- data/test/test_urinorm.rb +35 -0
- data/test/test_util.rb +145 -0
- data/test/test_xrds.rb +169 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +63 -0
- data/test/test_yadis_discovery.rb +220 -0
- data/test/testutil.rb +127 -0
- data/test/util.rb +53 -0
- metadata +336 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
module OpenID
|
2
|
+
class Consumer
|
3
|
+
|
4
|
+
# A set of discovered services, for tracking which providers have
|
5
|
+
# been attempted for an OpenID identifier
|
6
|
+
class DiscoveredServices
|
7
|
+
attr_reader :current
|
8
|
+
|
9
|
+
def initialize(starting_url, yadis_url, services)
|
10
|
+
@starting_url = starting_url
|
11
|
+
@yadis_url = yadis_url
|
12
|
+
@services = services.dup
|
13
|
+
@current = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def next
|
17
|
+
@current = @services.shift
|
18
|
+
end
|
19
|
+
|
20
|
+
def for_url?(url)
|
21
|
+
[@starting_url, @yadis_url].member?(url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def started?
|
25
|
+
!@current.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
@services.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Manages calling discovery and tracking which endpoints have
|
34
|
+
# already been attempted.
|
35
|
+
class DiscoveryManager
|
36
|
+
def initialize(session, url, session_key_suffix=nil)
|
37
|
+
@url = url
|
38
|
+
|
39
|
+
@session = session
|
40
|
+
@session_key_suffix = session_key_suffix || 'auth'
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_next_service
|
44
|
+
manager = get_manager
|
45
|
+
if !manager.nil? && manager.empty?
|
46
|
+
destroy_manager
|
47
|
+
manager = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
if manager.nil?
|
51
|
+
yadis_url, services = yield @url
|
52
|
+
manager = create_manager(yadis_url, services)
|
53
|
+
end
|
54
|
+
|
55
|
+
if !manager.nil?
|
56
|
+
service = manager.next
|
57
|
+
store(manager)
|
58
|
+
else
|
59
|
+
service = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
return service
|
63
|
+
end
|
64
|
+
|
65
|
+
def cleanup(force=false)
|
66
|
+
manager = get_manager(force)
|
67
|
+
if !manager.nil?
|
68
|
+
service = manager.current
|
69
|
+
destroy_manager(force)
|
70
|
+
else
|
71
|
+
service = nil
|
72
|
+
end
|
73
|
+
return service
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def get_manager(force=false)
|
79
|
+
manager = load
|
80
|
+
if force || manager.nil? || manager.for_url?(@url)
|
81
|
+
return manager
|
82
|
+
else
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_manager(yadis_url, services)
|
88
|
+
manager = get_manager
|
89
|
+
if !manager.nil?
|
90
|
+
raise StandardError, "There is already a manager for #{yadis_url}"
|
91
|
+
end
|
92
|
+
if services.empty?
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
manager = DiscoveredServices.new(@url, yadis_url, services)
|
96
|
+
store(manager)
|
97
|
+
return manager
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy_manager(force=false)
|
101
|
+
if !get_manager(force).nil?
|
102
|
+
destroy!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def session_key
|
107
|
+
'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix
|
108
|
+
end
|
109
|
+
|
110
|
+
def store(manager)
|
111
|
+
@session[session_key] = manager
|
112
|
+
end
|
113
|
+
|
114
|
+
def load
|
115
|
+
@session[session_key]
|
116
|
+
end
|
117
|
+
|
118
|
+
def destroy!
|
119
|
+
@session[session_key] = nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "openid/yadis/htmltokenizer"
|
2
|
+
|
3
|
+
module OpenID
|
4
|
+
|
5
|
+
# Stuff to remove before we start looking for tags
|
6
|
+
REMOVED_RE = /
|
7
|
+
# Comments
|
8
|
+
<!--.*?-->
|
9
|
+
|
10
|
+
# CDATA blocks
|
11
|
+
| <!\[CDATA\[.*?\]\]>
|
12
|
+
|
13
|
+
# script blocks
|
14
|
+
| <script\b
|
15
|
+
|
16
|
+
# make sure script is not an XML namespace
|
17
|
+
(?!:)
|
18
|
+
|
19
|
+
[^>]*>.*?<\/script>
|
20
|
+
|
21
|
+
/mix
|
22
|
+
|
23
|
+
def OpenID.openid_unescape(s)
|
24
|
+
s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"')
|
25
|
+
end
|
26
|
+
|
27
|
+
def OpenID.unescape_hash(h)
|
28
|
+
newh = {}
|
29
|
+
h.map{|k,v|
|
30
|
+
newh[k]=openid_unescape(v)
|
31
|
+
}
|
32
|
+
newh
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def OpenID.parse_link_attrs(html)
|
37
|
+
stripped = html.gsub(REMOVED_RE,'')
|
38
|
+
parser = HTMLTokenizer.new(stripped)
|
39
|
+
|
40
|
+
links = []
|
41
|
+
# to keep track of whether or not we are in the head element
|
42
|
+
in_head = false
|
43
|
+
in_html = false
|
44
|
+
saw_head = false
|
45
|
+
|
46
|
+
begin
|
47
|
+
while el = parser.getTag('head', '/head', 'link', 'body', '/body',
|
48
|
+
'html', '/html')
|
49
|
+
|
50
|
+
# we are leaving head or have reached body, so we bail
|
51
|
+
return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)
|
52
|
+
|
53
|
+
# enforce html > head > link
|
54
|
+
if el.tag_name == 'html'
|
55
|
+
in_html = true
|
56
|
+
end
|
57
|
+
next unless in_html
|
58
|
+
if el.tag_name == 'head'
|
59
|
+
if saw_head
|
60
|
+
return links #only allow one head
|
61
|
+
end
|
62
|
+
saw_head = true
|
63
|
+
unless el.to_s[-2] == 47 # tag ends with a /: a short tag
|
64
|
+
in_head = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
next unless in_head
|
68
|
+
|
69
|
+
return links if el.tag_name == 'html'
|
70
|
+
|
71
|
+
if el.tag_name == 'link'
|
72
|
+
links << unescape_hash(el.attr_hash)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
rescue Exception # just stop parsing if there's an error
|
77
|
+
end
|
78
|
+
return links
|
79
|
+
end
|
80
|
+
|
81
|
+
def OpenID.rel_matches(rel_attr, target_rel)
|
82
|
+
# Does this target_rel appear in the rel_str?
|
83
|
+
# XXX: TESTME
|
84
|
+
rels = rel_attr.strip().split()
|
85
|
+
rels.each { |rel|
|
86
|
+
rel = rel.downcase
|
87
|
+
if rel == target_rel
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
}
|
91
|
+
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
def OpenID.link_has_rel(link_attrs, target_rel)
|
96
|
+
# Does this link have target_rel as a relationship?
|
97
|
+
|
98
|
+
# XXX: TESTME
|
99
|
+
rel_attr = link_attrs['rel']
|
100
|
+
return (rel_attr and rel_matches(rel_attr, target_rel))
|
101
|
+
end
|
102
|
+
|
103
|
+
def OpenID.find_links_rel(link_attrs_list, target_rel)
|
104
|
+
# Filter the list of link attributes on whether it has target_rel
|
105
|
+
# as a relationship.
|
106
|
+
|
107
|
+
# XXX: TESTME
|
108
|
+
matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
|
109
|
+
result = []
|
110
|
+
|
111
|
+
link_attrs_list.each { |item|
|
112
|
+
if matchesTarget.call(item)
|
113
|
+
result << item
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
return result
|
118
|
+
end
|
119
|
+
|
120
|
+
def OpenID.find_first_href(link_attrs_list, target_rel)
|
121
|
+
# Return the value of the href attribute for the first link tag in
|
122
|
+
# the list that has target_rel as a relationship.
|
123
|
+
|
124
|
+
# XXX: TESTME
|
125
|
+
matches = find_links_rel(link_attrs_list, target_rel)
|
126
|
+
if !matches or matches.empty?
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
first = matches[0]
|
131
|
+
return first['href']
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
@@ -0,0 +1,523 @@
|
|
1
|
+
require "openid/message"
|
2
|
+
require "openid/protocolerror"
|
3
|
+
require "openid/kvpost"
|
4
|
+
require "openid/consumer/discovery"
|
5
|
+
require "openid/urinorm"
|
6
|
+
|
7
|
+
module OpenID
|
8
|
+
class TypeURIMismatch < ProtocolError
|
9
|
+
attr_reader :type_uri, :endpoint
|
10
|
+
|
11
|
+
def initialize(type_uri, endpoint)
|
12
|
+
@type_uri = type_uri
|
13
|
+
@endpoint = endpoint
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Consumer
|
18
|
+
@openid1_return_to_nonce_name = 'rp_nonce'
|
19
|
+
@openid1_return_to_claimed_id_name = 'openid1_claimed_id'
|
20
|
+
|
21
|
+
# Set the name of the query parameter that this library will use
|
22
|
+
# to thread a nonce through an OpenID 1 transaction. It will be
|
23
|
+
# appended to the return_to URL.
|
24
|
+
def self.openid1_return_to_nonce_name=(query_arg_name)
|
25
|
+
@openid1_return_to_nonce_name = query_arg_name
|
26
|
+
end
|
27
|
+
|
28
|
+
# See openid1_return_to_nonce_name= documentation
|
29
|
+
def self.openid1_return_to_nonce_name
|
30
|
+
@openid1_return_to_nonce_name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set the name of the query parameter that this library will use
|
34
|
+
# to thread the requested URL through an OpenID 1 transaction (for
|
35
|
+
# use when verifying discovered information). It will be appended
|
36
|
+
# to the return_to URL.
|
37
|
+
def self.openid1_return_to_claimed_id_name=(query_arg_name)
|
38
|
+
@openid1_return_to_claimed_id_name = query_arg_name
|
39
|
+
end
|
40
|
+
|
41
|
+
# See openid1_return_to_claimed_id_name=
|
42
|
+
def self.openid1_return_to_claimed_id_name
|
43
|
+
@openid1_return_to_claimed_id_name
|
44
|
+
end
|
45
|
+
|
46
|
+
# Handles an openid.mode=id_res response. This object is
|
47
|
+
# instantiated and used by the Consumer.
|
48
|
+
class IdResHandler
|
49
|
+
attr_reader :endpoint, :message
|
50
|
+
|
51
|
+
def initialize(message, current_url, store=nil, endpoint=nil)
|
52
|
+
@store = store # Fer the nonce and invalidate_handle
|
53
|
+
@message = message
|
54
|
+
@endpoint = endpoint
|
55
|
+
@current_url = current_url
|
56
|
+
@signed_list = nil
|
57
|
+
|
58
|
+
# Start the verification process
|
59
|
+
id_res
|
60
|
+
end
|
61
|
+
|
62
|
+
def signed_fields
|
63
|
+
signed_list.map {|x| 'openid.' + x}
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
# This method will raise ProtocolError unless the request is a
|
69
|
+
# valid id_res response. Once it has been verified, the methods
|
70
|
+
# 'endpoint', 'message', and 'signed_fields' contain the
|
71
|
+
# verified information.
|
72
|
+
def id_res
|
73
|
+
check_for_fields
|
74
|
+
verify_return_to
|
75
|
+
verify_discovery_results
|
76
|
+
check_signature
|
77
|
+
check_nonce
|
78
|
+
end
|
79
|
+
|
80
|
+
def server_url
|
81
|
+
@endpoint.nil? ? nil : @endpoint.server_url
|
82
|
+
end
|
83
|
+
|
84
|
+
def openid_namespace
|
85
|
+
@message.get_openid_namespace
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch(field, default=NO_DEFAULT)
|
89
|
+
@message.get_arg(OPENID_NS, field, default)
|
90
|
+
end
|
91
|
+
|
92
|
+
def signed_list
|
93
|
+
if @signed_list.nil?
|
94
|
+
signed_list_str = fetch('signed', nil)
|
95
|
+
if signed_list_str.nil?
|
96
|
+
raise ProtocolError, 'Response missing signed list'
|
97
|
+
end
|
98
|
+
|
99
|
+
@signed_list = signed_list_str.split(',', -1)
|
100
|
+
end
|
101
|
+
@signed_list
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_for_fields
|
105
|
+
# XXX: if a field is missing, we should not have to explicitly
|
106
|
+
# check that it's present, just make sure that the fields are
|
107
|
+
# actually being used by the rest of the code in
|
108
|
+
# tests. Although, which fields are signed does need to be
|
109
|
+
# checked somewhere.
|
110
|
+
basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
|
111
|
+
basic_sig_fields = ['return_to', 'identity']
|
112
|
+
|
113
|
+
case openid_namespace
|
114
|
+
when OPENID2_NS
|
115
|
+
require_fields = basic_fields + ['op_endpoint']
|
116
|
+
require_sigs = basic_sig_fields +
|
117
|
+
['response_nonce', 'claimed_id', 'assoc_handle', 'op_endpoint']
|
118
|
+
when OPENID1_NS, OPENID11_NS
|
119
|
+
require_fields = basic_fields + ['identity']
|
120
|
+
require_sigs = basic_sig_fields
|
121
|
+
else
|
122
|
+
raise RuntimeError, "check_for_fields doesn't know about "\
|
123
|
+
"namespace #{openid_namespace.inspect}"
|
124
|
+
end
|
125
|
+
|
126
|
+
require_fields.each do |field|
|
127
|
+
if !@message.has_key?(OPENID_NS, field)
|
128
|
+
raise ProtocolError, "Missing required field #{field}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
require_sigs.each do |field|
|
133
|
+
# Field is present and not in signed list
|
134
|
+
if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
|
135
|
+
raise ProtocolError, "#{field.inspect} not signed"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def verify_return_to
|
141
|
+
begin
|
142
|
+
msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
|
143
|
+
rescue URI::InvalidURIError
|
144
|
+
raise ProtocolError, ("return_to is not a valid URI")
|
145
|
+
end
|
146
|
+
|
147
|
+
verify_return_to_args(msg_return_to)
|
148
|
+
if !@current_url.nil?
|
149
|
+
verify_return_to_base(msg_return_to)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def verify_return_to_args(msg_return_to)
|
154
|
+
return_to_parsed_query = {}
|
155
|
+
if !msg_return_to.query.nil?
|
156
|
+
CGI.parse(msg_return_to.query).each_pair do |k, vs|
|
157
|
+
return_to_parsed_query[k] = vs[0]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
query = @message.to_post_args
|
161
|
+
return_to_parsed_query.each_pair do |rt_key, rt_val|
|
162
|
+
msg_val = query[rt_key]
|
163
|
+
if msg_val.nil?
|
164
|
+
raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
|
165
|
+
elsif msg_val != rt_val
|
166
|
+
raise ProtocolError, ("Parameter '#{rt_key}' value "\
|
167
|
+
"#{msg_val.inspect} does not match "\
|
168
|
+
"return_to's value #{rt_val.inspect}")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
@message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
|
172
|
+
rt_val = return_to_parsed_query[bare_key]
|
173
|
+
if not return_to_parsed_query.has_key? bare_key
|
174
|
+
# This may be caused by your web framework throwing extra
|
175
|
+
# entries in to your parameters hash that were not GET or
|
176
|
+
# POST parameters. For example, Rails has been known to
|
177
|
+
# add "controller" and "action" keys; another server adds
|
178
|
+
# at least a "format" key.
|
179
|
+
raise ProtocolError, ("Unexpected parameter (not on return_to): "\
|
180
|
+
"'#{bare_key}'=#{rt_val.inspect})")
|
181
|
+
end
|
182
|
+
if rt_val != bare_val
|
183
|
+
raise ProtocolError, ("Parameter '#{bare_key}' value "\
|
184
|
+
"#{bare_val.inspect} does not match "\
|
185
|
+
"return_to's value #{rt_val.inspect}")
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def verify_return_to_base(msg_return_to)
|
191
|
+
begin
|
192
|
+
app_parsed = URI.parse(URINorm::urinorm(@current_url))
|
193
|
+
rescue URI::InvalidURIError
|
194
|
+
raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
|
195
|
+
end
|
196
|
+
|
197
|
+
[:scheme, :host, :port, :path].each do |meth|
|
198
|
+
if msg_return_to.send(meth) != app_parsed.send(meth)
|
199
|
+
raise ProtocolError, "return_to #{meth.to_s} does not match"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Raises ProtocolError if the signature is bad
|
205
|
+
def check_signature
|
206
|
+
if @store.nil?
|
207
|
+
assoc = nil
|
208
|
+
else
|
209
|
+
assoc = @store.get_association(server_url, fetch('assoc_handle'))
|
210
|
+
end
|
211
|
+
|
212
|
+
if assoc.nil?
|
213
|
+
check_auth
|
214
|
+
else
|
215
|
+
if assoc.expires_in <= 0
|
216
|
+
# XXX: It might be a good idea sometimes to re-start the
|
217
|
+
# authentication with a new association. Doing it
|
218
|
+
# automatically opens the possibility for
|
219
|
+
# denial-of-service by a server that just returns expired
|
220
|
+
# associations (or really short-lived associations)
|
221
|
+
raise ProtocolError, "Association with #{server_url} expired"
|
222
|
+
elsif !assoc.check_message_signature(@message)
|
223
|
+
raise ProtocolError, "Bad signature in response from #{server_url}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def check_auth
|
229
|
+
Util.log("Using 'check_authentication' with #{server_url}")
|
230
|
+
begin
|
231
|
+
request = create_check_auth_request
|
232
|
+
rescue Message::KeyNotFound => why
|
233
|
+
raise ProtocolError, "Could not generate 'check_authentication' "\
|
234
|
+
"request: #{why.message}"
|
235
|
+
end
|
236
|
+
|
237
|
+
response = OpenID.make_kv_post(request, server_url)
|
238
|
+
|
239
|
+
process_check_auth_response(response)
|
240
|
+
end
|
241
|
+
|
242
|
+
def create_check_auth_request
|
243
|
+
signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')
|
244
|
+
|
245
|
+
# check that we got all the signed arguments
|
246
|
+
signed_list.each {|k|
|
247
|
+
@message.get_aliased_arg(k, NO_DEFAULT)
|
248
|
+
}
|
249
|
+
|
250
|
+
ca_message = @message.copy
|
251
|
+
ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')
|
252
|
+
|
253
|
+
return ca_message
|
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, OPENID11_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, OPENID11_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.claimed_id, [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.claimed_id, [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 ProtocolError, ("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
|
+
discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
|
412
|
+
end
|
413
|
+
|
414
|
+
# Given an endpoint object created from the information in an
|
415
|
+
# OpenID response, perform discovery and verify the discovery
|
416
|
+
# results, returning the matching endpoint that is the result of
|
417
|
+
# doing that discovery.
|
418
|
+
def discover_and_verify(claimed_id, to_match_endpoints)
|
419
|
+
Util.log("Performing discovery on #{claimed_id}")
|
420
|
+
_, services = OpenID.discover(claimed_id)
|
421
|
+
if services.length == 0
|
422
|
+
# XXX: this might want to be something other than
|
423
|
+
# ProtocolError. In Python, it's DiscoveryFailure
|
424
|
+
raise ProtocolError, ("No OpenID information found at "\
|
425
|
+
"#{claimed_id}")
|
426
|
+
end
|
427
|
+
verify_discovered_services(claimed_id, services, to_match_endpoints)
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
def verify_discovered_services(claimed_id, services, to_match_endpoints)
|
432
|
+
# Search the services resulting from discovery to find one
|
433
|
+
# that matches the information from the assertion
|
434
|
+
failure_messages = []
|
435
|
+
for endpoint in services
|
436
|
+
for to_match_endpoint in to_match_endpoints
|
437
|
+
begin
|
438
|
+
verify_discovery_single(endpoint, to_match_endpoint)
|
439
|
+
rescue ProtocolError => why
|
440
|
+
failure_messages << why.message
|
441
|
+
else
|
442
|
+
# It matches, so discover verification has
|
443
|
+
# succeeded. Return this endpoint.
|
444
|
+
@endpoint = endpoint
|
445
|
+
return
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
Util.log("Discovery verification failure for #{claimed_id}")
|
451
|
+
failure_messages.each do |failure_message|
|
452
|
+
Util.log(" * Endpoint mismatch: " + failure_message)
|
453
|
+
end
|
454
|
+
|
455
|
+
# XXX: is DiscoveryFailure in Python OpenID
|
456
|
+
raise ProtocolError, ("No matching endpoint found after "\
|
457
|
+
"discovering #{claimed_id}")
|
458
|
+
end
|
459
|
+
|
460
|
+
def verify_discovery_single(endpoint, to_match)
|
461
|
+
# Every type URI that's in the to_match endpoint has to be
|
462
|
+
# present in the discovered endpoint.
|
463
|
+
for type_uri in to_match.type_uris
|
464
|
+
if !endpoint.uses_extension(type_uri)
|
465
|
+
raise TypeURIMismatch.new(type_uri, endpoint)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Fragments do not influence discovery, so we can't compare a
|
470
|
+
# claimed identifier with a fragment to discovered information.
|
471
|
+
defragged_claimed_id =
|
472
|
+
case Yadis::XRI.identifier_scheme(to_match.claimed_id)
|
473
|
+
when :xri
|
474
|
+
to_match.claimed_id
|
475
|
+
when :uri
|
476
|
+
begin
|
477
|
+
parsed = URI.parse(to_match.claimed_id)
|
478
|
+
rescue URI::InvalidURIError
|
479
|
+
to_match.claimed_id
|
480
|
+
else
|
481
|
+
parsed.fragment = nil
|
482
|
+
parsed.to_s
|
483
|
+
end
|
484
|
+
else
|
485
|
+
raise StandardError, 'Not reached'
|
486
|
+
end
|
487
|
+
|
488
|
+
if defragged_claimed_id != endpoint.claimed_id
|
489
|
+
raise ProtocolError, ("Claimed ID does not match (different "\
|
490
|
+
"subjects!), Expected "\
|
491
|
+
"#{defragged_claimed_id}, got "\
|
492
|
+
"#{endpoint.claimed_id}")
|
493
|
+
end
|
494
|
+
|
495
|
+
if to_match.get_local_id != endpoint.get_local_id
|
496
|
+
raise ProtocolError, ("local_id mismatch. Expected "\
|
497
|
+
"#{to_match.get_local_id}, got "\
|
498
|
+
"#{endpoint.get_local_id}")
|
499
|
+
end
|
500
|
+
|
501
|
+
# If the server URL is nil, this must be an OpenID 1
|
502
|
+
# response, because op_endpoint is a required parameter in
|
503
|
+
# OpenID 2. In that case, we don't actually care what the
|
504
|
+
# discovered server_url is, because signature checking or
|
505
|
+
# check_auth should take care of that check for us.
|
506
|
+
if to_match.server_url.nil?
|
507
|
+
if to_match.preferred_namespace != OPENID1_NS
|
508
|
+
raise StandardError,
|
509
|
+
"The code calling this must ensure that OpenID 2 "\
|
510
|
+
"responses have a non-none `openid.op_endpoint' and "\
|
511
|
+
"that it is set as the `server_url' attribute of the "\
|
512
|
+
"`to_match' endpoint."
|
513
|
+
end
|
514
|
+
elsif to_match.server_url != endpoint.server_url
|
515
|
+
raise ProtocolError, ("OP Endpoint mismatch. Expected"\
|
516
|
+
"#{to_match.server_url}, got "\
|
517
|
+
"#{endpoint.server_url}")
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|