pelle-ruby-openid 2.1.8
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/CHANGES-2.1.0 +36 -0
- data/INSTALL +47 -0
- data/LICENSE +210 -0
- data/NOTICE +2 -0
- data/README +82 -0
- data/UPGRADE +127 -0
- data/VERSION +1 -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/association.rb +249 -0
- data/lib/openid/consumer/associationmanager.rb +344 -0
- data/lib/openid/consumer/checkid_request.rb +186 -0
- data/lib/openid/consumer/discovery.rb +498 -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/consumer.rb +395 -0
- data/lib/openid/cryptutil.rb +97 -0
- data/lib/openid/dh.rb +89 -0
- data/lib/openid/extension.rb +39 -0
- data/lib/openid/extensions/ax.rb +516 -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 +238 -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 +106 -0
- data/lib/openid.rb +20 -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/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 +648 -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 +838 -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 +538 -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_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 +316 -0
|
@@ -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
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
require "openid/extras"
|
|
4
|
+
|
|
5
|
+
module OpenID
|
|
6
|
+
|
|
7
|
+
module URINorm
|
|
8
|
+
public
|
|
9
|
+
def URINorm.urinorm(uri)
|
|
10
|
+
uri = URI.parse(uri)
|
|
11
|
+
|
|
12
|
+
raise URI::InvalidURIError.new('no scheme') unless uri.scheme
|
|
13
|
+
uri.scheme = uri.scheme.downcase
|
|
14
|
+
unless ['http','https'].member?(uri.scheme)
|
|
15
|
+
raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
raise URI::InvalidURIError.new('no host') unless uri.host
|
|
19
|
+
uri.host = uri.host.downcase
|
|
20
|
+
|
|
21
|
+
uri.path = remove_dot_segments(uri.path)
|
|
22
|
+
uri.path = '/' if uri.path.length == 0
|
|
23
|
+
|
|
24
|
+
uri = uri.normalize.to_s
|
|
25
|
+
uri = uri.gsub(PERCENT_ESCAPE_RE) {
|
|
26
|
+
sub = $&[1..2].to_i(16).chr
|
|
27
|
+
reserved(sub) ? $&.upcase : sub
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return uri
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
RESERVED_RE = /[A-Za-z0-9._~-]/
|
|
35
|
+
PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/
|
|
36
|
+
|
|
37
|
+
def URINorm.reserved(chr)
|
|
38
|
+
not RESERVED_RE =~ chr
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def URINorm.remove_dot_segments(path)
|
|
42
|
+
result_segments = []
|
|
43
|
+
|
|
44
|
+
while path.length > 0
|
|
45
|
+
if path.starts_with?('../')
|
|
46
|
+
path = path[3..-1]
|
|
47
|
+
elsif path.starts_with?('./')
|
|
48
|
+
path = path[2..-1]
|
|
49
|
+
elsif path.starts_with?('/./')
|
|
50
|
+
path = path[2..-1]
|
|
51
|
+
elsif path == '/.'
|
|
52
|
+
path = '/'
|
|
53
|
+
elsif path.starts_with?('/../')
|
|
54
|
+
path = path[3..-1]
|
|
55
|
+
result_segments.pop if result_segments.length > 0
|
|
56
|
+
elsif path == '/..'
|
|
57
|
+
path = '/'
|
|
58
|
+
result_segments.pop if result_segments.length > 0
|
|
59
|
+
elsif path == '..' or path == '.'
|
|
60
|
+
path = ''
|
|
61
|
+
else
|
|
62
|
+
i = 0
|
|
63
|
+
i = 1 if path[0].chr == '/'
|
|
64
|
+
i = path.index('/', i)
|
|
65
|
+
i = path.length if i.nil?
|
|
66
|
+
result_segments << path[0...i]
|
|
67
|
+
path = path[i..-1]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
return result_segments.join('')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
data/lib/openid/util.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "uri"
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
require "openid/extras"
|
|
6
|
+
|
|
7
|
+
# See OpenID::Consumer or OpenID::Server modules, as well as the store classes
|
|
8
|
+
module OpenID
|
|
9
|
+
class AssertionError < Exception
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Exceptions that are raised by the library are subclasses of this
|
|
13
|
+
# exception type, so if you want to catch all exceptions raised by
|
|
14
|
+
# the library, you can catch OpenIDError
|
|
15
|
+
class OpenIDError < StandardError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Util
|
|
19
|
+
|
|
20
|
+
BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
|
|
21
|
+
'abcdefghijklmnopqrstuvwxyz0123456789+/')
|
|
22
|
+
BASE64_RE = Regexp.compile("
|
|
23
|
+
\\A
|
|
24
|
+
([#{BASE64_CHARS}]{4})*
|
|
25
|
+
([#{BASE64_CHARS}]{2}==|
|
|
26
|
+
[#{BASE64_CHARS}]{3}=)?
|
|
27
|
+
\\Z", Regexp::EXTENDED)
|
|
28
|
+
|
|
29
|
+
def Util.assert(value, message=nil)
|
|
30
|
+
if not value
|
|
31
|
+
raise AssertionError, message or value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def Util.to_base64(s)
|
|
36
|
+
[s].pack('m').gsub("\n", "")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def Util.from_base64(s)
|
|
40
|
+
without_newlines = s.gsub(/[\r\n]+/, '')
|
|
41
|
+
if !BASE64_RE.match(without_newlines)
|
|
42
|
+
raise ArgumentError, "Malformed input: #{s.inspect}"
|
|
43
|
+
end
|
|
44
|
+
without_newlines.unpack('m').first
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def Util.urlencode(args)
|
|
48
|
+
a = []
|
|
49
|
+
args.each do |key, val|
|
|
50
|
+
val = '' unless val
|
|
51
|
+
a << (CGI::escape(key) + "=" + CGI::escape(val))
|
|
52
|
+
end
|
|
53
|
+
a.join("&")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def Util.parse_query(qs)
|
|
57
|
+
query = {}
|
|
58
|
+
CGI::parse(qs).each {|k,v| query[k] = v[0]}
|
|
59
|
+
return query
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def Util.append_args(url, args)
|
|
63
|
+
url = url.dup
|
|
64
|
+
return url if args.length == 0
|
|
65
|
+
|
|
66
|
+
if args.respond_to?('each_pair')
|
|
67
|
+
args = args.sort
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
url << (url.include?("?") ? "&" : "?")
|
|
71
|
+
url << Util.urlencode(args)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@@logger = Logger.new(STDERR)
|
|
75
|
+
@@logger.progname = "OpenID"
|
|
76
|
+
|
|
77
|
+
def Util.logger=(logger)
|
|
78
|
+
@@logger = logger
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def Util.logger
|
|
82
|
+
@@logger
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# change the message below to do whatever you like for logging
|
|
86
|
+
def Util.log(message)
|
|
87
|
+
logger.info(message)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def Util.auto_submit_html(form, title='OpenID transaction in progress')
|
|
91
|
+
return "
|
|
92
|
+
<html>
|
|
93
|
+
<head>
|
|
94
|
+
<title>#{title}</title>
|
|
95
|
+
</head>
|
|
96
|
+
<body onload='document.forms[0].submit();'>
|
|
97
|
+
#{form}
|
|
98
|
+
<script>
|
|
99
|
+
var elements = document.forms[0].elements;
|
|
100
|
+
for (var i = 0; i < elements.length; i++) {
|
|
101
|
+
elements[i].style.display = \"none\";
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
106
|
+
"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
module OpenID
|
|
2
|
+
|
|
3
|
+
module Yadis
|
|
4
|
+
|
|
5
|
+
# Generate an accept header value
|
|
6
|
+
#
|
|
7
|
+
# [str or (str, float)] -> str
|
|
8
|
+
def self.generate_accept_header(*elements)
|
|
9
|
+
parts = []
|
|
10
|
+
elements.each { |element|
|
|
11
|
+
if element.is_a?(String)
|
|
12
|
+
qs = "1.0"
|
|
13
|
+
mtype = element
|
|
14
|
+
else
|
|
15
|
+
mtype, q = element
|
|
16
|
+
q = q.to_f
|
|
17
|
+
if q > 1 or q <= 0
|
|
18
|
+
raise ArgumentError.new("Invalid preference factor: #{q}")
|
|
19
|
+
end
|
|
20
|
+
qs = sprintf("%0.1f", q)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
parts << [qs, mtype]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
parts.sort!
|
|
27
|
+
chunks = []
|
|
28
|
+
parts.each { |q, mtype|
|
|
29
|
+
if q == '1.0'
|
|
30
|
+
chunks << mtype
|
|
31
|
+
else
|
|
32
|
+
chunks << sprintf("%s; q=%s", mtype, q)
|
|
33
|
+
end
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return chunks.join(', ')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.parse_accept_header(value)
|
|
40
|
+
# Parse an accept header, ignoring any accept-extensions
|
|
41
|
+
#
|
|
42
|
+
# returns a list of tuples containing main MIME type, MIME
|
|
43
|
+
# subtype, and quality markdown.
|
|
44
|
+
#
|
|
45
|
+
# str -> [(str, str, float)]
|
|
46
|
+
chunks = value.split(',', -1).collect { |v| v.strip }
|
|
47
|
+
accept = []
|
|
48
|
+
chunks.each { |chunk|
|
|
49
|
+
parts = chunk.split(";", -1).collect { |s| s.strip }
|
|
50
|
+
|
|
51
|
+
mtype = parts.shift
|
|
52
|
+
if mtype.index('/').nil?
|
|
53
|
+
# This is not a MIME type, so ignore the bad data
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
main, sub = mtype.split('/', 2)
|
|
58
|
+
|
|
59
|
+
q = nil
|
|
60
|
+
parts.each { |ext|
|
|
61
|
+
if !ext.index('=').nil?
|
|
62
|
+
k, v = ext.split('=', 2)
|
|
63
|
+
if k == 'q'
|
|
64
|
+
q = v.to_f
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
q = 1.0 if q.nil?
|
|
70
|
+
|
|
71
|
+
accept << [q, main, sub]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
accept.sort!
|
|
75
|
+
accept.reverse!
|
|
76
|
+
|
|
77
|
+
return accept.collect { |q, main, sub| [main, sub, q] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.match_types(accept_types, have_types)
|
|
81
|
+
# Given the result of parsing an Accept: header, and the
|
|
82
|
+
# available MIME types, return the acceptable types with their
|
|
83
|
+
# quality markdowns.
|
|
84
|
+
#
|
|
85
|
+
# For example:
|
|
86
|
+
#
|
|
87
|
+
# >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5')
|
|
88
|
+
# >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
|
|
89
|
+
# [('text/html', 1.0), ('text/plain', 0.5)]
|
|
90
|
+
#
|
|
91
|
+
# Type signature: ([(str, str, float)], [str]) -> [(str, float)]
|
|
92
|
+
if accept_types.nil? or accept_types == []
|
|
93
|
+
# Accept all of them
|
|
94
|
+
default = 1
|
|
95
|
+
else
|
|
96
|
+
default = 0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
match_main = {}
|
|
100
|
+
match_sub = {}
|
|
101
|
+
accept_types.each { |main, sub, q|
|
|
102
|
+
if main == '*'
|
|
103
|
+
default = [default, q].max
|
|
104
|
+
next
|
|
105
|
+
elsif sub == '*'
|
|
106
|
+
match_main[main] = [match_main.fetch(main, 0), q].max
|
|
107
|
+
else
|
|
108
|
+
match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max
|
|
109
|
+
end
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
accepted_list = []
|
|
113
|
+
order_maintainer = 0
|
|
114
|
+
have_types.each { |mtype|
|
|
115
|
+
main, sub = mtype.split('/', 2)
|
|
116
|
+
if match_sub.member?([main, sub])
|
|
117
|
+
q = match_sub[[main, sub]]
|
|
118
|
+
else
|
|
119
|
+
q = match_main.fetch(main, default)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if q != 0
|
|
123
|
+
accepted_list << [1 - q, order_maintainer, q, mtype]
|
|
124
|
+
order_maintainer += 1
|
|
125
|
+
end
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
accepted_list.sort!
|
|
129
|
+
return accepted_list.collect { |_, _, q, mtype| [mtype, q] }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.get_acceptable(accept_header, have_types)
|
|
133
|
+
# Parse the accept header and return a list of available types
|
|
134
|
+
# in preferred order. If a type is unacceptable, it will not be
|
|
135
|
+
# in the resulting list.
|
|
136
|
+
#
|
|
137
|
+
# This is a convenience wrapper around matchTypes and
|
|
138
|
+
# parse_accept_header
|
|
139
|
+
#
|
|
140
|
+
# (str, [str]) -> [str]
|
|
141
|
+
accepted = self.parse_accept_header(accept_header)
|
|
142
|
+
preferred = self.match_types(accepted, have_types)
|
|
143
|
+
return preferred.collect { |mtype, _| mtype }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
require 'openid/yadis/accept'
|
|
3
|
+
|
|
4
|
+
module OpenID
|
|
5
|
+
|
|
6
|
+
module Yadis
|
|
7
|
+
|
|
8
|
+
YADIS_HEADER_NAME = 'X-XRDS-Location'
|
|
9
|
+
YADIS_CONTENT_TYPE = 'application/xrds+xml'
|
|
10
|
+
|
|
11
|
+
# A value suitable for using as an accept header when performing
|
|
12
|
+
# YADIS discovery, unless the application has special requirements
|
|
13
|
+
YADIS_ACCEPT_HEADER = generate_accept_header(
|
|
14
|
+
['text/html', 0.3],
|
|
15
|
+
['application/xhtml+xml', 0.5],
|
|
16
|
+
[YADIS_CONTENT_TYPE, 1.0]
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|