nov-ruby-openid 2.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/CHANGELOG +215 -0
  2. data/CHANGES-2.1.0 +36 -0
  3. data/INSTALL +47 -0
  4. data/LICENSE +210 -0
  5. data/NOTICE +2 -0
  6. data/README +81 -0
  7. data/Rakefile +98 -0
  8. data/UPGRADE +127 -0
  9. data/VERSION +1 -0
  10. data/contrib/google/ruby-openid-apps-discovery-1.0.gem +0 -0
  11. data/contrib/google/ruby-openid-apps-discovery-1.01.gem +0 -0
  12. data/examples/README +32 -0
  13. data/examples/active_record_openid_store/README +58 -0
  14. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  15. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  16. data/examples/active_record_openid_store/init.rb +8 -0
  17. data/examples/active_record_openid_store/lib/association.rb +10 -0
  18. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  19. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  20. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  21. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  22. data/examples/discover +49 -0
  23. data/examples/rails_openid/README +153 -0
  24. data/examples/rails_openid/Rakefile +10 -0
  25. data/examples/rails_openid/app/controllers/application.rb +4 -0
  26. data/examples/rails_openid/app/controllers/consumer_controller.rb +122 -0
  27. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  28. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  29. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  30. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  31. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  32. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  33. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  34. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  35. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  36. data/examples/rails_openid/config/boot.rb +19 -0
  37. data/examples/rails_openid/config/database.yml +74 -0
  38. data/examples/rails_openid/config/environment.rb +54 -0
  39. data/examples/rails_openid/config/environments/development.rb +19 -0
  40. data/examples/rails_openid/config/environments/production.rb +19 -0
  41. data/examples/rails_openid/config/environments/test.rb +19 -0
  42. data/examples/rails_openid/config/routes.rb +24 -0
  43. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  44. data/examples/rails_openid/public/.htaccess +40 -0
  45. data/examples/rails_openid/public/404.html +8 -0
  46. data/examples/rails_openid/public/500.html +8 -0
  47. data/examples/rails_openid/public/dispatch.cgi +12 -0
  48. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  49. data/examples/rails_openid/public/dispatch.rb +12 -0
  50. data/examples/rails_openid/public/favicon.ico +0 -0
  51. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  52. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  53. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  54. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  55. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  56. data/examples/rails_openid/public/robots.txt +1 -0
  57. data/examples/rails_openid/script/about +3 -0
  58. data/examples/rails_openid/script/breakpointer +3 -0
  59. data/examples/rails_openid/script/console +3 -0
  60. data/examples/rails_openid/script/destroy +3 -0
  61. data/examples/rails_openid/script/generate +3 -0
  62. data/examples/rails_openid/script/performance/benchmarker +3 -0
  63. data/examples/rails_openid/script/performance/profiler +3 -0
  64. data/examples/rails_openid/script/plugin +3 -0
  65. data/examples/rails_openid/script/process/reaper +3 -0
  66. data/examples/rails_openid/script/process/spawner +3 -0
  67. data/examples/rails_openid/script/process/spinner +3 -0
  68. data/examples/rails_openid/script/runner +3 -0
  69. data/examples/rails_openid/script/server +3 -0
  70. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  71. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  72. data/examples/rails_openid/test/test_helper.rb +28 -0
  73. data/lib/hmac/hmac.rb +112 -0
  74. data/lib/hmac/sha1.rb +11 -0
  75. data/lib/hmac/sha2.rb +25 -0
  76. data/lib/openid.rb +20 -0
  77. data/lib/openid/association.rb +249 -0
  78. data/lib/openid/consumer.rb +395 -0
  79. data/lib/openid/consumer/associationmanager.rb +344 -0
  80. data/lib/openid/consumer/checkid_request.rb +186 -0
  81. data/lib/openid/consumer/discovery.rb +497 -0
  82. data/lib/openid/consumer/discovery_manager.rb +123 -0
  83. data/lib/openid/consumer/html_parse.rb +134 -0
  84. data/lib/openid/consumer/idres.rb +523 -0
  85. data/lib/openid/consumer/responses.rb +148 -0
  86. data/lib/openid/cryptutil.rb +115 -0
  87. data/lib/openid/dh.rb +89 -0
  88. data/lib/openid/extension.rb +39 -0
  89. data/lib/openid/extensions/ax.rb +539 -0
  90. data/lib/openid/extensions/oauth.rb +91 -0
  91. data/lib/openid/extensions/pape.rb +179 -0
  92. data/lib/openid/extensions/sreg.rb +277 -0
  93. data/lib/openid/extensions/ui.rb +53 -0
  94. data/lib/openid/extras.rb +11 -0
  95. data/lib/openid/fetchers.rb +258 -0
  96. data/lib/openid/kvform.rb +136 -0
  97. data/lib/openid/kvpost.rb +58 -0
  98. data/lib/openid/message.rb +553 -0
  99. data/lib/openid/protocolerror.rb +8 -0
  100. data/lib/openid/server.rb +1544 -0
  101. data/lib/openid/store/filesystem.rb +271 -0
  102. data/lib/openid/store/interface.rb +75 -0
  103. data/lib/openid/store/memcache.rb +107 -0
  104. data/lib/openid/store/memory.rb +84 -0
  105. data/lib/openid/store/nonce.rb +68 -0
  106. data/lib/openid/trustroot.rb +349 -0
  107. data/lib/openid/urinorm.rb +75 -0
  108. data/lib/openid/util.rb +110 -0
  109. data/lib/openid/yadis/accept.rb +148 -0
  110. data/lib/openid/yadis/constants.rb +21 -0
  111. data/lib/openid/yadis/discovery.rb +153 -0
  112. data/lib/openid/yadis/filters.rb +205 -0
  113. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  114. data/lib/openid/yadis/parsehtml.rb +45 -0
  115. data/lib/openid/yadis/services.rb +42 -0
  116. data/lib/openid/yadis/xrds.rb +155 -0
  117. data/lib/openid/yadis/xri.rb +90 -0
  118. data/lib/openid/yadis/xrires.rb +99 -0
  119. data/setup.rb +1551 -0
  120. data/test/data/accept.txt +124 -0
  121. data/test/data/dh.txt +29 -0
  122. data/test/data/example-xrds.xml +14 -0
  123. data/test/data/linkparse.txt +587 -0
  124. data/test/data/n2b64 +650 -0
  125. data/test/data/test1-discover.txt +137 -0
  126. data/test/data/test1-parsehtml.txt +152 -0
  127. data/test/data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/data/test_discover/openid.html +11 -0
  129. data/test/data/test_discover/openid2.html +11 -0
  130. data/test/data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/data/test_discover/openid_utf8.html +11 -0
  138. data/test/data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/data/test_discover/yadis_idp.xml +12 -0
  144. data/test/data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/data/test_xrds/README +12 -0
  148. data/test/data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/data/test_xrds/no-xrd.xml +7 -0
  152. data/test/data/test_xrds/not-xrds.xml +2 -0
  153. data/test/data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/data/test_xrds/ref.xrds +109 -0
  155. data/test/data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/data/test_xrds/spoof1.xrds +25 -0
  157. data/test/data/test_xrds/spoof2.xrds +25 -0
  158. data/test/data/test_xrds/spoof3.xrds +37 -0
  159. data/test/data/test_xrds/status222.xrds +9 -0
  160. data/test/data/test_xrds/subsegments.xrds +58 -0
  161. data/test/data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/data/trustroot.txt +153 -0
  163. data/test/data/urinorm.txt +79 -0
  164. data/test/discoverdata.rb +131 -0
  165. data/test/test_accept.rb +170 -0
  166. data/test/test_association.rb +266 -0
  167. data/test/test_associationmanager.rb +917 -0
  168. data/test/test_ax.rb +690 -0
  169. data/test/test_checkid_request.rb +294 -0
  170. data/test/test_consumer.rb +257 -0
  171. data/test/test_cryptutil.rb +119 -0
  172. data/test/test_dh.rb +86 -0
  173. data/test/test_discover.rb +852 -0
  174. data/test/test_discovery_manager.rb +262 -0
  175. data/test/test_extension.rb +46 -0
  176. data/test/test_extras.rb +35 -0
  177. data/test/test_fetchers.rb +565 -0
  178. data/test/test_filters.rb +270 -0
  179. data/test/test_idres.rb +963 -0
  180. data/test/test_kvform.rb +165 -0
  181. data/test/test_kvpost.rb +65 -0
  182. data/test/test_linkparse.rb +101 -0
  183. data/test/test_message.rb +1116 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +175 -0
  186. data/test/test_openid_yadis.rb +178 -0
  187. data/test/test_pape.rb +247 -0
  188. data/test/test_parsehtml.rb +80 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2457 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +298 -0
  193. data/test/test_trustroot.rb +113 -0
  194. data/test/test_ui.rb +93 -0
  195. data/test/test_urinorm.rb +35 -0
  196. data/test/test_util.rb +145 -0
  197. data/test/test_xrds.rb +169 -0
  198. data/test/test_xri.rb +48 -0
  199. data/test/test_xrires.rb +63 -0
  200. data/test/test_yadis_discovery.rb +220 -0
  201. data/test/testutil.rb +127 -0
  202. data/test/util.rb +53 -0
  203. metadata +336 -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