nov-ruby-openid 2.1.9

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