entp-ruby-openid 2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +215 -0
- data/INSTALL +47 -0
- data/LICENSE +210 -0
- data/NOTICE +2 -0
- data/README +85 -0
- data/UPGRADE +127 -0
- data/admin/runtests.rb +45 -0
- data/examples/README +32 -0
- data/examples/active_record_openid_store/README +58 -0
- data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
- data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
- data/examples/active_record_openid_store/init.rb +8 -0
- data/examples/active_record_openid_store/lib/association.rb +10 -0
- data/examples/active_record_openid_store/lib/nonce.rb +3 -0
- data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
- data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
- data/examples/active_record_openid_store/test/store_test.rb +212 -0
- data/examples/discover +49 -0
- data/examples/rails_openid/README +153 -0
- data/examples/rails_openid/Rakefile +10 -0
- data/examples/rails_openid/app/controllers/application.rb +4 -0
- data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
- data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
- data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
- data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
- data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
- data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
- data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
- data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
- data/examples/rails_openid/app/views/login/index.rhtml +56 -0
- data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
- data/examples/rails_openid/config/boot.rb +19 -0
- data/examples/rails_openid/config/database.yml +74 -0
- data/examples/rails_openid/config/environment.rb +54 -0
- data/examples/rails_openid/config/environments/development.rb +19 -0
- data/examples/rails_openid/config/environments/production.rb +19 -0
- data/examples/rails_openid/config/environments/test.rb +19 -0
- data/examples/rails_openid/config/routes.rb +24 -0
- data/examples/rails_openid/doc/README_FOR_APP +2 -0
- data/examples/rails_openid/public/404.html +8 -0
- data/examples/rails_openid/public/500.html +8 -0
- data/examples/rails_openid/public/dispatch.cgi +12 -0
- data/examples/rails_openid/public/dispatch.fcgi +26 -0
- data/examples/rails_openid/public/dispatch.rb +12 -0
- data/examples/rails_openid/public/favicon.ico +0 -0
- data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
- data/examples/rails_openid/public/javascripts/controls.js +750 -0
- data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
- data/examples/rails_openid/public/javascripts/effects.js +854 -0
- data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
- data/examples/rails_openid/public/robots.txt +1 -0
- data/examples/rails_openid/script/about +3 -0
- data/examples/rails_openid/script/breakpointer +3 -0
- data/examples/rails_openid/script/console +3 -0
- data/examples/rails_openid/script/destroy +3 -0
- data/examples/rails_openid/script/generate +3 -0
- data/examples/rails_openid/script/performance/benchmarker +3 -0
- data/examples/rails_openid/script/performance/profiler +3 -0
- data/examples/rails_openid/script/plugin +3 -0
- data/examples/rails_openid/script/process/reaper +3 -0
- data/examples/rails_openid/script/process/spawner +3 -0
- data/examples/rails_openid/script/process/spinner +3 -0
- data/examples/rails_openid/script/runner +3 -0
- data/examples/rails_openid/script/server +3 -0
- data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
- data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
- data/examples/rails_openid/test/test_helper.rb +28 -0
- data/lib/hmac/hmac.rb +112 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid.rb +22 -0
- data/lib/openid/association.rb +249 -0
- data/lib/openid/consumer.rb +395 -0
- data/lib/openid/consumer/associationmanager.rb +344 -0
- data/lib/openid/consumer/checkid_request.rb +186 -0
- data/lib/openid/consumer/discovery.rb +497 -0
- data/lib/openid/consumer/discovery_manager.rb +123 -0
- data/lib/openid/consumer/html_parse.rb +134 -0
- data/lib/openid/consumer/idres.rb +523 -0
- data/lib/openid/consumer/responses.rb +150 -0
- data/lib/openid/cryptutil.rb +115 -0
- data/lib/openid/dh.rb +89 -0
- data/lib/openid/extension.rb +39 -0
- data/lib/openid/extensions/ax.rb +539 -0
- data/lib/openid/extensions/oauth.rb +91 -0
- data/lib/openid/extensions/pape.rb +179 -0
- data/lib/openid/extensions/sreg.rb +277 -0
- data/lib/openid/extras.rb +11 -0
- data/lib/openid/fetchers.rb +258 -0
- data/lib/openid/kvform.rb +136 -0
- data/lib/openid/kvpost.rb +58 -0
- data/lib/openid/message.rb +553 -0
- data/lib/openid/protocolerror.rb +12 -0
- data/lib/openid/server.rb +1544 -0
- data/lib/openid/store.rb +10 -0
- data/lib/openid/store/filesystem.rb +272 -0
- data/lib/openid/store/interface.rb +75 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +84 -0
- data/lib/openid/store/nonce.rb +68 -0
- data/lib/openid/trustroot.rb +349 -0
- data/lib/openid/urinorm.rb +75 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +3 -0
- data/lib/openid/yadis.rb +15 -0
- data/lib/openid/yadis/accept.rb +148 -0
- data/lib/openid/yadis/constants.rb +21 -0
- data/lib/openid/yadis/discovery.rb +153 -0
- data/lib/openid/yadis/filters.rb +205 -0
- data/lib/openid/yadis/htmltokenizer.rb +305 -0
- data/lib/openid/yadis/parsehtml.rb +45 -0
- data/lib/openid/yadis/services.rb +42 -0
- data/lib/openid/yadis/xrds.rb +155 -0
- data/lib/openid/yadis/xri.rb +90 -0
- data/lib/openid/yadis/xrires.rb +91 -0
- data/test/data/test_discover/openid_utf8.html +11 -0
- data/test/support/test_data_mixin.rb +127 -0
- data/test/support/test_util.rb +53 -0
- data/test/support/yadis_data.rb +131 -0
- data/test/support/yadis_data/accept.txt +124 -0
- data/test/support/yadis_data/dh.txt +29 -0
- data/test/support/yadis_data/example-xrds.xml +14 -0
- data/test/support/yadis_data/linkparse.txt +587 -0
- data/test/support/yadis_data/n2b64 +650 -0
- data/test/support/yadis_data/test1-discover.txt +137 -0
- data/test/support/yadis_data/test1-parsehtml.txt +152 -0
- data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
- data/test/support/yadis_data/test_discover/openid.html +11 -0
- data/test/support/yadis_data/test_discover/openid2.html +11 -0
- data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
- data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
- data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
- data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
- data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
- data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
- data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
- data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
- data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
- data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
- data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
- data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
- data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
- data/test/support/yadis_data/test_xrds/README +12 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
- data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
- data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
- data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
- data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
- data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
- data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
- data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
- data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
- data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
- data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
- data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
- data/test/support/yadis_data/trustroot.txt +153 -0
- data/test/support/yadis_data/urinorm.txt +79 -0
- data/test/test_accept.rb +170 -0
- data/test/test_association.rb +268 -0
- data/test/test_associationmanager.rb +918 -0
- data/test/test_ax.rb +690 -0
- data/test/test_checkid_request.rb +293 -0
- data/test/test_consumer.rb +260 -0
- data/test/test_cryptutil.rb +119 -0
- data/test/test_dh.rb +85 -0
- data/test/test_discover.rb +848 -0
- data/test/test_discovery_manager.rb +259 -0
- data/test/test_extension.rb +46 -0
- data/test/test_extras.rb +35 -0
- data/test/test_fetchers.rb +554 -0
- data/test/test_filters.rb +269 -0
- data/test/test_helper.rb +4 -0
- data/test/test_idres.rb +961 -0
- data/test/test_kvform.rb +164 -0
- data/test/test_kvpost.rb +64 -0
- data/test/test_linkparse.rb +100 -0
- data/test/test_message.rb +1115 -0
- data/test/test_nonce.rb +89 -0
- data/test/test_oauth.rb +176 -0
- data/test/test_openid_yadis.rb +177 -0
- data/test/test_pape.rb +248 -0
- data/test/test_parsehtml.rb +79 -0
- data/test/test_responses.rb +63 -0
- data/test/test_server.rb +2455 -0
- data/test/test_sreg.rb +479 -0
- data/test/test_stores.rb +292 -0
- data/test/test_trustroot.rb +111 -0
- data/test/test_urinorm.rb +34 -0
- data/test/test_util.rb +145 -0
- data/test/test_xrds.rb +167 -0
- data/test/test_xri.rb +48 -0
- data/test/test_xrires.rb +67 -0
- data/test/test_yadis_discovery.rb +218 -0
- metadata +268 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require "openid/message"
|
|
2
|
+
require "openid/util"
|
|
3
|
+
|
|
4
|
+
module OpenID
|
|
5
|
+
class Consumer
|
|
6
|
+
# An object that holds the state necessary for generating an
|
|
7
|
+
# OpenID authentication request. This object holds the association
|
|
8
|
+
# with the server and the discovered information with which the
|
|
9
|
+
# request will be made.
|
|
10
|
+
#
|
|
11
|
+
# It is separate from the consumer because you may wish to add
|
|
12
|
+
# things to the request before sending it on its way to the
|
|
13
|
+
# server. It also has serialization options that let you encode
|
|
14
|
+
# the authentication request as a URL or as a form POST.
|
|
15
|
+
class CheckIDRequest
|
|
16
|
+
attr_accessor :return_to_args, :message
|
|
17
|
+
attr_reader :endpoint
|
|
18
|
+
|
|
19
|
+
# Users of this library should not create instances of this
|
|
20
|
+
# class. Instances of this class are created by the library
|
|
21
|
+
# when needed.
|
|
22
|
+
def initialize(assoc, endpoint)
|
|
23
|
+
@assoc = assoc
|
|
24
|
+
@endpoint = endpoint
|
|
25
|
+
@return_to_args = {}
|
|
26
|
+
@message = Message.new(endpoint.preferred_namespace)
|
|
27
|
+
@anonymous = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :anonymous
|
|
31
|
+
|
|
32
|
+
# Set whether this request should be made anonymously. If a
|
|
33
|
+
# request is anonymous, the identifier will not be sent in the
|
|
34
|
+
# request. This is only useful if you are making another kind of
|
|
35
|
+
# request with an extension in this request.
|
|
36
|
+
#
|
|
37
|
+
# Anonymous requests are not allowed when the request is made
|
|
38
|
+
# with OpenID 1.
|
|
39
|
+
def anonymous=(is_anonymous)
|
|
40
|
+
if is_anonymous && @message.is_openid1
|
|
41
|
+
raise ArgumentError, ("OpenID1 requests MUST include the "\
|
|
42
|
+
"identifier in the request")
|
|
43
|
+
end
|
|
44
|
+
@anonymous = is_anonymous
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Add an object that implements the extension interface for
|
|
48
|
+
# adding arguments to an OpenID message to this checkid request.
|
|
49
|
+
#
|
|
50
|
+
# extension_request: an OpenID::Extension object.
|
|
51
|
+
def add_extension(extension_request)
|
|
52
|
+
extension_request.to_message(@message)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add an extension argument to this OpenID authentication
|
|
56
|
+
# request. You probably want to use add_extension and the
|
|
57
|
+
# OpenID::Extension interface.
|
|
58
|
+
#
|
|
59
|
+
# Use caution when adding arguments, because they will be
|
|
60
|
+
# URL-escaped and appended to the redirect URL, which can easily
|
|
61
|
+
# get quite long.
|
|
62
|
+
def add_extension_arg(namespace, key, value)
|
|
63
|
+
@message.set_arg(namespace, key, value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Produce a OpenID::Message representing this request.
|
|
67
|
+
#
|
|
68
|
+
# Not specifying a return_to URL means that the user will not be
|
|
69
|
+
# returned to the site issuing the request upon its completion.
|
|
70
|
+
#
|
|
71
|
+
# If immediate mode is requested, the OpenID provider is to send
|
|
72
|
+
# back a response immediately, useful for behind-the-scenes
|
|
73
|
+
# authentication attempts. Otherwise the OpenID provider may
|
|
74
|
+
# engage the user before providing a response. This is the
|
|
75
|
+
# default case, as the user may need to provide credentials or
|
|
76
|
+
# approve the request before a positive response can be sent.
|
|
77
|
+
def get_message(realm, return_to=nil, immediate=false)
|
|
78
|
+
if !return_to.nil?
|
|
79
|
+
return_to = Util.append_args(return_to, @return_to_args)
|
|
80
|
+
elsif immediate
|
|
81
|
+
raise ArgumentError, ('"return_to" is mandatory when using '\
|
|
82
|
+
'"checkid_immediate"')
|
|
83
|
+
elsif @message.is_openid1
|
|
84
|
+
raise ArgumentError, ('"return_to" is mandatory for OpenID 1 '\
|
|
85
|
+
'requests')
|
|
86
|
+
elsif @return_to_args.empty?
|
|
87
|
+
raise ArgumentError, ('extra "return_to" arguments were specified, '\
|
|
88
|
+
'but no return_to was specified')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
message = @message.copy
|
|
93
|
+
|
|
94
|
+
mode = immediate ? 'checkid_immediate' : 'checkid_setup'
|
|
95
|
+
message.set_arg(OPENID_NS, 'mode', mode)
|
|
96
|
+
|
|
97
|
+
realm_key = message.is_openid1 ? 'trust_root' : 'realm'
|
|
98
|
+
message.set_arg(OPENID_NS, realm_key, realm)
|
|
99
|
+
|
|
100
|
+
if !return_to.nil?
|
|
101
|
+
message.set_arg(OPENID_NS, 'return_to', return_to)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if not @anonymous
|
|
105
|
+
if @endpoint.is_op_identifier
|
|
106
|
+
# This will never happen when we're in OpenID 1
|
|
107
|
+
# compatibility mode, as long as is_op_identifier()
|
|
108
|
+
# returns false whenever preferred_namespace returns
|
|
109
|
+
# OPENID1_NS.
|
|
110
|
+
claimed_id = request_identity = IDENTIFIER_SELECT
|
|
111
|
+
else
|
|
112
|
+
request_identity = @endpoint.get_local_id
|
|
113
|
+
claimed_id = @endpoint.claimed_id
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# This is true for both OpenID 1 and 2
|
|
117
|
+
message.set_arg(OPENID_NS, 'identity', request_identity)
|
|
118
|
+
|
|
119
|
+
if message.is_openid2
|
|
120
|
+
message.set_arg(OPENID2_NS, 'claimed_id', claimed_id)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if @assoc
|
|
125
|
+
message.set_arg(OPENID_NS, 'assoc_handle', @assoc.handle)
|
|
126
|
+
assoc_log_msg = "with assocication #{@assoc.handle}"
|
|
127
|
+
else
|
|
128
|
+
assoc_log_msg = 'using stateless mode.'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
Util.log("Generated #{mode} request to #{@endpoint.server_url} "\
|
|
132
|
+
"#{assoc_log_msg}")
|
|
133
|
+
return message
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns a URL with an encoded OpenID request.
|
|
137
|
+
#
|
|
138
|
+
# The resulting URL is the OpenID provider's endpoint URL with
|
|
139
|
+
# parameters appended as query arguments. You should redirect
|
|
140
|
+
# the user agent to this URL.
|
|
141
|
+
#
|
|
142
|
+
# OpenID 2.0 endpoints also accept POST requests, see
|
|
143
|
+
# 'send_redirect?' and 'form_markup'.
|
|
144
|
+
def redirect_url(realm, return_to=nil, immediate=false)
|
|
145
|
+
message = get_message(realm, return_to, immediate)
|
|
146
|
+
return message.to_url(@endpoint.server_url)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Get html for a form to submit this request to the IDP.
|
|
150
|
+
#
|
|
151
|
+
# form_tag_attrs is a hash of attributes to be added to the form
|
|
152
|
+
# tag. 'accept-charset' and 'enctype' have defaults that can be
|
|
153
|
+
# overridden. If a value is supplied for 'action' or 'method',
|
|
154
|
+
# it will be replaced.
|
|
155
|
+
def form_markup(realm, return_to=nil, immediate=false,
|
|
156
|
+
form_tag_attrs=nil)
|
|
157
|
+
message = get_message(realm, return_to, immediate)
|
|
158
|
+
return message.to_form_markup(@endpoint.server_url, form_tag_attrs)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Get a complete HTML document that autosubmits the request to the IDP
|
|
162
|
+
# with javascript. This method wraps form_markup - see that method's
|
|
163
|
+
# documentation for help with the parameters.
|
|
164
|
+
def html_markup(realm, return_to=nil, immediate=false,
|
|
165
|
+
form_tag_attrs=nil)
|
|
166
|
+
Util.auto_submit_html(form_markup(realm,
|
|
167
|
+
return_to,
|
|
168
|
+
immediate,
|
|
169
|
+
form_tag_attrs))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Should this OpenID authentication request be sent as a HTTP
|
|
173
|
+
# redirect or as a POST (form submission)?
|
|
174
|
+
#
|
|
175
|
+
# This takes the same parameters as redirect_url or form_markup
|
|
176
|
+
def send_redirect?(realm, return_to=nil, immediate=false)
|
|
177
|
+
if @endpoint.compatibility_mode
|
|
178
|
+
return true
|
|
179
|
+
else
|
|
180
|
+
url = redirect_url(realm, return_to, immediate)
|
|
181
|
+
return url.length <= OPENID1_URL_LIMIT
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Functions to discover OpenID endpoints from identifiers.
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'openid/util'
|
|
5
|
+
require 'openid/fetchers'
|
|
6
|
+
require 'openid/urinorm'
|
|
7
|
+
require 'openid/message'
|
|
8
|
+
require 'openid/yadis/discovery'
|
|
9
|
+
require 'openid/yadis/xrds'
|
|
10
|
+
require 'openid/yadis/xri'
|
|
11
|
+
require 'openid/yadis/services'
|
|
12
|
+
require 'openid/yadis/filters'
|
|
13
|
+
require 'openid/consumer/html_parse'
|
|
14
|
+
require 'openid/yadis/xrires'
|
|
15
|
+
|
|
16
|
+
module OpenID
|
|
17
|
+
|
|
18
|
+
OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
|
|
19
|
+
OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'
|
|
20
|
+
OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'
|
|
21
|
+
OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'
|
|
22
|
+
OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'
|
|
23
|
+
|
|
24
|
+
OPENID_1_0_MESSAGE_NS = OPENID1_NS
|
|
25
|
+
OPENID_2_0_MESSAGE_NS = OPENID2_NS
|
|
26
|
+
|
|
27
|
+
# Object representing an OpenID service endpoint.
|
|
28
|
+
class OpenIDServiceEndpoint
|
|
29
|
+
|
|
30
|
+
# OpenID service type URIs, listed in order of preference. The
|
|
31
|
+
# ordering of this list affects yadis and XRI service discovery.
|
|
32
|
+
OPENID_TYPE_URIS = [
|
|
33
|
+
OPENID_IDP_2_0_TYPE,
|
|
34
|
+
|
|
35
|
+
OPENID_2_0_TYPE,
|
|
36
|
+
OPENID_1_1_TYPE,
|
|
37
|
+
OPENID_1_0_TYPE,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# the verified identifier.
|
|
41
|
+
attr_accessor :claimed_id
|
|
42
|
+
|
|
43
|
+
# For XRI, the persistent identifier.
|
|
44
|
+
attr_accessor :canonical_id
|
|
45
|
+
|
|
46
|
+
attr_accessor :server_url, :type_uris, :local_id, :used_yadis
|
|
47
|
+
|
|
48
|
+
def initialize
|
|
49
|
+
@claimed_id = nil
|
|
50
|
+
@server_url = nil
|
|
51
|
+
@type_uris = []
|
|
52
|
+
@local_id = nil
|
|
53
|
+
@canonical_id = nil
|
|
54
|
+
@used_yadis = false # whether this came from an XRDS
|
|
55
|
+
@display_identifier = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def display_identifier
|
|
59
|
+
return @display_identifier if @display_identifier
|
|
60
|
+
|
|
61
|
+
return @claimed_id if @claimed_id.nil?
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
parsed_identifier = URI.parse(@claimed_id)
|
|
65
|
+
rescue URI::InvalidURIError
|
|
66
|
+
raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return @claimed_id if not parsed_identifier.fragment
|
|
70
|
+
|
|
71
|
+
disp = parsed_identifier
|
|
72
|
+
disp.fragment = nil
|
|
73
|
+
|
|
74
|
+
return disp.to_s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def display_identifier=(display_identifier)
|
|
78
|
+
@display_identifier = display_identifier
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def uses_extension(extension_uri)
|
|
82
|
+
return @type_uris.member?(extension_uri)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def preferred_namespace
|
|
86
|
+
if (@type_uris.member?(OPENID_IDP_2_0_TYPE) or
|
|
87
|
+
@type_uris.member?(OPENID_2_0_TYPE))
|
|
88
|
+
return OPENID_2_0_MESSAGE_NS
|
|
89
|
+
else
|
|
90
|
+
return OPENID_1_0_MESSAGE_NS
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def supports_type(type_uri)
|
|
95
|
+
# Does this endpoint support this type?
|
|
96
|
+
#
|
|
97
|
+
# I consider C{/server} endpoints to implicitly support C{/signon}.
|
|
98
|
+
(
|
|
99
|
+
@type_uris.member?(type_uri) or
|
|
100
|
+
(type_uri == OPENID_2_0_TYPE and is_op_identifier())
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def compatibility_mode
|
|
105
|
+
return preferred_namespace() != OPENID_2_0_MESSAGE_NS
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def is_op_identifier
|
|
109
|
+
return @type_uris.member?(OPENID_IDP_2_0_TYPE)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_service(yadis_url, uri, type_uris, service_element)
|
|
113
|
+
# Set the state of this object based on the contents of the
|
|
114
|
+
# service element.
|
|
115
|
+
@type_uris = type_uris
|
|
116
|
+
@server_url = uri
|
|
117
|
+
@used_yadis = true
|
|
118
|
+
|
|
119
|
+
if !is_op_identifier()
|
|
120
|
+
# XXX: This has crappy implications for Service elements that
|
|
121
|
+
# contain both 'server' and 'signon' Types. But that's a
|
|
122
|
+
# pathological configuration anyway, so I don't think I care.
|
|
123
|
+
@local_id = OpenID.find_op_local_identifier(service_element,
|
|
124
|
+
@type_uris)
|
|
125
|
+
@claimed_id = yadis_url
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def get_local_id
|
|
130
|
+
# Return the identifier that should be sent as the
|
|
131
|
+
# openid.identity parameter to the server.
|
|
132
|
+
if @local_id.nil? and @canonical_id.nil?
|
|
133
|
+
return @claimed_id
|
|
134
|
+
else
|
|
135
|
+
return (@local_id or @canonical_id)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.from_basic_service_endpoint(endpoint)
|
|
140
|
+
# Create a new instance of this class from the endpoint object
|
|
141
|
+
# passed in.
|
|
142
|
+
#
|
|
143
|
+
# @return: nil or OpenIDServiceEndpoint for this endpoint object"""
|
|
144
|
+
|
|
145
|
+
type_uris = endpoint.match_types(OPENID_TYPE_URIS)
|
|
146
|
+
|
|
147
|
+
# If any Type URIs match and there is an endpoint URI specified,
|
|
148
|
+
# then this is an OpenID endpoint
|
|
149
|
+
if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
|
|
150
|
+
openid_endpoint = self.new
|
|
151
|
+
openid_endpoint.parse_service(
|
|
152
|
+
endpoint.yadis_url,
|
|
153
|
+
endpoint.uri,
|
|
154
|
+
endpoint.type_uris,
|
|
155
|
+
endpoint.service_element)
|
|
156
|
+
else
|
|
157
|
+
openid_endpoint = nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
return openid_endpoint
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.from_html(uri, html)
|
|
164
|
+
# Parse the given document as HTML looking for an OpenID <link
|
|
165
|
+
# rel=...>
|
|
166
|
+
#
|
|
167
|
+
# @rtype: [OpenIDServiceEndpoint]
|
|
168
|
+
|
|
169
|
+
discovery_types = [
|
|
170
|
+
[OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'],
|
|
171
|
+
[OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'],
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
link_attrs = OpenID.parse_link_attrs(html)
|
|
175
|
+
services = []
|
|
176
|
+
discovery_types.each { |type_uri, op_endpoint_rel, local_id_rel|
|
|
177
|
+
|
|
178
|
+
op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
|
|
179
|
+
|
|
180
|
+
if !op_endpoint_url
|
|
181
|
+
next
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
service = self.new
|
|
185
|
+
service.claimed_id = uri
|
|
186
|
+
service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
|
|
187
|
+
service.server_url = op_endpoint_url
|
|
188
|
+
service.type_uris = [type_uri]
|
|
189
|
+
|
|
190
|
+
services << service
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return services
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.from_xrds(uri, xrds)
|
|
197
|
+
# Parse the given document as XRDS looking for OpenID services.
|
|
198
|
+
#
|
|
199
|
+
# @rtype: [OpenIDServiceEndpoint]
|
|
200
|
+
#
|
|
201
|
+
# @raises L{XRDSError}: When the XRDS does not parse.
|
|
202
|
+
return Yadis::apply_filter(uri, xrds, self)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def self.from_discovery_result(discoveryResult)
|
|
206
|
+
# Create endpoints from a DiscoveryResult.
|
|
207
|
+
#
|
|
208
|
+
# @type discoveryResult: L{DiscoveryResult}
|
|
209
|
+
#
|
|
210
|
+
# @rtype: list of L{OpenIDServiceEndpoint}
|
|
211
|
+
#
|
|
212
|
+
# @raises L{XRDSError}: When the XRDS does not parse.
|
|
213
|
+
if discoveryResult.is_xrds()
|
|
214
|
+
meth = self.method('from_xrds')
|
|
215
|
+
else
|
|
216
|
+
meth = self.method('from_html')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
return meth.call(discoveryResult.normalized_uri,
|
|
220
|
+
discoveryResult.response_text)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def self.from_op_endpoint_url(op_endpoint_url)
|
|
224
|
+
# Construct an OP-Identifier OpenIDServiceEndpoint object for
|
|
225
|
+
# a given OP Endpoint URL
|
|
226
|
+
#
|
|
227
|
+
# @param op_endpoint_url: The URL of the endpoint
|
|
228
|
+
# @rtype: OpenIDServiceEndpoint
|
|
229
|
+
service = self.new
|
|
230
|
+
service.server_url = op_endpoint_url
|
|
231
|
+
service.type_uris = [OPENID_IDP_2_0_TYPE]
|
|
232
|
+
return service
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def to_s
|
|
236
|
+
return sprintf("<%s server_url=%s claimed_id=%s " +
|
|
237
|
+
"local_id=%s canonical_id=%s used_yadis=%s>",
|
|
238
|
+
self.class, @server_url, @claimed_id,
|
|
239
|
+
@local_id, @canonical_id, @used_yadis)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def self.find_op_local_identifier(service_element, type_uris)
|
|
244
|
+
# Find the OP-Local Identifier for this xrd:Service element.
|
|
245
|
+
#
|
|
246
|
+
# This considers openid:Delegate to be a synonym for xrd:LocalID
|
|
247
|
+
# if both OpenID 1.X and OpenID 2.0 types are present. If only
|
|
248
|
+
# OpenID 1.X is present, it returns the value of
|
|
249
|
+
# openid:Delegate. If only OpenID 2.0 is present, it returns the
|
|
250
|
+
# value of xrd:LocalID. If there is more than one LocalID tag and
|
|
251
|
+
# the values are different, it raises a DiscoveryFailure. This is
|
|
252
|
+
# also triggered when the xrd:LocalID and openid:Delegate tags are
|
|
253
|
+
# different.
|
|
254
|
+
|
|
255
|
+
# XXX: Test this function on its own!
|
|
256
|
+
|
|
257
|
+
# Build the list of tags that could contain the OP-Local
|
|
258
|
+
# Identifier
|
|
259
|
+
local_id_tags = []
|
|
260
|
+
if type_uris.member?(OPENID_1_1_TYPE) or
|
|
261
|
+
type_uris.member?(OPENID_1_0_TYPE)
|
|
262
|
+
# local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
|
|
263
|
+
service_element.add_namespace('openid', OPENID_1_0_NS)
|
|
264
|
+
local_id_tags << "openid:Delegate"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
if type_uris.member?(OPENID_2_0_TYPE)
|
|
268
|
+
# local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
|
|
269
|
+
service_element.add_namespace('xrd', Yadis::XRD_NS_2_0)
|
|
270
|
+
local_id_tags << "xrd:LocalID"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Walk through all the matching tags and make sure that they all
|
|
274
|
+
# have the same value
|
|
275
|
+
local_id = nil
|
|
276
|
+
local_id_tags.each { |local_id_tag|
|
|
277
|
+
service_element.each_element(local_id_tag) { |local_id_element|
|
|
278
|
+
if local_id.nil?
|
|
279
|
+
local_id = local_id_element.text
|
|
280
|
+
elsif local_id != local_id_element.text
|
|
281
|
+
format = 'More than one %s tag found in one service element'
|
|
282
|
+
message = sprintf(format, local_id_tag)
|
|
283
|
+
raise DiscoveryFailure.new(message, nil)
|
|
284
|
+
end
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return local_id
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def self.normalize_xri(xri)
|
|
292
|
+
# Normalize an XRI, stripping its scheme if present
|
|
293
|
+
m = /^xri:\/\/(.*)/.match(xri)
|
|
294
|
+
xri = m[1] if m
|
|
295
|
+
return xri
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def self.normalize_url(url)
|
|
299
|
+
# Normalize a URL, converting normalization failures to
|
|
300
|
+
# DiscoveryFailure
|
|
301
|
+
begin
|
|
302
|
+
normalized = URINorm.urinorm(url)
|
|
303
|
+
rescue URI::Error => why
|
|
304
|
+
raise DiscoveryFailure.new("Error normalizing #{url}: #{why.message}", nil)
|
|
305
|
+
else
|
|
306
|
+
defragged = URI::parse(normalized)
|
|
307
|
+
defragged.fragment = nil
|
|
308
|
+
return defragged.normalize.to_s
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def self.best_matching_service(service, preferred_types)
|
|
313
|
+
# Return the index of the first matching type, or something higher
|
|
314
|
+
# if no type matches.
|
|
315
|
+
#
|
|
316
|
+
# This provides an ordering in which service elements that contain
|
|
317
|
+
# a type that comes earlier in the preferred types list come
|
|
318
|
+
# before service elements that come later. If a service element
|
|
319
|
+
# has more than one type, the most preferred one wins.
|
|
320
|
+
preferred_types.each_with_index { |value, index|
|
|
321
|
+
if service.type_uris.member?(value)
|
|
322
|
+
return index
|
|
323
|
+
end
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return preferred_types.length
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def self.arrange_by_type(service_list, preferred_types)
|
|
330
|
+
# Rearrange service_list in a new list so services are ordered by
|
|
331
|
+
# types listed in preferred_types. Return the new list.
|
|
332
|
+
|
|
333
|
+
# Build a list with the service elements in tuples whose
|
|
334
|
+
# comparison will prefer the one with the best matching service
|
|
335
|
+
prio_services = []
|
|
336
|
+
|
|
337
|
+
service_list.each_with_index { |s, index|
|
|
338
|
+
prio_services << [best_matching_service(s, preferred_types), index, s]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
prio_services.sort!
|
|
342
|
+
|
|
343
|
+
# Now that the services are sorted by priority, remove the sort
|
|
344
|
+
# keys from the list.
|
|
345
|
+
(0...prio_services.length).each { |i|
|
|
346
|
+
prio_services[i] = prio_services[i][2]
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return prio_services
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def self.get_op_or_user_services(openid_services)
|
|
353
|
+
# Extract OP Identifier services. If none found, return the rest,
|
|
354
|
+
# sorted with most preferred first according to
|
|
355
|
+
# OpenIDServiceEndpoint.openid_type_uris.
|
|
356
|
+
#
|
|
357
|
+
# openid_services is a list of OpenIDServiceEndpoint objects.
|
|
358
|
+
#
|
|
359
|
+
# Returns a list of OpenIDServiceEndpoint objects.
|
|
360
|
+
|
|
361
|
+
op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])
|
|
362
|
+
|
|
363
|
+
openid_services = arrange_by_type(openid_services,
|
|
364
|
+
OpenIDServiceEndpoint::OPENID_TYPE_URIS)
|
|
365
|
+
|
|
366
|
+
if !op_services.empty?
|
|
367
|
+
return op_services
|
|
368
|
+
else
|
|
369
|
+
return openid_services
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def self.discover_yadis(uri)
|
|
374
|
+
# Discover OpenID services for a URI. Tries Yadis and falls back
|
|
375
|
+
# on old-style <link rel='...'> discovery if Yadis fails.
|
|
376
|
+
#
|
|
377
|
+
# @param uri: normalized identity URL
|
|
378
|
+
# @type uri: str
|
|
379
|
+
#
|
|
380
|
+
# @return: (claimed_id, services)
|
|
381
|
+
# @rtype: (str, list(OpenIDServiceEndpoint))
|
|
382
|
+
#
|
|
383
|
+
# @raises DiscoveryFailure: when discovery fails.
|
|
384
|
+
|
|
385
|
+
# Might raise a yadis.discover.DiscoveryFailure if no document
|
|
386
|
+
# came back for that URI at all. I don't think falling back to
|
|
387
|
+
# OpenID 1.0 discovery on the same URL will help, so don't bother
|
|
388
|
+
# to catch it.
|
|
389
|
+
response = Yadis.discover(uri)
|
|
390
|
+
|
|
391
|
+
yadis_url = response.normalized_uri
|
|
392
|
+
body = response.response_text
|
|
393
|
+
|
|
394
|
+
begin
|
|
395
|
+
openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
|
|
396
|
+
rescue Yadis::XRDSError
|
|
397
|
+
# Does not parse as a Yadis XRDS file
|
|
398
|
+
openid_services = []
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
if openid_services.empty?
|
|
402
|
+
# Either not an XRDS or there are no OpenID services.
|
|
403
|
+
|
|
404
|
+
if response.is_xrds
|
|
405
|
+
# if we got the Yadis content-type or followed the Yadis
|
|
406
|
+
# header, re-fetch the document without following the Yadis
|
|
407
|
+
# header, with no Accept header.
|
|
408
|
+
return self.discover_no_yadis(uri)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Try to parse the response as HTML.
|
|
412
|
+
# <link rel="...">
|
|
413
|
+
openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
return [yadis_url, self.get_op_or_user_services(openid_services)]
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def self.discover_xri(iname)
|
|
420
|
+
endpoints = []
|
|
421
|
+
iname = self.normalize_xri(iname)
|
|
422
|
+
|
|
423
|
+
begin
|
|
424
|
+
canonical_id, services = Yadis::XRI::ProxyResolver.new().query( iname )
|
|
425
|
+
|
|
426
|
+
if canonical_id.nil?
|
|
427
|
+
raise Yadis::XRDSError.new(sprintf('No CanonicalID found for XRI %s', iname))
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
flt = Yadis.make_filter(OpenIDServiceEndpoint)
|
|
431
|
+
|
|
432
|
+
services.each { |service_element|
|
|
433
|
+
endpoints += flt.get_service_endpoints(iname, service_element)
|
|
434
|
+
}
|
|
435
|
+
rescue Yadis::XRDSError => why
|
|
436
|
+
Util.log('xrds error on ' + iname + ': ' + why.to_s)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
endpoints.each { |endpoint|
|
|
440
|
+
# Is there a way to pass this through the filter to the endpoint
|
|
441
|
+
# constructor instead of tacking it on after?
|
|
442
|
+
endpoint.canonical_id = canonical_id
|
|
443
|
+
endpoint.claimed_id = canonical_id
|
|
444
|
+
endpoint.display_identifier = iname
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# FIXME: returned xri should probably be in some normal form
|
|
448
|
+
return [iname, self.get_op_or_user_services(endpoints)]
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def self.discover_no_yadis(uri)
|
|
452
|
+
http_resp = OpenID.fetch(uri)
|
|
453
|
+
if http_resp.code != "200" and http_resp.code != "206"
|
|
454
|
+
raise DiscoveryFailure.new(
|
|
455
|
+
"HTTP Response status from identity URL host is not \"200\". "\
|
|
456
|
+
"Got status #{http_resp.code.inspect}", http_resp)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
claimed_id = http_resp.final_url
|
|
460
|
+
openid_services = OpenIDServiceEndpoint.from_html(
|
|
461
|
+
claimed_id, http_resp.body)
|
|
462
|
+
return [claimed_id, openid_services]
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def self.discover_uri(uri)
|
|
466
|
+
# Hack to work around URI parsing for URls with *no* scheme.
|
|
467
|
+
if uri.index("://").nil?
|
|
468
|
+
uri = 'http://' + uri
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
begin
|
|
472
|
+
parsed = URI::parse(uri)
|
|
473
|
+
rescue URI::InvalidURIError => why
|
|
474
|
+
raise DiscoveryFailure.new("URI is not valid: #{why.message}", nil)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
if !parsed.scheme.nil? and !parsed.scheme.empty?
|
|
478
|
+
if !['http', 'https'].member?(parsed.scheme)
|
|
479
|
+
raise DiscoveryFailure.new(
|
|
480
|
+
"URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil)
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
uri = self.normalize_url(uri)
|
|
485
|
+
claimed_id, openid_services = self.discover_yadis(uri)
|
|
486
|
+
claimed_id = self.normalize_url(claimed_id)
|
|
487
|
+
return [claimed_id, openid_services]
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def self.discover(identifier)
|
|
491
|
+
if Yadis::XRI::identifier_scheme(identifier) == :xri
|
|
492
|
+
normalized_identifier, services = discover_xri(identifier)
|
|
493
|
+
else
|
|
494
|
+
return discover_uri(identifier)
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|