actionpack 2.2.3 → 2.3.2

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

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (264) hide show
  1. data/CHANGELOG +433 -375
  2. data/MIT-LICENSE +1 -1
  3. data/README +21 -75
  4. data/Rakefile +1 -1
  5. data/lib/action_controller.rb +80 -43
  6. data/lib/action_controller/assertions/model_assertions.rb +1 -0
  7. data/lib/action_controller/assertions/response_assertions.rb +43 -16
  8. data/lib/action_controller/assertions/routing_assertions.rb +1 -1
  9. data/lib/action_controller/assertions/selector_assertions.rb +17 -12
  10. data/lib/action_controller/assertions/tag_assertions.rb +1 -4
  11. data/lib/action_controller/base.rb +153 -82
  12. data/lib/action_controller/benchmarking.rb +9 -9
  13. data/lib/action_controller/caching.rb +9 -11
  14. data/lib/action_controller/caching/actions.rb +11 -18
  15. data/lib/action_controller/caching/fragments.rb +28 -20
  16. data/lib/action_controller/caching/pages.rb +13 -15
  17. data/lib/action_controller/caching/sweeping.rb +2 -2
  18. data/lib/action_controller/cgi_ext.rb +0 -1
  19. data/lib/action_controller/cgi_ext/cookie.rb +2 -0
  20. data/lib/action_controller/cgi_process.rb +54 -162
  21. data/lib/action_controller/cookies.rb +13 -25
  22. data/lib/action_controller/dispatcher.rb +43 -122
  23. data/lib/action_controller/failsafe.rb +52 -0
  24. data/lib/action_controller/flash.rb +38 -47
  25. data/lib/action_controller/helpers.rb +13 -9
  26. data/lib/action_controller/http_authentication.rb +203 -23
  27. data/lib/action_controller/integration.rb +126 -70
  28. data/lib/action_controller/layout.rb +36 -39
  29. data/lib/action_controller/middleware_stack.rb +119 -0
  30. data/lib/action_controller/middlewares.rb +13 -0
  31. data/lib/action_controller/mime_responds.rb +19 -4
  32. data/lib/action_controller/mime_type.rb +8 -0
  33. data/lib/action_controller/params_parser.rb +71 -0
  34. data/lib/action_controller/performance_test.rb +0 -1
  35. data/lib/action_controller/polymorphic_routes.rb +36 -30
  36. data/lib/action_controller/reloader.rb +14 -0
  37. data/lib/action_controller/request.rb +107 -499
  38. data/lib/action_controller/request_forgery_protection.rb +7 -39
  39. data/lib/action_controller/rescue.rb +55 -35
  40. data/lib/action_controller/resources.rb +34 -31
  41. data/lib/action_controller/response.rb +99 -57
  42. data/lib/action_controller/rewindable_input.rb +28 -0
  43. data/lib/action_controller/routing.rb +7 -7
  44. data/lib/action_controller/routing/builder.rb +4 -1
  45. data/lib/action_controller/routing/optimisations.rb +1 -1
  46. data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
  47. data/lib/action_controller/routing/route.rb +15 -5
  48. data/lib/action_controller/routing/route_set.rb +82 -35
  49. data/lib/action_controller/routing/segments.rb +35 -0
  50. data/lib/action_controller/session/abstract_store.rb +181 -0
  51. data/lib/action_controller/session/cookie_store.rb +197 -175
  52. data/lib/action_controller/session/mem_cache_store.rb +36 -83
  53. data/lib/action_controller/session_management.rb +26 -134
  54. data/lib/action_controller/streaming.rb +24 -7
  55. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  56. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  57. data/lib/action_controller/test_case.rb +87 -30
  58. data/lib/action_controller/test_process.rb +145 -104
  59. data/lib/action_controller/uploaded_file.rb +44 -0
  60. data/lib/action_controller/url_rewriter.rb +3 -6
  61. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  62. data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
  63. data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
  64. data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
  65. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
  66. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
  67. data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
  68. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
  69. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
  70. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
  71. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
  72. data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
  73. data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
  74. data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
  75. data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
  76. data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
  77. data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
  78. data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
  79. data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
  80. data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
  81. data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
  82. data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
  83. data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
  84. data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
  85. data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
  86. data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
  87. data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
  88. data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
  89. data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
  90. data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
  91. data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
  92. data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
  93. data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
  94. data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
  95. data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
  96. data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
  97. data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
  98. data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
  99. data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
  100. data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
  101. data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
  102. data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
  103. data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
  104. data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
  105. data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
  106. data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
  107. data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
  108. data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
  109. data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
  110. data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
  111. data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
  112. data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
  113. data/lib/action_controller/verification.rb +1 -1
  114. data/lib/action_pack.rb +1 -1
  115. data/lib/action_pack/version.rb +2 -2
  116. data/lib/action_view.rb +22 -17
  117. data/lib/action_view/base.rb +53 -79
  118. data/lib/action_view/erb/util.rb +38 -0
  119. data/lib/action_view/helpers.rb +24 -5
  120. data/lib/action_view/helpers/active_record_helper.rb +2 -2
  121. data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
  122. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  123. data/lib/action_view/helpers/benchmark_helper.rb +26 -5
  124. data/lib/action_view/helpers/date_helper.rb +82 -7
  125. data/lib/action_view/helpers/form_helper.rb +295 -64
  126. data/lib/action_view/helpers/form_options_helper.rb +160 -18
  127. data/lib/action_view/helpers/form_tag_helper.rb +2 -2
  128. data/lib/action_view/helpers/number_helper.rb +31 -18
  129. data/lib/action_view/helpers/prototype_helper.rb +2 -12
  130. data/lib/action_view/helpers/sanitize_helper.rb +0 -10
  131. data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
  132. data/lib/action_view/helpers/tag_helper.rb +3 -4
  133. data/lib/action_view/helpers/text_helper.rb +99 -122
  134. data/lib/action_view/helpers/translation_helper.rb +19 -1
  135. data/lib/action_view/helpers/url_helper.rb +25 -2
  136. data/lib/action_view/inline_template.rb +1 -1
  137. data/lib/action_view/locale/en.yml +19 -1
  138. data/lib/action_view/partials.rb +46 -9
  139. data/lib/action_view/paths.rb +28 -84
  140. data/lib/action_view/reloadable_template.rb +117 -0
  141. data/lib/action_view/renderable.rb +28 -35
  142. data/lib/action_view/renderable_partial.rb +3 -4
  143. data/lib/action_view/template.rb +172 -31
  144. data/lib/action_view/template_error.rb +8 -9
  145. data/lib/action_view/template_handler.rb +1 -1
  146. data/lib/action_view/template_handlers.rb +9 -6
  147. data/lib/action_view/template_handlers/erb.rb +2 -39
  148. data/lib/action_view/template_handlers/rjs.rb +1 -0
  149. data/lib/action_view/test_case.rb +27 -1
  150. data/test/abstract_unit.rb +23 -17
  151. data/test/active_record_unit.rb +5 -4
  152. data/test/activerecord/active_record_store_test.rb +139 -106
  153. data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
  154. data/test/controller/action_pack_assertions_test.rb +25 -23
  155. data/test/controller/addresses_render_test.rb +3 -6
  156. data/test/controller/assert_select_test.rb +83 -70
  157. data/test/controller/base_test.rb +11 -13
  158. data/test/controller/benchmark_test.rb +3 -3
  159. data/test/controller/caching_test.rb +34 -24
  160. data/test/controller/capture_test.rb +3 -6
  161. data/test/controller/content_type_test.rb +3 -6
  162. data/test/controller/cookie_test.rb +31 -66
  163. data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
  164. data/test/controller/dispatcher_test.rb +23 -28
  165. data/test/controller/fake_models.rb +8 -0
  166. data/test/controller/filters_test.rb +6 -2
  167. data/test/controller/flash_test.rb +2 -6
  168. data/test/controller/helper_test.rb +15 -1
  169. data/test/controller/html-scanner/document_test.rb +1 -1
  170. data/test/controller/html-scanner/sanitizer_test.rb +1 -1
  171. data/test/controller/http_basic_authentication_test.rb +88 -0
  172. data/test/controller/http_digest_authentication_test.rb +178 -0
  173. data/test/controller/integration_test.rb +56 -52
  174. data/test/controller/layout_test.rb +46 -44
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +7 -11
  177. data/test/controller/mime_type_test.rb +9 -0
  178. data/test/controller/polymorphic_routes_test.rb +235 -151
  179. data/test/controller/rack_test.rb +52 -81
  180. data/test/controller/redirect_test.rb +6 -14
  181. data/test/controller/render_test.rb +273 -60
  182. data/test/controller/request/json_params_parsing_test.rb +45 -0
  183. data/test/controller/request/multipart_params_parsing_test.rb +223 -0
  184. data/test/controller/request/query_string_parsing_test.rb +120 -0
  185. data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
  186. data/test/controller/request/xml_params_parsing_test.rb +88 -0
  187. data/test/controller/request_forgery_protection_test.rb +17 -98
  188. data/test/controller/request_test.rb +45 -530
  189. data/test/controller/rescue_test.rb +45 -22
  190. data/test/controller/resources_test.rb +112 -37
  191. data/test/controller/routing_test.rb +1442 -1384
  192. data/test/controller/selector_test.rb +3 -3
  193. data/test/controller/send_file_test.rb +30 -3
  194. data/test/controller/session/cookie_store_test.rb +169 -240
  195. data/test/controller/session/mem_cache_store_test.rb +94 -148
  196. data/test/controller/session/test_session_test.rb +58 -0
  197. data/test/controller/test_test.rb +32 -13
  198. data/test/controller/url_rewriter_test.rb +54 -4
  199. data/test/controller/verification_test.rb +1 -1
  200. data/test/controller/view_paths_test.rb +15 -15
  201. data/test/controller/webservice_test.rb +178 -147
  202. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  203. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  204. data/test/fixtures/layouts/default_html.html.erb +1 -0
  205. data/test/fixtures/layouts/xhr.html.erb +2 -0
  206. data/test/fixtures/multipart/empty +10 -0
  207. data/test/fixtures/multipart/hello.txt +1 -0
  208. data/test/fixtures/multipart/none +9 -0
  209. data/test/fixtures/public/500.da.html +1 -0
  210. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  211. data/test/fixtures/replies.yml +1 -1
  212. data/test/fixtures/test/_one.html.erb +1 -0
  213. data/test/fixtures/test/_two.html.erb +1 -0
  214. data/test/fixtures/test/dont_pick_me +1 -0
  215. data/test/fixtures/test/hello.builder +1 -1
  216. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  217. data/test/fixtures/test/hello_world.erb~ +1 -0
  218. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  219. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  220. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  221. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  222. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  223. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  224. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  225. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  226. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  227. data/test/fixtures/test/utf8.html.erb +2 -0
  228. data/test/template/active_record_helper_i18n_test.rb +31 -33
  229. data/test/template/active_record_helper_test.rb +34 -0
  230. data/test/template/asset_tag_helper_test.rb +52 -14
  231. data/test/template/atom_feed_helper_test.rb +3 -5
  232. data/test/template/benchmark_helper_test.rb +50 -24
  233. data/test/template/compiled_templates_test.rb +177 -33
  234. data/test/template/date_helper_i18n_test.rb +88 -81
  235. data/test/template/date_helper_test.rb +427 -43
  236. data/test/template/form_helper_test.rb +243 -44
  237. data/test/template/form_options_helper_test.rb +631 -565
  238. data/test/template/form_tag_helper_test.rb +9 -2
  239. data/test/template/javascript_helper_test.rb +0 -5
  240. data/test/template/number_helper_i18n_test.rb +60 -48
  241. data/test/template/number_helper_test.rb +1 -0
  242. data/test/template/render_test.rb +117 -35
  243. data/test/template/test_test.rb +4 -6
  244. data/test/template/text_helper_test.rb +129 -50
  245. data/test/template/translation_helper_test.rb +23 -19
  246. data/test/template/url_helper_test.rb +35 -2
  247. data/test/view/test_case_test.rb +8 -0
  248. metadata +197 -23
  249. data/lib/action_controller/assertions.rb +0 -69
  250. data/lib/action_controller/caching/sql_cache.rb +0 -18
  251. data/lib/action_controller/cgi_ext/session.rb +0 -53
  252. data/lib/action_controller/components.rb +0 -169
  253. data/lib/action_controller/rack_process.rb +0 -297
  254. data/lib/action_controller/request_profiler.rb +0 -169
  255. data/lib/action_controller/session/active_record_store.rb +0 -340
  256. data/lib/action_controller/session/drb_server.rb +0 -32
  257. data/lib/action_controller/session/drb_store.rb +0 -35
  258. data/test/controller/cgi_test.rb +0 -269
  259. data/test/controller/components_test.rb +0 -156
  260. data/test/controller/http_authentication_test.rb +0 -54
  261. data/test/controller/integration_upload_test.rb +0 -43
  262. data/test/controller/session_fixation_test.rb +0 -89
  263. data/test/controller/session_management_test.rb +0 -178
  264. data/test/fixtures/test/hello_world.js +0 -1
@@ -0,0 +1,51 @@
1
+ require 'digest/md5'
2
+
3
+ module Rack
4
+ module Auth
5
+ module Digest
6
+ # Rack::Auth::Digest::Nonce is the default nonce generator for the
7
+ # Rack::Auth::Digest::MD5 authentication handler.
8
+ #
9
+ # +private_key+ needs to set to a constant string.
10
+ #
11
+ # +time_limit+ can be optionally set to an integer (number of seconds),
12
+ # to limit the validity of the generated nonces.
13
+
14
+ class Nonce
15
+
16
+ class << self
17
+ attr_accessor :private_key, :time_limit
18
+ end
19
+
20
+ def self.parse(string)
21
+ new(*string.unpack("m*").first.split(' ', 2))
22
+ end
23
+
24
+ def initialize(timestamp = Time.now, given_digest = nil)
25
+ @timestamp, @given_digest = timestamp.to_i, given_digest
26
+ end
27
+
28
+ def to_s
29
+ [([ @timestamp, digest ] * ' ')].pack("m*").strip
30
+ end
31
+
32
+ def digest
33
+ ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':')
34
+ end
35
+
36
+ def valid?
37
+ digest == @given_digest
38
+ end
39
+
40
+ def stale?
41
+ !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
42
+ end
43
+
44
+ def fresh?
45
+ !stale?
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ module Rack
2
+ module Auth
3
+ module Digest
4
+ class Params < Hash
5
+
6
+ def self.parse(str)
7
+ split_header_value(str).inject(new) do |header, param|
8
+ k, v = param.split('=', 2)
9
+ header[k] = dequote(v)
10
+ header
11
+ end
12
+ end
13
+
14
+ def self.dequote(str) # From WEBrick::HTTPUtils
15
+ ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
16
+ ret.gsub!(/\\(.)/, "\\1")
17
+ ret
18
+ end
19
+
20
+ def self.split_header_value(str)
21
+ str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
22
+ end
23
+
24
+ def initialize
25
+ super
26
+
27
+ yield self if block_given?
28
+ end
29
+
30
+ def [](k)
31
+ super k.to_s
32
+ end
33
+
34
+ def []=(k, v)
35
+ super k.to_s, v.to_s
36
+ end
37
+
38
+ UNQUOTED = ['qop', 'nc', 'stale']
39
+
40
+ def to_s
41
+ inject([]) do |parts, (k, v)|
42
+ parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
43
+ parts
44
+ end.join(', ')
45
+ end
46
+
47
+ def quote(str) # From WEBrick::HTTPUtils
48
+ '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,40 @@
1
+ require 'rack/auth/abstract/request'
2
+ require 'rack/auth/digest/params'
3
+ require 'rack/auth/digest/nonce'
4
+
5
+ module Rack
6
+ module Auth
7
+ module Digest
8
+ class Request < Auth::AbstractRequest
9
+
10
+ def method
11
+ @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
12
+ end
13
+
14
+ def digest?
15
+ :digest == scheme
16
+ end
17
+
18
+ def correct_uri?
19
+ (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri
20
+ end
21
+
22
+ def nonce
23
+ @nonce ||= Nonce.parse(params['nonce'])
24
+ end
25
+
26
+ def params
27
+ @params ||= Params.parse(parts.last)
28
+ end
29
+
30
+ def method_missing(sym)
31
+ if params.has_key? key = sym.to_s
32
+ return params[key]
33
+ end
34
+ super
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,480 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+
3
+ gem 'ruby-openid', '~> 2' if defined? Gem
4
+ require 'rack/request'
5
+ require 'rack/utils'
6
+ require 'rack/auth/abstract/handler'
7
+ require 'uri'
8
+ require 'openid' #gem
9
+ require 'openid/extension' #gem
10
+ require 'openid/store/memory' #gem
11
+
12
+ module Rack
13
+ class Request
14
+ def openid_request
15
+ @env['rack.auth.openid.request']
16
+ end
17
+
18
+ def openid_response
19
+ @env['rack.auth.openid.response']
20
+ end
21
+ end
22
+
23
+ module Auth
24
+
25
+ # Rack::Auth::OpenID provides a simple method for setting up an OpenID
26
+ # Consumer. It requires the ruby-openid library from janrain to operate,
27
+ # as well as a rack method of session management.
28
+ #
29
+ # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
30
+ #
31
+ # The OpenID specifications can be found at
32
+ # http://openid.net/specs/openid-authentication-1_1.html
33
+ # and
34
+ # http://openid.net/specs/openid-authentication-2_0.html. Documentation
35
+ # for published OpenID extensions and related topics can be found at
36
+ # http://openid.net/developers/specs/.
37
+ #
38
+ # It is recommended to read through the OpenID spec, as well as
39
+ # ruby-openid's documentation, to understand what exactly goes on. However
40
+ # a setup as simple as the presented examples is enough to provide
41
+ # Consumer functionality.
42
+ #
43
+ # This library strongly intends to utilize the OpenID 2.0 features of the
44
+ # ruby-openid library, which provides OpenID 1.0 compatiblity.
45
+ #
46
+ # NOTE: Due to the amount of data that this library stores in the
47
+ # session, Rack::Session::Cookie may fault.
48
+
49
+ class OpenID
50
+
51
+ class NoSession < RuntimeError; end
52
+ class BadExtension < RuntimeError; end
53
+ # Required for ruby-openid
54
+ ValidStatus = [:success, :setup_needed, :cancel, :failure]
55
+
56
+ # = Arguments
57
+ #
58
+ # The first argument is the realm, identifying the site they are trusting
59
+ # with their identity. This is required, also treated as the trust_root
60
+ # in OpenID 1.x exchanges.
61
+ #
62
+ # The optional second argument is a hash of options.
63
+ #
64
+ # == Options
65
+ #
66
+ # <tt>:return_to</tt> defines the url to return to after the client
67
+ # authenticates with the openid service provider. This url should point
68
+ # to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
69
+ # provided, return_to will be the current url which allows flexibility
70
+ # with caveats.
71
+ #
72
+ # <tt>:session_key</tt> defines the key to the session hash in the env.
73
+ # It defaults to 'rack.session'.
74
+ #
75
+ # <tt>:openid_param</tt> defines at what key in the request parameters to
76
+ # find the identifier to resolve. As per the 2.0 spec, the default is
77
+ # 'openid_identifier'.
78
+ #
79
+ # <tt>:store</tt> defined what OpenID Store to use for persistant
80
+ # information. By default a Store::Memory will be used.
81
+ #
82
+ # <tt>:immediate</tt> as true will make initial requests to be of an
83
+ # immediate type. This is false by default. See OpenID specification
84
+ # documentation.
85
+ #
86
+ # <tt>:extensions</tt> should be a hash of openid extension
87
+ # implementations. The key should be the extension main module, the value
88
+ # should be an array of arguments for extension::Request.new.
89
+ # The hash is iterated over and passed to #add_extension for processing.
90
+ # Please see #add_extension for further documentation.
91
+ #
92
+ # == Examples
93
+ #
94
+ # simple_oid = OpenID.new('http://mysite.com/')
95
+ #
96
+ # return_oid = OpenID.new('http://mysite.com/', {
97
+ # :return_to => 'http://mysite.com/openid'
98
+ # })
99
+ #
100
+ # complex_oid = OpenID.new('http://mysite.com/',
101
+ # :immediate => true,
102
+ # :extensions => {
103
+ # ::OpenID::SReg => [['email'],['nickname']]
104
+ # }
105
+ # )
106
+ #
107
+ # = Advanced
108
+ #
109
+ # Most of the functionality of this library is encapsulated such that
110
+ # expansion and overriding functions isn't difficult nor tricky.
111
+ # Alternately, to avoid opening up singleton objects or subclassing, a
112
+ # wrapper rack middleware can be composed to act upon Auth::OpenID's
113
+ # responses. See #check and #finish for locations of pertinent data.
114
+ #
115
+ # == Responses
116
+ #
117
+ # To change the responses that Auth::OpenID returns, override the methods
118
+ # #redirect, #bad_request, #unauthorized, #access_denied, and
119
+ # #foreign_server_failure.
120
+ #
121
+ # Additionally #confirm_post_params is used when the URI would exceed
122
+ # length limits on a GET request when doing the initial verification
123
+ # request.
124
+ #
125
+ # == Processing
126
+ #
127
+ # To change methods of processing completed transactions, override the
128
+ # methods #success, #setup_needed, #cancel, and #failure. Please ensure
129
+ # the returned object is a rack compatible response.
130
+ #
131
+ # The first argument is an OpenID::Response, the second is a
132
+ # Rack::Request of the current request, the last is the hash used in
133
+ # ruby-openid handling, which can be found manually at
134
+ # env['rack.session'][:openid].
135
+ #
136
+ # This is useful if you wanted to expand the processing done, such as
137
+ # setting up user accounts.
138
+ #
139
+ # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
140
+ # def oid_app.success oid, request, session
141
+ # user = Models::User[oid.identity_url]
142
+ # user ||= Models::User.create_from_openid oid
143
+ # request['rack.session'][:user] = user.id
144
+ # redirect MyApp.site_home
145
+ # end
146
+ #
147
+ # site_map['/openid'] = oid_app
148
+ # map = Rack::URLMap.new site_map
149
+ # ...
150
+
151
+ def initialize(realm, options={})
152
+ realm = URI(realm)
153
+ raise ArgumentError, "Invalid realm: #{realm}" \
154
+ unless realm.absolute? \
155
+ and realm.fragment.nil? \
156
+ and realm.scheme =~ /^https?$/ \
157
+ and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
158
+ realm.path = '/' if realm.path.empty?
159
+ @realm = realm.to_s
160
+
161
+ if ruri = options[:return_to]
162
+ ruri = URI(ruri)
163
+ raise ArgumentError, "Invalid return_to: #{ruri}" \
164
+ unless ruri.absolute? \
165
+ and ruri.scheme =~ /^https?$/ \
166
+ and ruri.fragment.nil?
167
+ raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
168
+ unless self.within_realm?(ruri)
169
+ @return_to = ruri.to_s
170
+ end
171
+
172
+ @session_key = options[:session_key] || 'rack.session'
173
+ @openid_param = options[:openid_param] || 'openid_identifier'
174
+ @store = options[:store] || ::OpenID::Store::Memory.new
175
+ @immediate = !!options[:immediate]
176
+
177
+ @extensions = {}
178
+ if extensions = options.delete(:extensions)
179
+ extensions.each do |ext, args|
180
+ add_extension ext, *args
181
+ end
182
+ end
183
+
184
+ # Undocumented, semi-experimental
185
+ @anonymous = !!options[:anonymous]
186
+ end
187
+
188
+ attr_reader :realm, :return_to, :session_key, :openid_param, :store,
189
+ :immediate, :extensions
190
+
191
+ # Sets up and uses session data at <tt>:openid</tt> within the session.
192
+ # Errors in this setup will raise a NoSession exception.
193
+ #
194
+ # If the parameter 'openid.mode' is set, which implies a followup from
195
+ # the openid server, processing is passed to #finish and the result is
196
+ # returned. However, if there is no appropriate openid information in the
197
+ # session, a 400 error is returned.
198
+ #
199
+ # If the parameter specified by <tt>options[:openid_param]</tt> is
200
+ # present, processing is passed to #check and the result is returned.
201
+ #
202
+ # If neither of these conditions are met, #unauthorized is called.
203
+
204
+ def call(env)
205
+ env['rack.auth.openid'] = self
206
+ env_session = env[@session_key]
207
+ unless env_session and env_session.is_a?(Hash)
208
+ raise NoSession, 'No compatible session'
209
+ end
210
+ # let us work in our own namespace...
211
+ session = (env_session[:openid] ||= {})
212
+ unless session and session.is_a?(Hash)
213
+ raise NoSession, 'Incompatible openid session'
214
+ end
215
+
216
+ request = Rack::Request.new(env)
217
+ consumer = ::OpenID::Consumer.new(session, @store)
218
+
219
+ if mode = request.GET['openid.mode']
220
+ if session.key?(:openid_param)
221
+ finish(consumer, session, request)
222
+ else
223
+ bad_request
224
+ end
225
+ elsif request.GET[@openid_param]
226
+ check(consumer, session, request)
227
+ else
228
+ unauthorized
229
+ end
230
+ end
231
+
232
+ # As the first part of OpenID consumer action, #check retrieves the data
233
+ # required for completion.
234
+ #
235
+ # If all parameters fit within the max length of a URI, a 303 redirect
236
+ # will be returned. Otherwise #confirm_post_params will be called.
237
+ #
238
+ # Any messages from OpenID's request are logged to env['rack.errors']
239
+ #
240
+ # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
241
+ # instance.
242
+ #
243
+ # <tt>session[:openid_param]</tt> is set to the openid identifier
244
+ # provided by the user.
245
+ #
246
+ # <tt>session[:return_to]</tt> is set to the return_to uri given to the
247
+ # identity provider.
248
+
249
+ def check(consumer, session, req)
250
+ oid = consumer.begin(req.GET[@openid_param], @anonymous)
251
+ req.env['rack.auth.openid.request'] = oid
252
+ req.env['rack.errors'].puts(oid.message)
253
+ p oid if $DEBUG
254
+
255
+ ## Extension support
256
+ extensions.each do |ext,args|
257
+ oid.add_extension(ext::Request.new(*args))
258
+ end
259
+
260
+ session[:openid_param] = req.GET[openid_param]
261
+ return_to_uri = return_to ? return_to : req.url
262
+ session[:return_to] = return_to_uri
263
+ immediate = session.key?(:setup_needed) ? false : immediate
264
+
265
+ if oid.send_redirect?(realm, return_to_uri, immediate)
266
+ uri = oid.redirect_url(realm, return_to_uri, immediate)
267
+ redirect(uri)
268
+ else
269
+ confirm_post_params(oid, realm, return_to_uri, immediate)
270
+ end
271
+ rescue ::OpenID::DiscoveryFailure => e
272
+ # thrown from inside OpenID::Consumer#begin by yadis stuff
273
+ req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n")
274
+ return foreign_server_failure
275
+ end
276
+
277
+ # This is the final portion of authentication.
278
+ # If successful, a redirect to the realm is be returned.
279
+ # Data gathered from extensions are stored in session[:openid] with the
280
+ # extension's namespace uri as the key.
281
+ #
282
+ # Any messages from OpenID's response are logged to env['rack.errors']
283
+ #
284
+ # <tt>env['rack.auth.openid.response']</tt> will contain the openid
285
+ # response.
286
+
287
+ def finish(consumer, session, req)
288
+ oid = consumer.complete(req.GET, req.url)
289
+ req.env['rack.auth.openid.response'] = oid
290
+ req.env['rack.errors'].puts(oid.message)
291
+ p oid if $DEBUG
292
+
293
+ raise unless ValidStatus.include?(oid.status)
294
+ __send__(oid.status, oid, req, session)
295
+ end
296
+
297
+ # The first argument should be the main extension module.
298
+ # The extension module should contain the constants:
299
+ # * class Request, should have OpenID::Extension as an ancestor
300
+ # * class Response, should have OpenID::Extension as an ancestor
301
+ # * string NS_URI, which defining the namespace of the extension
302
+ #
303
+ # All trailing arguments will be passed to extension::Request.new in
304
+ # #check.
305
+ # The openid response will be passed to
306
+ # extension::Response#from_success_response, #get_extension_args will be
307
+ # called on the result to attain the gathered data.
308
+ #
309
+ # This method returns the key at which the response data will be found in
310
+ # the session, which is the namespace uri by default.
311
+
312
+ def add_extension(ext, *args)
313
+ raise BadExtension unless valid_extension?(ext)
314
+ extensions[ext] = args
315
+ return ext::NS_URI
316
+ end
317
+
318
+ # Checks the validitity, in the context of usage, of a submitted
319
+ # extension.
320
+
321
+ def valid_extension?(ext)
322
+ if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
323
+ raise ArgumentError, 'Extension is missing constants.'
324
+ elsif not ext::Response.respond_to?(:from_success_response)
325
+ raise ArgumentError, 'Response is missing required method.'
326
+ end
327
+ return true
328
+ rescue
329
+ return false
330
+ end
331
+
332
+ # Checks the provided uri to ensure it'd be considered within the realm.
333
+ # is currently not compatible with wildcard realms.
334
+
335
+ def within_realm? uri
336
+ uri = URI.parse(uri.to_s)
337
+ realm = URI.parse(self.realm)
338
+ return false unless uri.absolute?
339
+ return false unless uri.path[0, realm.path.size] == realm.path
340
+ return false unless uri.host == realm.host or realm.host[/^\*\./]
341
+ # for wildcard support, is awkward with URI limitations
342
+ realm_match = Regexp.escape(realm.host).
343
+ sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
344
+ return false unless uri.host.match(realm_match)
345
+ return true
346
+ end
347
+ alias_method :include?, :within_realm?
348
+
349
+ protected
350
+
351
+ ### These methods define some of the boilerplate responses.
352
+
353
+ # Returns an html form page for posting to an Identity Provider if the
354
+ # GET request would exceed the upper URI length limit.
355
+
356
+ def confirm_post_params(oid, realm, return_to, immediate)
357
+ Rack::Response.new.finish do |r|
358
+ r.write '<html><head><title>Confirm...</title></head><body>'
359
+ r.write oid.form_markup(realm, return_to, immediate)
360
+ r.write '</body></html>'
361
+ end
362
+ end
363
+
364
+ # Returns a 303 redirect with the destination of that provided by the
365
+ # argument.
366
+
367
+ def redirect(uri)
368
+ [ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
369
+ 'Location' => uri},
370
+ [] ]
371
+ end
372
+
373
+ # Returns an empty 400 response.
374
+
375
+ def bad_request
376
+ [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
377
+ [''] ]
378
+ end
379
+
380
+ # Returns a basic unauthorized 401 response.
381
+
382
+ def unauthorized
383
+ [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
384
+ ['Unauthorized.'] ]
385
+ end
386
+
387
+ # Returns a basic access denied 403 response.
388
+
389
+ def access_denied
390
+ [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
391
+ ['Access denied.'] ]
392
+ end
393
+
394
+ # Returns a 503 response to be used if communication with the remote
395
+ # OpenID server fails.
396
+
397
+ def foreign_server_failure
398
+ [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
399
+ ['Foreign server failure.'] ]
400
+ end
401
+
402
+ private
403
+
404
+ ### These methods are called after a transaction is completed, depending
405
+ # on its outcome. These should all return a rack compatible response.
406
+ # You'd want to override these to provide additional functionality.
407
+
408
+ # Called to complete processing on a successful transaction.
409
+ # Within the openid session, :openid_identity and :openid_identifier are
410
+ # set to the user friendly and the standard representation of the
411
+ # validated identity. All other data in the openid session is cleared.
412
+
413
+ def success(oid, request, session)
414
+ session.clear
415
+ session[:openid_identity] = oid.display_identifier
416
+ session[:openid_identifier] = oid.identity_url
417
+ extensions.keys.each do |ext|
418
+ label = ext.name[/[^:]+$/].downcase
419
+ response = ext::Response.from_success_response(oid)
420
+ session[label] = response.data
421
+ end
422
+ redirect(realm)
423
+ end
424
+
425
+ # Called if the Identity Provider indicates further setup by the user is
426
+ # required.
427
+ # The identifier is retrived from the openid session at :openid_param.
428
+ # And :setup_needed is set to true to prevent looping.
429
+
430
+ def setup_needed(oid, request, session)
431
+ identifier = session[:openid_param]
432
+ session[:setup_needed] = true
433
+ redirect req.script_name + '?' + openid_param + '=' + identifier
434
+ end
435
+
436
+ # Called if the user indicates they wish to cancel identification.
437
+ # Data within openid session is cleared.
438
+
439
+ def cancel(oid, request, session)
440
+ session.clear
441
+ access_denied
442
+ end
443
+
444
+ # Called if the Identity Provider indicates the user is unable to confirm
445
+ # their identity. Data within the openid session is left alone, in case
446
+ # of swarm auth attacks.
447
+
448
+ def failure(oid, request, session)
449
+ unauthorized
450
+ end
451
+ end
452
+
453
+ # A class developed out of the request to use OpenID as an authentication
454
+ # middleware. The request will be sent to the OpenID instance unless the
455
+ # block evaluates to true. For example in rackup, you can use it as such:
456
+ #
457
+ # use Rack::Session::Pool
458
+ # use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
459
+ # env['rack.session'][:authkey] == a_string
460
+ # end
461
+ # run RackApp
462
+ #
463
+ # Or simply:
464
+ #
465
+ # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
466
+
467
+ class OpenIDAuth < Rack::Auth::AbstractHandler
468
+ attr_reader :oid
469
+ def initialize(app, realm, options={}, &auth)
470
+ @oid = OpenID.new(realm, options)
471
+ super(app, &auth)
472
+ end
473
+
474
+ def call(env)
475
+ to = auth.call(env) ? @app : @oid
476
+ to.call env
477
+ end
478
+ end
479
+ end
480
+ end