ruby-openid 1.1.4 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-openid might be problematic. Click here for more details.

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