entp-ruby-openid 2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data/CHANGELOG +215 -0
  2. data/INSTALL +47 -0
  3. data/LICENSE +210 -0
  4. data/NOTICE +2 -0
  5. data/README +85 -0
  6. data/UPGRADE +127 -0
  7. data/admin/runtests.rb +45 -0
  8. data/examples/README +32 -0
  9. data/examples/active_record_openid_store/README +58 -0
  10. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  11. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  12. data/examples/active_record_openid_store/init.rb +8 -0
  13. data/examples/active_record_openid_store/lib/association.rb +10 -0
  14. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  15. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  16. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  17. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  18. data/examples/discover +49 -0
  19. data/examples/rails_openid/README +153 -0
  20. data/examples/rails_openid/Rakefile +10 -0
  21. data/examples/rails_openid/app/controllers/application.rb +4 -0
  22. data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
  23. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  24. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  25. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  26. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  27. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  28. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  29. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  30. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  31. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  32. data/examples/rails_openid/config/boot.rb +19 -0
  33. data/examples/rails_openid/config/database.yml +74 -0
  34. data/examples/rails_openid/config/environment.rb +54 -0
  35. data/examples/rails_openid/config/environments/development.rb +19 -0
  36. data/examples/rails_openid/config/environments/production.rb +19 -0
  37. data/examples/rails_openid/config/environments/test.rb +19 -0
  38. data/examples/rails_openid/config/routes.rb +24 -0
  39. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  40. data/examples/rails_openid/public/404.html +8 -0
  41. data/examples/rails_openid/public/500.html +8 -0
  42. data/examples/rails_openid/public/dispatch.cgi +12 -0
  43. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  44. data/examples/rails_openid/public/dispatch.rb +12 -0
  45. data/examples/rails_openid/public/favicon.ico +0 -0
  46. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  47. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  48. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  49. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  50. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  51. data/examples/rails_openid/public/robots.txt +1 -0
  52. data/examples/rails_openid/script/about +3 -0
  53. data/examples/rails_openid/script/breakpointer +3 -0
  54. data/examples/rails_openid/script/console +3 -0
  55. data/examples/rails_openid/script/destroy +3 -0
  56. data/examples/rails_openid/script/generate +3 -0
  57. data/examples/rails_openid/script/performance/benchmarker +3 -0
  58. data/examples/rails_openid/script/performance/profiler +3 -0
  59. data/examples/rails_openid/script/plugin +3 -0
  60. data/examples/rails_openid/script/process/reaper +3 -0
  61. data/examples/rails_openid/script/process/spawner +3 -0
  62. data/examples/rails_openid/script/process/spinner +3 -0
  63. data/examples/rails_openid/script/runner +3 -0
  64. data/examples/rails_openid/script/server +3 -0
  65. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  66. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  67. data/examples/rails_openid/test/test_helper.rb +28 -0
  68. data/lib/hmac/hmac.rb +112 -0
  69. data/lib/hmac/sha1.rb +11 -0
  70. data/lib/hmac/sha2.rb +25 -0
  71. data/lib/openid.rb +22 -0
  72. data/lib/openid/association.rb +249 -0
  73. data/lib/openid/consumer.rb +395 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +497 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +150 -0
  81. data/lib/openid/cryptutil.rb +115 -0
  82. data/lib/openid/dh.rb +89 -0
  83. data/lib/openid/extension.rb +39 -0
  84. data/lib/openid/extensions/ax.rb +539 -0
  85. data/lib/openid/extensions/oauth.rb +91 -0
  86. data/lib/openid/extensions/pape.rb +179 -0
  87. data/lib/openid/extensions/sreg.rb +277 -0
  88. data/lib/openid/extras.rb +11 -0
  89. data/lib/openid/fetchers.rb +258 -0
  90. data/lib/openid/kvform.rb +136 -0
  91. data/lib/openid/kvpost.rb +58 -0
  92. data/lib/openid/message.rb +553 -0
  93. data/lib/openid/protocolerror.rb +12 -0
  94. data/lib/openid/server.rb +1544 -0
  95. data/lib/openid/store.rb +10 -0
  96. data/lib/openid/store/filesystem.rb +272 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +109 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +119 -0
  104. data/lib/openid/version.rb +3 -0
  105. data/lib/openid/yadis.rb +15 -0
  106. data/lib/openid/yadis/accept.rb +148 -0
  107. data/lib/openid/yadis/constants.rb +21 -0
  108. data/lib/openid/yadis/discovery.rb +153 -0
  109. data/lib/openid/yadis/filters.rb +205 -0
  110. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  111. data/lib/openid/yadis/parsehtml.rb +45 -0
  112. data/lib/openid/yadis/services.rb +42 -0
  113. data/lib/openid/yadis/xrds.rb +155 -0
  114. data/lib/openid/yadis/xri.rb +90 -0
  115. data/lib/openid/yadis/xrires.rb +91 -0
  116. data/test/data/test_discover/openid_utf8.html +11 -0
  117. data/test/support/test_data_mixin.rb +127 -0
  118. data/test/support/test_util.rb +53 -0
  119. data/test/support/yadis_data.rb +131 -0
  120. data/test/support/yadis_data/accept.txt +124 -0
  121. data/test/support/yadis_data/dh.txt +29 -0
  122. data/test/support/yadis_data/example-xrds.xml +14 -0
  123. data/test/support/yadis_data/linkparse.txt +587 -0
  124. data/test/support/yadis_data/n2b64 +650 -0
  125. data/test/support/yadis_data/test1-discover.txt +137 -0
  126. data/test/support/yadis_data/test1-parsehtml.txt +152 -0
  127. data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/support/yadis_data/test_discover/openid.html +11 -0
  129. data/test/support/yadis_data/test_discover/openid2.html +11 -0
  130. data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
  138. data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
  144. data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/support/yadis_data/test_xrds/README +12 -0
  148. data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
  152. data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
  153. data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
  155. data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
  157. data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
  158. data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
  159. data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
  160. data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
  161. data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/support/yadis_data/trustroot.txt +153 -0
  163. data/test/support/yadis_data/urinorm.txt +79 -0
  164. data/test/test_accept.rb +170 -0
  165. data/test/test_association.rb +268 -0
  166. data/test/test_associationmanager.rb +918 -0
  167. data/test/test_ax.rb +690 -0
  168. data/test/test_checkid_request.rb +293 -0
  169. data/test/test_consumer.rb +260 -0
  170. data/test/test_cryptutil.rb +119 -0
  171. data/test/test_dh.rb +85 -0
  172. data/test/test_discover.rb +848 -0
  173. data/test/test_discovery_manager.rb +259 -0
  174. data/test/test_extension.rb +46 -0
  175. data/test/test_extras.rb +35 -0
  176. data/test/test_fetchers.rb +554 -0
  177. data/test/test_filters.rb +269 -0
  178. data/test/test_helper.rb +4 -0
  179. data/test/test_idres.rb +961 -0
  180. data/test/test_kvform.rb +164 -0
  181. data/test/test_kvpost.rb +64 -0
  182. data/test/test_linkparse.rb +100 -0
  183. data/test/test_message.rb +1115 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +176 -0
  186. data/test/test_openid_yadis.rb +177 -0
  187. data/test/test_pape.rb +248 -0
  188. data/test/test_parsehtml.rb +79 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2455 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +292 -0
  193. data/test/test_trustroot.rb +111 -0
  194. data/test/test_urinorm.rb +34 -0
  195. data/test/test_util.rb +145 -0
  196. data/test/test_xrds.rb +167 -0
  197. data/test/test_xri.rb +48 -0
  198. data/test/test_xrires.rb +67 -0
  199. data/test/test_yadis_discovery.rb +218 -0
  200. metadata +268 -0
@@ -0,0 +1,123 @@
1
+ module OpenID
2
+ class Consumer
3
+
4
+ # A set of discovered services, for tracking which providers have
5
+ # been attempted for an OpenID identifier
6
+ class DiscoveredServices
7
+ attr_reader :current
8
+
9
+ def initialize(starting_url, yadis_url, services)
10
+ @starting_url = starting_url
11
+ @yadis_url = yadis_url
12
+ @services = services.dup
13
+ @current = nil
14
+ end
15
+
16
+ def next
17
+ @current = @services.shift
18
+ end
19
+
20
+ def for_url?(url)
21
+ [@starting_url, @yadis_url].member?(url)
22
+ end
23
+
24
+ def started?
25
+ !@current.nil?
26
+ end
27
+
28
+ def empty?
29
+ @services.empty?
30
+ end
31
+ end
32
+
33
+ # Manages calling discovery and tracking which endpoints have
34
+ # already been attempted.
35
+ class DiscoveryManager
36
+ def initialize(session, url, session_key_suffix=nil)
37
+ @url = url
38
+
39
+ @session = session
40
+ @session_key_suffix = session_key_suffix || 'auth'
41
+ end
42
+
43
+ def get_next_service
44
+ manager = get_manager
45
+ if !manager.nil? && manager.empty?
46
+ destroy_manager
47
+ manager = nil
48
+ end
49
+
50
+ if manager.nil?
51
+ yadis_url, services = yield @url
52
+ manager = create_manager(yadis_url, services)
53
+ end
54
+
55
+ if !manager.nil?
56
+ service = manager.next
57
+ store(manager)
58
+ else
59
+ service = nil
60
+ end
61
+
62
+ return service
63
+ end
64
+
65
+ def cleanup(force=false)
66
+ manager = get_manager(force)
67
+ if !manager.nil?
68
+ service = manager.current
69
+ destroy_manager(force)
70
+ else
71
+ service = nil
72
+ end
73
+ return service
74
+ end
75
+
76
+ protected
77
+
78
+ def get_manager(force=false)
79
+ manager = load
80
+ if force || manager.nil? || manager.for_url?(@url)
81
+ return manager
82
+ else
83
+ return nil
84
+ end
85
+ end
86
+
87
+ def create_manager(yadis_url, services)
88
+ manager = get_manager
89
+ if !manager.nil?
90
+ raise StandardError, "There is already a manager for #{yadis_url}"
91
+ end
92
+ if services.empty?
93
+ return nil
94
+ end
95
+ manager = DiscoveredServices.new(@url, yadis_url, services)
96
+ store(manager)
97
+ return manager
98
+ end
99
+
100
+ def destroy_manager(force=false)
101
+ if !get_manager(force).nil?
102
+ destroy!
103
+ end
104
+ end
105
+
106
+ def session_key
107
+ 'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix
108
+ end
109
+
110
+ def store(manager)
111
+ @session[session_key] = manager
112
+ end
113
+
114
+ def load
115
+ @session[session_key]
116
+ end
117
+
118
+ def destroy!
119
+ @session[session_key] = nil
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,134 @@
1
+ require "openid/yadis/htmltokenizer"
2
+
3
+ module OpenID
4
+
5
+ # Stuff to remove before we start looking for tags
6
+ REMOVED_RE = /
7
+ # Comments
8
+ <!--.*?-->
9
+
10
+ # CDATA blocks
11
+ | <!\[CDATA\[.*?\]\]>
12
+
13
+ # script blocks
14
+ | <script\b
15
+
16
+ # make sure script is not an XML namespace
17
+ (?!:)
18
+
19
+ [^>]*>.*?<\/script>
20
+
21
+ /mix
22
+
23
+ def OpenID.openid_unescape(s)
24
+ s.gsub('&amp;','&').gsub('&lt;','<').gsub('&gt;','>').gsub('&quot;','"')
25
+ end
26
+
27
+ def OpenID.unescape_hash(h)
28
+ newh = {}
29
+ h.map{|k,v|
30
+ newh[k]=openid_unescape(v)
31
+ }
32
+ newh
33
+ end
34
+
35
+
36
+ def OpenID.parse_link_attrs(html)
37
+ stripped = html.gsub(REMOVED_RE,'')
38
+ parser = HTMLTokenizer.new(stripped)
39
+
40
+ links = []
41
+ # to keep track of whether or not we are in the head element
42
+ in_head = false
43
+ in_html = false
44
+ saw_head = false
45
+
46
+ begin
47
+ while el = parser.getTag('head', '/head', 'link', 'body', '/body',
48
+ 'html', '/html')
49
+
50
+ # we are leaving head or have reached body, so we bail
51
+ return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)
52
+
53
+ # enforce html > head > link
54
+ if el.tag_name == 'html'
55
+ in_html = true
56
+ end
57
+ next unless in_html
58
+ if el.tag_name == 'head'
59
+ if saw_head
60
+ return links #only allow one head
61
+ end
62
+ saw_head = true
63
+ unless el.to_s[-2] == 47 # tag ends with a /: a short tag
64
+ in_head = true
65
+ end
66
+ end
67
+ next unless in_head
68
+
69
+ return links if el.tag_name == 'html'
70
+
71
+ if el.tag_name == 'link'
72
+ links << unescape_hash(el.attr_hash)
73
+ end
74
+
75
+ end
76
+ rescue Exception # just stop parsing if there's an error
77
+ end
78
+ return links
79
+ end
80
+
81
+ def OpenID.rel_matches(rel_attr, target_rel)
82
+ # Does this target_rel appear in the rel_str?
83
+ # XXX: TESTME
84
+ rels = rel_attr.strip().split()
85
+ rels.each { |rel|
86
+ rel = rel.downcase
87
+ if rel == target_rel
88
+ return true
89
+ end
90
+ }
91
+
92
+ return false
93
+ end
94
+
95
+ def OpenID.link_has_rel(link_attrs, target_rel)
96
+ # Does this link have target_rel as a relationship?
97
+
98
+ # XXX: TESTME
99
+ rel_attr = link_attrs['rel']
100
+ return (rel_attr and rel_matches(rel_attr, target_rel))
101
+ end
102
+
103
+ def OpenID.find_links_rel(link_attrs_list, target_rel)
104
+ # Filter the list of link attributes on whether it has target_rel
105
+ # as a relationship.
106
+
107
+ # XXX: TESTME
108
+ matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
109
+ result = []
110
+
111
+ link_attrs_list.each { |item|
112
+ if matchesTarget.call(item)
113
+ result << item
114
+ end
115
+ }
116
+
117
+ return result
118
+ end
119
+
120
+ def OpenID.find_first_href(link_attrs_list, target_rel)
121
+ # Return the value of the href attribute for the first link tag in
122
+ # the list that has target_rel as a relationship.
123
+
124
+ # XXX: TESTME
125
+ matches = find_links_rel(link_attrs_list, target_rel)
126
+ if !matches or matches.empty?
127
+ return nil
128
+ end
129
+
130
+ first = matches[0]
131
+ return first['href']
132
+ end
133
+ end
134
+
@@ -0,0 +1,523 @@
1
+ require "openid/message"
2
+ require "openid/protocolerror"
3
+ require "openid/kvpost"
4
+ require "openid/consumer/discovery"
5
+ require "openid/urinorm"
6
+
7
+ module OpenID
8
+ class TypeURIMismatch < ProtocolError
9
+ attr_reader :type_uri, :endpoint
10
+
11
+ def initialize(type_uri, endpoint)
12
+ @type_uri = type_uri
13
+ @endpoint = endpoint
14
+ end
15
+ end
16
+
17
+ class Consumer
18
+ @openid1_return_to_nonce_name = 'rp_nonce'
19
+ @openid1_return_to_claimed_id_name = 'openid1_claimed_id'
20
+
21
+ # Set the name of the query parameter that this library will use
22
+ # to thread a nonce through an OpenID 1 transaction. It will be
23
+ # appended to the return_to URL.
24
+ def self.openid1_return_to_nonce_name=(query_arg_name)
25
+ @openid1_return_to_nonce_name = query_arg_name
26
+ end
27
+
28
+ # See openid1_return_to_nonce_name= documentation
29
+ def self.openid1_return_to_nonce_name
30
+ @openid1_return_to_nonce_name
31
+ end
32
+
33
+ # Set the name of the query parameter that this library will use
34
+ # to thread the requested URL through an OpenID 1 transaction (for
35
+ # use when verifying discovered information). It will be appended
36
+ # to the return_to URL.
37
+ def self.openid1_return_to_claimed_id_name=(query_arg_name)
38
+ @openid1_return_to_claimed_id_name = query_arg_name
39
+ end
40
+
41
+ # See openid1_return_to_claimed_id_name=
42
+ def self.openid1_return_to_claimed_id_name
43
+ @openid1_return_to_claimed_id_name
44
+ end
45
+
46
+ # Handles an openid.mode=id_res response. This object is
47
+ # instantiated and used by the Consumer.
48
+ class IdResHandler
49
+ attr_reader :endpoint, :message
50
+
51
+ def initialize(message, current_url, store=nil, endpoint=nil)
52
+ @store = store # Fer the nonce and invalidate_handle
53
+ @message = message
54
+ @endpoint = endpoint
55
+ @current_url = current_url
56
+ @signed_list = nil
57
+
58
+ # Start the verification process
59
+ id_res
60
+ end
61
+
62
+ def signed_fields
63
+ signed_list.map {|x| 'openid.' + x}
64
+ end
65
+
66
+ protected
67
+
68
+ # This method will raise ProtocolError unless the request is a
69
+ # valid id_res response. Once it has been verified, the methods
70
+ # 'endpoint', 'message', and 'signed_fields' contain the
71
+ # verified information.
72
+ def id_res
73
+ check_for_fields
74
+ verify_return_to
75
+ verify_discovery_results
76
+ check_signature
77
+ check_nonce
78
+ end
79
+
80
+ def server_url
81
+ @endpoint.nil? ? nil : @endpoint.server_url
82
+ end
83
+
84
+ def openid_namespace
85
+ @message.get_openid_namespace
86
+ end
87
+
88
+ def fetch(field, default=NO_DEFAULT)
89
+ @message.get_arg(OPENID_NS, field, default)
90
+ end
91
+
92
+ def signed_list
93
+ if @signed_list.nil?
94
+ signed_list_str = fetch('signed', nil)
95
+ if signed_list_str.nil?
96
+ raise ProtocolError, 'Response missing signed list'
97
+ end
98
+
99
+ @signed_list = signed_list_str.split(',', -1)
100
+ end
101
+ @signed_list
102
+ end
103
+
104
+ def check_for_fields
105
+ # XXX: if a field is missing, we should not have to explicitly
106
+ # check that it's present, just make sure that the fields are
107
+ # actually being used by the rest of the code in
108
+ # tests. Although, which fields are signed does need to be
109
+ # checked somewhere.
110
+ basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
111
+ basic_sig_fields = ['return_to', 'identity']
112
+
113
+ case openid_namespace
114
+ when OPENID2_NS
115
+ require_fields = basic_fields + ['op_endpoint']
116
+ require_sigs = basic_sig_fields +
117
+ ['response_nonce', 'claimed_id', 'assoc_handle', 'op_endpoint']
118
+ when OPENID1_NS, OPENID11_NS
119
+ require_fields = basic_fields + ['identity']
120
+ require_sigs = basic_sig_fields
121
+ else
122
+ raise RuntimeError, "check_for_fields doesn't know about "\
123
+ "namespace #{openid_namespace.inspect}"
124
+ end
125
+
126
+ require_fields.each do |field|
127
+ if !@message.has_key?(OPENID_NS, field)
128
+ raise ProtocolError, "Missing required field #{field}"
129
+ end
130
+ end
131
+
132
+ require_sigs.each do |field|
133
+ # Field is present and not in signed list
134
+ if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
135
+ raise ProtocolError, "#{field.inspect} not signed"
136
+ end
137
+ end
138
+ end
139
+
140
+ def verify_return_to
141
+ begin
142
+ msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
143
+ rescue URI::InvalidURIError
144
+ raise ProtocolError, ("return_to is not a valid URI")
145
+ end
146
+
147
+ verify_return_to_args(msg_return_to)
148
+ if !@current_url.nil?
149
+ verify_return_to_base(msg_return_to)
150
+ end
151
+ end
152
+
153
+ def verify_return_to_args(msg_return_to)
154
+ return_to_parsed_query = {}
155
+ if !msg_return_to.query.nil?
156
+ CGI.parse(msg_return_to.query).each_pair do |k, vs|
157
+ return_to_parsed_query[k] = vs[0]
158
+ end
159
+ end
160
+ query = @message.to_post_args
161
+ return_to_parsed_query.each_pair do |rt_key, rt_val|
162
+ msg_val = query[rt_key]
163
+ if msg_val.nil?
164
+ raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
165
+ elsif msg_val != rt_val
166
+ raise ProtocolError, ("Parameter '#{rt_key}' value "\
167
+ "#{msg_val.inspect} does not match "\
168
+ "return_to's value #{rt_val.inspect}")
169
+ end
170
+ end
171
+ @message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
172
+ rt_val = return_to_parsed_query[bare_key]
173
+ if not return_to_parsed_query.has_key? bare_key
174
+ # This may be caused by your web framework throwing extra
175
+ # entries in to your parameters hash that were not GET or
176
+ # POST parameters. For example, Rails has been known to
177
+ # add "controller" and "action" keys; another server adds
178
+ # at least a "format" key.
179
+ raise ProtocolError, ("Unexpected parameter (not on return_to): "\
180
+ "'#{bare_key}'=#{rt_val.inspect})")
181
+ end
182
+ if rt_val != bare_val
183
+ raise ProtocolError, ("Parameter '#{bare_key}' value "\
184
+ "#{bare_val.inspect} does not match "\
185
+ "return_to's value #{rt_val.inspect}")
186
+ end
187
+ end
188
+ end
189
+
190
+ def verify_return_to_base(msg_return_to)
191
+ begin
192
+ app_parsed = URI.parse(URINorm::urinorm(@current_url))
193
+ rescue URI::InvalidURIError
194
+ raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
195
+ end
196
+
197
+ [:scheme, :host, :port, :path].each do |meth|
198
+ if msg_return_to.send(meth) != app_parsed.send(meth)
199
+ raise ProtocolError, "return_to #{meth.to_s} does not match"
200
+ end
201
+ end
202
+ end
203
+
204
+ # Raises ProtocolError if the signature is bad
205
+ def check_signature
206
+ if @store.nil?
207
+ assoc = nil
208
+ else
209
+ assoc = @store.get_association(server_url, fetch('assoc_handle'))
210
+ end
211
+
212
+ if assoc.nil?
213
+ check_auth
214
+ else
215
+ if assoc.expires_in <= 0
216
+ # XXX: It might be a good idea sometimes to re-start the
217
+ # authentication with a new association. Doing it
218
+ # automatically opens the possibility for
219
+ # denial-of-service by a server that just returns expired
220
+ # associations (or really short-lived associations)
221
+ raise ProtocolError, "Association with #{server_url} expired"
222
+ elsif !assoc.check_message_signature(@message)
223
+ raise ProtocolError, "Bad signature in response from #{server_url}"
224
+ end
225
+ end
226
+ end
227
+
228
+ def check_auth
229
+ Util.log("Using 'check_authentication' with #{server_url}")
230
+ begin
231
+ request = create_check_auth_request
232
+ rescue Message::KeyNotFound => why
233
+ raise ProtocolError, "Could not generate 'check_authentication' "\
234
+ "request: #{why.message}"
235
+ end
236
+
237
+ response = OpenID.make_kv_post(request, server_url)
238
+
239
+ process_check_auth_response(response)
240
+ end
241
+
242
+ def create_check_auth_request
243
+ signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')
244
+
245
+ # check that we got all the signed arguments
246
+ signed_list.each {|k|
247
+ @message.get_aliased_arg(k, NO_DEFAULT)
248
+ }
249
+
250
+ ca_message = @message.copy
251
+ ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')
252
+
253
+ return ca_message
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, OPENID11_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, OPENID11_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.claimed_id, [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.claimed_id, [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 ProtocolError, ("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
+ discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
412
+ end
413
+
414
+ # Given an endpoint object created from the information in an
415
+ # OpenID response, perform discovery and verify the discovery
416
+ # results, returning the matching endpoint that is the result of
417
+ # doing that discovery.
418
+ def discover_and_verify(claimed_id, to_match_endpoints)
419
+ Util.log("Performing discovery on #{claimed_id}")
420
+ _, services = OpenID.discover(claimed_id)
421
+ if services.length == 0
422
+ # XXX: this might want to be something other than
423
+ # ProtocolError. In Python, it's DiscoveryFailure
424
+ raise ProtocolError, ("No OpenID information found at "\
425
+ "#{claimed_id}")
426
+ end
427
+ verify_discovered_services(claimed_id, services, to_match_endpoints)
428
+ end
429
+
430
+
431
+ def verify_discovered_services(claimed_id, services, to_match_endpoints)
432
+ # Search the services resulting from discovery to find one
433
+ # that matches the information from the assertion
434
+ failure_messages = []
435
+ for endpoint in services
436
+ for to_match_endpoint in to_match_endpoints
437
+ begin
438
+ verify_discovery_single(endpoint, to_match_endpoint)
439
+ rescue ProtocolError => why
440
+ failure_messages << why.message
441
+ else
442
+ # It matches, so discover verification has
443
+ # succeeded. Return this endpoint.
444
+ @endpoint = endpoint
445
+ return
446
+ end
447
+ end
448
+ end
449
+
450
+ Util.log("Discovery verification failure for #{claimed_id}")
451
+ failure_messages.each do |failure_message|
452
+ Util.log(" * Endpoint mismatch: " + failure_message)
453
+ end
454
+
455
+ # XXX: is DiscoveryFailure in Python OpenID
456
+ raise ProtocolError, ("No matching endpoint found after "\
457
+ "discovering #{claimed_id}")
458
+ end
459
+
460
+ def verify_discovery_single(endpoint, to_match)
461
+ # Every type URI that's in the to_match endpoint has to be
462
+ # present in the discovered endpoint.
463
+ for type_uri in to_match.type_uris
464
+ if !endpoint.uses_extension(type_uri)
465
+ raise TypeURIMismatch.new(type_uri, endpoint)
466
+ end
467
+ end
468
+
469
+ # Fragments do not influence discovery, so we can't compare a
470
+ # claimed identifier with a fragment to discovered information.
471
+ defragged_claimed_id =
472
+ case Yadis::XRI.identifier_scheme(to_match.claimed_id)
473
+ when :xri
474
+ to_match.claimed_id
475
+ when :uri
476
+ begin
477
+ parsed = URI.parse(to_match.claimed_id)
478
+ rescue URI::InvalidURIError
479
+ to_match.claimed_id
480
+ else
481
+ parsed.fragment = nil
482
+ parsed.to_s
483
+ end
484
+ else
485
+ raise StandardError, 'Not reached'
486
+ end
487
+
488
+ if defragged_claimed_id != endpoint.claimed_id
489
+ raise ProtocolError, ("Claimed ID does not match (different "\
490
+ "subjects!), Expected "\
491
+ "#{defragged_claimed_id}, got "\
492
+ "#{endpoint.claimed_id}")
493
+ end
494
+
495
+ if to_match.claimed_id != endpoint.claimed_id
496
+ raise ProtocolError, ("claimed_id mismatch. Expected "\
497
+ "#{to_match.claimed_id}, got "\
498
+ "#{endpoint.claimed_id}")
499
+ end
500
+
501
+ # If the server URL is nil, this must be an OpenID 1
502
+ # response, because op_endpoint is a required parameter in
503
+ # OpenID 2. In that case, we don't actually care what the
504
+ # discovered server_url is, because signature checking or
505
+ # check_auth should take care of that check for us.
506
+ if to_match.server_url.nil?
507
+ if to_match.preferred_namespace != OPENID1_NS
508
+ raise StandardError,
509
+ "The code calling this must ensure that OpenID 2 "\
510
+ "responses have a non-none `openid.op_endpoint' and "\
511
+ "that it is set as the `server_url' attribute of the "\
512
+ "`to_match' endpoint."
513
+ end
514
+ elsif to_match.server_url != endpoint.server_url
515
+ raise ProtocolError, ("OP Endpoint mismatch. Expected "\
516
+ "#{to_match.server_url}, got "\
517
+ "#{endpoint.server_url}")
518
+ end
519
+ end
520
+
521
+ end
522
+ end
523
+ end