ruby-openid 1.1.4 → 2.0.1

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.
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