qoobaa-rack 1.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +18 -0
  3. data/RDOX +0 -0
  4. data/README +353 -0
  5. data/Rakefile +164 -0
  6. data/SPEC +164 -0
  7. data/bin/rackup +176 -0
  8. data/contrib/rack_logo.svg +111 -0
  9. data/example/lobster.ru +4 -0
  10. data/example/protectedlobster.rb +14 -0
  11. data/example/protectedlobster.ru +8 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +37 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +487 -0
  21. data/lib/rack/builder.rb +63 -0
  22. data/lib/rack/cascade.rb +41 -0
  23. data/lib/rack/chunked.rb +49 -0
  24. data/lib/rack/commonlogger.rb +52 -0
  25. data/lib/rack/conditionalget.rb +47 -0
  26. data/lib/rack/content_length.rb +29 -0
  27. data/lib/rack/content_type.rb +23 -0
  28. data/lib/rack/deflater.rb +96 -0
  29. data/lib/rack/directory.rb +153 -0
  30. data/lib/rack/file.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +88 -0
  34. data/lib/rack/handler/lsws.rb +60 -0
  35. data/lib/rack/handler/mongrel.rb +87 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +71 -0
  40. data/lib/rack/handler.rb +69 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +546 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +16 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +204 -0
  47. data/lib/rack/mock.rb +187 -0
  48. data/lib/rack/recursive.rb +57 -0
  49. data/lib/rack/reloader.rb +107 -0
  50. data/lib/rack/request.rb +248 -0
  51. data/lib/rack/response.rb +183 -0
  52. data/lib/rack/rewindable_input.rb +100 -0
  53. data/lib/rack/session/abstract/id.rb +142 -0
  54. data/lib/rack/session/cookie.rb +91 -0
  55. data/lib/rack/session/memcache.rb +109 -0
  56. data/lib/rack/session/pool.rb +100 -0
  57. data/lib/rack/showexceptions.rb +349 -0
  58. data/lib/rack/showstatus.rb +106 -0
  59. data/lib/rack/static.rb +38 -0
  60. data/lib/rack/urlmap.rb +55 -0
  61. data/lib/rack/utils.rb +528 -0
  62. data/lib/rack.rb +90 -0
  63. data/rack.gemspec +60 -0
  64. data/test/cgi/lighttpd.conf +20 -0
  65. data/test/cgi/test +9 -0
  66. data/test/cgi/test.fcgi +8 -0
  67. data/test/cgi/test.ru +7 -0
  68. data/test/multipart/binary +0 -0
  69. data/test/multipart/empty +10 -0
  70. data/test/multipart/file1.txt +1 -0
  71. data/test/multipart/ie +6 -0
  72. data/test/multipart/nested +10 -0
  73. data/test/multipart/none +9 -0
  74. data/test/multipart/text +10 -0
  75. data/test/spec_rack_auth_basic.rb +73 -0
  76. data/test/spec_rack_auth_digest.rb +226 -0
  77. data/test/spec_rack_auth_openid.rb +84 -0
  78. data/test/spec_rack_builder.rb +84 -0
  79. data/test/spec_rack_camping.rb +51 -0
  80. data/test/spec_rack_cascade.rb +48 -0
  81. data/test/spec_rack_cgi.rb +89 -0
  82. data/test/spec_rack_chunked.rb +62 -0
  83. data/test/spec_rack_commonlogger.rb +61 -0
  84. data/test/spec_rack_conditionalget.rb +41 -0
  85. data/test/spec_rack_content_length.rb +43 -0
  86. data/test/spec_rack_content_type.rb +30 -0
  87. data/test/spec_rack_deflater.rb +127 -0
  88. data/test/spec_rack_directory.rb +61 -0
  89. data/test/spec_rack_fastcgi.rb +89 -0
  90. data/test/spec_rack_file.rb +75 -0
  91. data/test/spec_rack_handler.rb +43 -0
  92. data/test/spec_rack_head.rb +30 -0
  93. data/test/spec_rack_lint.rb +521 -0
  94. data/test/spec_rack_lobster.rb +45 -0
  95. data/test/spec_rack_lock.rb +38 -0
  96. data/test/spec_rack_methodoverride.rb +60 -0
  97. data/test/spec_rack_mock.rb +243 -0
  98. data/test/spec_rack_mongrel.rb +189 -0
  99. data/test/spec_rack_recursive.rb +77 -0
  100. data/test/spec_rack_request.rb +504 -0
  101. data/test/spec_rack_response.rb +218 -0
  102. data/test/spec_rack_rewindable_input.rb +118 -0
  103. data/test/spec_rack_session_cookie.rb +82 -0
  104. data/test/spec_rack_session_memcache.rb +250 -0
  105. data/test/spec_rack_session_pool.rb +172 -0
  106. data/test/spec_rack_showexceptions.rb +21 -0
  107. data/test/spec_rack_showstatus.rb +72 -0
  108. data/test/spec_rack_static.rb +37 -0
  109. data/test/spec_rack_thin.rb +91 -0
  110. data/test/spec_rack_urlmap.rb +185 -0
  111. data/test/spec_rack_utils.rb +467 -0
  112. data/test/spec_rack_webrick.rb +130 -0
  113. data/test/testrequest.rb +57 -0
  114. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  115. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  116. metadata +276 -0
@@ -0,0 +1,487 @@
1
+ # AUTHOR: Scytrin dai Kinthra <scytrin@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
+
8
+ require 'uri'
9
+ require 'openid'
10
+ require 'openid/extension'
11
+ require 'openid/store/memory'
12
+
13
+ module Rack
14
+ class Request
15
+ def openid_request
16
+ @env['rack.auth.openid.request']
17
+ end
18
+
19
+ def openid_response
20
+ @env['rack.auth.openid.response']
21
+ end
22
+ end
23
+
24
+ module Auth
25
+
26
+ # Rack::Auth::OpenID provides a simple method for setting up an OpenID
27
+ # Consumer. It requires the ruby-openid library from janrain to operate,
28
+ # as well as a rack method of session management.
29
+ #
30
+ # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
31
+ #
32
+ # The OpenID specifications can be found at
33
+ # http://openid.net/specs/openid-authentication-1_1.html
34
+ # and
35
+ # http://openid.net/specs/openid-authentication-2_0.html. Documentation
36
+ # for published OpenID extensions and related topics can be found at
37
+ # http://openid.net/developers/specs/.
38
+ #
39
+ # It is recommended to read through the OpenID spec, as well as
40
+ # ruby-openid's documentation, to understand what exactly goes on. However
41
+ # a setup as simple as the presented examples is enough to provide
42
+ # Consumer functionality.
43
+ #
44
+ # This library strongly intends to utilize the OpenID 2.0 features of the
45
+ # ruby-openid library, which provides OpenID 1.0 compatiblity.
46
+ #
47
+ # NOTE: Due to the amount of data that this library stores in the
48
+ # session, Rack::Session::Cookie may fault.
49
+ #
50
+ # == Examples
51
+ #
52
+ # simple_oid = OpenID.new('http://mysite.com/')
53
+ #
54
+ # return_oid = OpenID.new('http://mysite.com/', {
55
+ # :return_to => 'http://mysite.com/openid'
56
+ # })
57
+ #
58
+ # complex_oid = OpenID.new('http://mysite.com/',
59
+ # :immediate => true,
60
+ # :extensions => {
61
+ # ::OpenID::SReg => [['email'],['nickname']]
62
+ # }
63
+ # )
64
+ #
65
+ # = Advanced
66
+ #
67
+ # Most of the functionality of this library is encapsulated such that
68
+ # expansion and overriding functions isn't difficult nor tricky.
69
+ # Alternately, to avoid opening up singleton objects or subclassing, a
70
+ # wrapper rack middleware can be composed to act upon Auth::OpenID's
71
+ # responses. See #check and #finish for locations of pertinent data.
72
+ #
73
+ # == Responses
74
+ #
75
+ # To change the responses that Auth::OpenID returns, override the methods
76
+ # #redirect, #bad_request, #unauthorized, #access_denied, and
77
+ # #foreign_server_failure.
78
+ #
79
+ # Additionally #confirm_post_params is used when the URI would exceed
80
+ # length limits on a GET request when doing the initial verification
81
+ # request.
82
+ #
83
+ # == Processing
84
+ #
85
+ # To change methods of processing completed transactions, override the
86
+ # methods #success, #setup_needed, #cancel, and #failure. Please ensure
87
+ # the returned object is a rack compatible response.
88
+ #
89
+ # The first argument is an OpenID::Response, the second is a
90
+ # Rack::Request of the current request, the last is the hash used in
91
+ # ruby-openid handling, which can be found manually at
92
+ # env['rack.session'][:openid].
93
+ #
94
+ # This is useful if you wanted to expand the processing done, such as
95
+ # setting up user accounts.
96
+ #
97
+ # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
98
+ # def oid_app.success oid, request, session
99
+ # user = Models::User[oid.identity_url]
100
+ # user ||= Models::User.create_from_openid oid
101
+ # request['rack.session'][:user] = user.id
102
+ # redirect MyApp.site_home
103
+ # end
104
+ #
105
+ # site_map['/openid'] = oid_app
106
+ # map = Rack::URLMap.new site_map
107
+ # ...
108
+
109
+ class OpenID
110
+ # Raised if an incompatible session is being used.
111
+ class NoSession < RuntimeError; end
112
+ # Raised if an extension not matching specifications is provided.
113
+ class BadExtension < RuntimeError; end
114
+ # Possible statuses returned from consumer responses. See definitions
115
+ # in the ruby-openid library.
116
+ ValidStatus = [
117
+ ::OpenID::Consumer::SUCCESS,
118
+ ::OpenID::Consumer::FAILURE,
119
+ ::OpenID::Consumer::CANCEL,
120
+ ::OpenID::Consumer::SETUP_NEEDED
121
+ ]
122
+
123
+ # The first argument is the realm, identifying the site they are trusting
124
+ # with their identity. This is required, also treated as the trust_root
125
+ # in OpenID 1.x exchanges.
126
+ #
127
+ # The lits of acceptable options include :return_to, :session_key,
128
+ # :openid_param, :store, :immediate, :extensions.
129
+ #
130
+ # <tt>:return_to</tt> defines the url to return to after the client
131
+ # authenticates with the openid service provider. This url should point
132
+ # to where Rack::Auth::OpenID is mounted. If unprovided, the url of
133
+ # the current request is used.
134
+ #
135
+ # <tt>:session_key</tt> defines the key to the session hash in the env.
136
+ # The default is 'rack.session'.
137
+ #
138
+ # <tt>:openid_param</tt> defines at what key in the request parameters to
139
+ # find the identifier to resolve. As per the 2.0 spec, the default is
140
+ # 'openid_identifier'.
141
+ #
142
+ # <tt>:store</tt> defined what OpenID Store to use for persistant
143
+ # information. By default a Store::Memory is used.
144
+ #
145
+ # <tt>:immediate</tt> as true will make initial requests to be of an
146
+ # immediate type. This is false by default. See OpenID specification
147
+ # documentation.
148
+ #
149
+ # <tt>:extensions</tt> should be a hash of openid extension
150
+ # implementations. The key should be the extension module, the value
151
+ # should be an array of arguments for extension::Request.new().
152
+ # The hash is iterated over and passed to #add_extension for processing.
153
+ # Please see #add_extension for further documentation.
154
+
155
+ def initialize(realm, options={})
156
+ realm = URI(realm)
157
+ raise ArgumentError, "Invalid realm: #{realm}" \
158
+ unless realm.absolute? \
159
+ and realm.fragment.nil? \
160
+ and realm.scheme =~ /^https?$/ \
161
+ and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
162
+ realm.path = '/' if realm.path.empty?
163
+ @realm = realm.to_s
164
+
165
+ if ruri = options[:return_to]
166
+ ruri = URI(ruri)
167
+ raise ArgumentError, "Invalid return_to: #{ruri}" \
168
+ unless ruri.absolute? \
169
+ and ruri.scheme =~ /^https?$/ \
170
+ and ruri.fragment.nil?
171
+ raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
172
+ unless self.within_realm?(ruri)
173
+ @return_to = ruri.to_s
174
+ end
175
+
176
+ @session_key = options[:session_key] || 'rack.session'
177
+ @openid_param = options[:openid_param] || 'openid_identifier'
178
+ @store = options[:store] || ::OpenID::Store::Memory.new
179
+ @immediate = !!options[:immediate]
180
+
181
+ @extensions = {}
182
+ if extensions = options[:extensions]
183
+ extensions.each do |ext, args|
184
+ add_extension(ext, *args)
185
+ end
186
+ end
187
+
188
+ # Undocumented, semi-experimental
189
+ @anonymous = !!options[:anonymous]
190
+ end
191
+
192
+ attr_reader :realm, :return_to, :session_key, :openid_param, :store,
193
+ :immediate, :extensions
194
+
195
+ # Sets up and uses session data at <tt>:openid</tt> within the session.
196
+ # Errors in this setup will raise a NoSession exception.
197
+ #
198
+ # If the parameter 'openid.mode' is set, which implies a followup from
199
+ # the openid server, processing is passed to #finish and the result is
200
+ # returned. However, if there is no appropriate openid information in the
201
+ # session, a 400 error is returned.
202
+ #
203
+ # If the parameter specified by <tt>options[:openid_param]</tt> is
204
+ # present, processing is passed to #check and the result is returned.
205
+ #
206
+ # If neither of these conditions are met, #bad_request is called.
207
+
208
+ def call(env)
209
+ env['rack.auth.openid'] = self
210
+ env_session = env[@session_key]
211
+ unless env_session and env_session.is_a?(Hash)
212
+ raise NoSession, 'No compatible session.'
213
+ end
214
+ # let us work in our own namespace...
215
+ session = (env_session[:openid] ||= {})
216
+ unless session and session.is_a?(Hash)
217
+ raise NoSession, 'Incompatible openid session.'
218
+ end
219
+
220
+ request = Rack::Request.new(env)
221
+ consumer = ::OpenID::Consumer.new(session, @store)
222
+
223
+ if mode = request.GET['openid.mode']
224
+ finish(consumer, session, request)
225
+ elsif request.GET[@openid_param]
226
+ check(consumer, session, request)
227
+ else
228
+ bad_request
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
+ redirect(oid.redirect_url(realm, return_to_uri, immediate))
267
+ else
268
+ confirm_post_params(oid, realm, return_to_uri, immediate)
269
+ end
270
+ rescue ::OpenID::DiscoveryFailure => e
271
+ # thrown from inside OpenID::Consumer#begin by yadis stuff
272
+ req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" )
273
+ return foreign_server_failure
274
+ end
275
+
276
+ # This is the final portion of authentication.
277
+ # If successful, a redirect to the realm is be returned.
278
+ # Data gathered from extensions are stored in session[:openid] with the
279
+ # extension's namespace uri as the key.
280
+ #
281
+ # Any messages from OpenID's response are logged to env['rack.errors']
282
+ #
283
+ # <tt>env['rack.auth.openid.response']</tt> will contain the openid
284
+ # response.
285
+
286
+ def finish(consumer, session, req)
287
+ oid = consumer.complete(req.GET, req.url)
288
+ req.env['rack.auth.openid.response'] = oid
289
+ req.env['rack.errors'].puts(oid.message)
290
+ p oid if $DEBUG
291
+
292
+ if ValidStatus.include?(oid.status)
293
+ __send__(oid.status, oid, req, session)
294
+ else
295
+ invalid_status(oid, req, session)
296
+ end
297
+ end
298
+
299
+ # The first argument should be the main extension module.
300
+ # The extension module should contain the constants:
301
+ # * class Request, should have OpenID::Extension as an ancestor
302
+ # * class Response, should have OpenID::Extension as an ancestor
303
+ # * string NS_URI, which defining the namespace of the extension
304
+ #
305
+ # All trailing arguments will be passed to extension::Request.new in
306
+ # #check.
307
+ # The openid response will be passed to
308
+ # extension::Response#from_success_response, oid#get_extension_args will
309
+ # be called on the result to attain the gathered data.
310
+ #
311
+ # This method returns the key at which the response data will be found in
312
+ # the session, which is the namespace uri by default.
313
+
314
+ def add_extension(ext, *args)
315
+ raise BadExtension unless valid_extension?(ext)
316
+ extensions[ext] = args
317
+ return ext::NS_URI
318
+ end
319
+
320
+ # Checks the validitity, in the context of usage, of a submitted
321
+ # extension.
322
+
323
+ def valid_extension?(ext)
324
+ if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
325
+ raise ArgumentError, 'Extension is missing constants.'
326
+ elsif not ext::Response.respond_to?(:from_success_response)
327
+ raise ArgumentError, 'Response is missing required method.'
328
+ end
329
+ return true
330
+ rescue
331
+ return false
332
+ end
333
+
334
+ # Checks the provided uri to ensure it'd be considered within the realm.
335
+ # is currently not compatible with wildcard realms.
336
+
337
+ def within_realm? uri
338
+ uri = URI.parse(uri.to_s)
339
+ realm = URI.parse(self.realm)
340
+ return false unless uri.absolute?
341
+ return false unless uri.path[0, realm.path.size] == realm.path
342
+ return false unless uri.host == realm.host or realm.host[/^\*\./]
343
+ # for wildcard support, is awkward with URI limitations
344
+ realm_match = Regexp.escape(realm.host).
345
+ sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
346
+ return false unless uri.host.match(realm_match)
347
+ return true
348
+ end
349
+
350
+ alias_method :include?, :within_realm?
351
+
352
+ protected
353
+
354
+ # Returns an html form page for posting to an Identity Provider if the
355
+ # GET request would exceed the upper URI length limit.
356
+
357
+ def confirm_post_params(oid, realm, return_to, immediate)
358
+ response = Rack::Response.new '<html>'+
359
+ '<head><title>Confirm...</title></head>'+
360
+ '<body>'+oid.form_markup(realm, return_to, immediate)+'</body>'+
361
+ '</html>'
362
+ response.finish
363
+ end
364
+
365
+ # Returns a 303 redirect with the destination of that provided by the
366
+ # argument.
367
+
368
+ def redirect(uri)
369
+ [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0',
370
+ 'Location' => uri},
371
+ [] ]
372
+ end
373
+
374
+ # Returns an empty 400 response.
375
+
376
+ def bad_request
377
+ [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
378
+ [''] ]
379
+ end
380
+
381
+ # Returns a basic unauthorized 401 response.
382
+
383
+ def unauthorized
384
+ [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
385
+ ['Unauthorized.'] ]
386
+ end
387
+
388
+ # Returns a basic access denied 403 response.
389
+
390
+ def access_denied
391
+ [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
392
+ ['Access denied.'] ]
393
+ end
394
+
395
+ # Returns a 503 response to be used if communication with the remote
396
+ # OpenID server fails.
397
+
398
+ def foreign_server_failure
399
+ [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
400
+ ['Foreign server failure.'] ]
401
+ end
402
+
403
+ private
404
+
405
+ # Called to complete processing on a successful transaction.
406
+ # Within the openid session, :openid_identity and :openid_identifier are
407
+ # set to the user friendly and the standard representation of the
408
+ # validated identity. All other data in the openid session is cleared.
409
+
410
+ def success(oid, request, session)
411
+ session.clear
412
+ session[:openid_identity] = oid.display_identifier
413
+ session[:openid_identifier] = oid.identity_url
414
+ extensions.keys.each do |ext|
415
+ label = ext.name[/[^:]+$/].downcase
416
+ response = ext::Response.from_success_response(oid)
417
+ session[label] = response.data
418
+ end
419
+ redirect(realm)
420
+ end
421
+
422
+ # Called if the Identity Provider indicates further setup by the user is
423
+ # required.
424
+ # The identifier is retrived from the openid session at :openid_param.
425
+ # And :setup_needed is set to true to prevent looping.
426
+
427
+ def setup_needed(oid, request, session)
428
+ identifier = session[:openid_param]
429
+ session[:setup_needed] = true
430
+ redirect(req.script_name + '?' + openid_param + '=' + identifier)
431
+ end
432
+
433
+ # Called if the user indicates they wish to cancel identification.
434
+ # Data within openid session is cleared.
435
+
436
+ def cancel(oid, request, session)
437
+ session.clear
438
+ access_denied
439
+ end
440
+
441
+ # Called if the Identity Provider indicates the user is unable to confirm
442
+ # their identity. Data within the openid session is left alone, in case
443
+ # of swarm auth attacks.
444
+
445
+ def failure(oid, request, session)
446
+ unauthorized
447
+ end
448
+
449
+ # To be called if there is no method for handling the OpenID response
450
+ # status.
451
+
452
+ def invalid_status(oid, request, session)
453
+ msg = 'Invalid status returned by the OpenID authorization reponse.'
454
+ [ 500,
455
+ {'Content-Type'=>'text/plain','Content-Length'=>msg.length.to_s},
456
+ [msg] ]
457
+ end
458
+ end
459
+
460
+ # A class developed out of the request to use OpenID as an authentication
461
+ # middleware. The request will be sent to the OpenID instance unless the
462
+ # block evaluates to true. For example in rackup, you can use it as such:
463
+ #
464
+ # use Rack::Session::Pool
465
+ # use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
466
+ # env['rack.session'][:authkey] == a_string
467
+ # end
468
+ # run RackApp
469
+ #
470
+ # Or simply:
471
+ #
472
+ # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
473
+
474
+ class OpenIDAuth < Rack::Auth::AbstractHandler
475
+ attr_reader :oid
476
+ def initialize(app, realm, options={}, &auth)
477
+ @oid = OpenID.new(realm, options)
478
+ super(app, &auth)
479
+ end
480
+
481
+ def call(env)
482
+ to = @authenticator.call(env) ? @app : @oid
483
+ to.call(env)
484
+ end
485
+ end
486
+ end
487
+ end
@@ -0,0 +1,63 @@
1
+ module Rack
2
+ # Rack::Builder implements a small DSL to iteratively construct Rack
3
+ # applications.
4
+ #
5
+ # Example:
6
+ #
7
+ # app = Rack::Builder.new {
8
+ # use Rack::CommonLogger
9
+ # use Rack::ShowExceptions
10
+ # map "/lobster" do
11
+ # use Rack::Lint
12
+ # run Rack::Lobster.new
13
+ # end
14
+ # }
15
+ #
16
+ # Or
17
+ #
18
+ # app = Rack::Builder.app do
19
+ # use Rack::CommonLogger
20
+ # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
21
+ # end
22
+ #
23
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
24
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
25
+
26
+ class Builder
27
+ def initialize(&block)
28
+ @ins = []
29
+ instance_eval(&block) if block_given?
30
+ end
31
+
32
+ def self.app(&block)
33
+ self.new(&block).to_app
34
+ end
35
+
36
+ def use(middleware, *args, &block)
37
+ @ins << lambda { |app| middleware.new(app, *args, &block) }
38
+ end
39
+
40
+ def run(app)
41
+ @ins << app #lambda { |nothing| app }
42
+ end
43
+
44
+ def map(path, &block)
45
+ if @ins.last.kind_of? Hash
46
+ @ins.last[path] = self.class.new(&block).to_app
47
+ else
48
+ @ins << {}
49
+ map(path, &block)
50
+ end
51
+ end
52
+
53
+ def to_app
54
+ @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
55
+ inner_app = @ins.last
56
+ @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
57
+ end
58
+
59
+ def call(env)
60
+ to_app.call(env)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,41 @@
1
+ module Rack
2
+ # Rack::Cascade tries an request on several apps, and returns the
3
+ # first response that is not 404 (or in a list of configurable
4
+ # status codes).
5
+
6
+ class Cascade
7
+ NotFound = [404, {}, []]
8
+
9
+ attr_reader :apps
10
+
11
+ def initialize(apps, catch=404)
12
+ @apps = []; @has_app = {}
13
+ apps.each { |app| add app }
14
+
15
+ @catch = {}
16
+ [*catch].each { |status| @catch[status] = true }
17
+ end
18
+
19
+ def call(env)
20
+ result = NotFound
21
+
22
+ @apps.each do |app|
23
+ result = app.call(env)
24
+ break unless @catch.include?(result[0].to_i)
25
+ end
26
+
27
+ result
28
+ end
29
+
30
+ def add app
31
+ @has_app[app] = true
32
+ @apps << app
33
+ end
34
+
35
+ def include? app
36
+ @has_app.include? app
37
+ end
38
+
39
+ alias_method :<<, :add
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+
5
+ # Middleware that applies chunked transfer encoding to response bodies
6
+ # when the response does not include a Content-Length header.
7
+ class Chunked
8
+ include Rack::Utils
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ status, headers, body = @app.call(env)
16
+ headers = HeaderHash.new(headers)
17
+
18
+ if env['HTTP_VERSION'] == 'HTTP/1.0' ||
19
+ STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
20
+ headers['Content-Length'] ||
21
+ headers['Transfer-Encoding']
22
+ [status, headers.to_hash, body]
23
+ else
24
+ dup.chunk(status, headers, body)
25
+ end
26
+ end
27
+
28
+ def chunk(status, headers, body)
29
+ @body = body
30
+ headers.delete('Content-Length')
31
+ headers['Transfer-Encoding'] = 'chunked'
32
+ [status, headers.to_hash, self]
33
+ end
34
+
35
+ def each
36
+ term = "\r\n"
37
+ @body.each do |chunk|
38
+ size = bytesize(chunk)
39
+ next if size == 0
40
+ yield [size.to_s(16), term, chunk, term].join
41
+ end
42
+ yield ["0", term, "", term].join
43
+ end
44
+
45
+ def close
46
+ @body.close if @body.respond_to?(:close)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,52 @@
1
+ module Rack
2
+ # Rack::CommonLogger forwards every request to an +app+ given, and
3
+ # logs a line in the Apache common log format to the +logger+, or
4
+ # rack.errors by default.
5
+ class CommonLogger
6
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
7
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
8
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
9
+ FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
10
+
11
+ def initialize(app, logger=nil)
12
+ @app = app
13
+ @logger = logger
14
+ end
15
+
16
+ def call(env)
17
+ began_at = Time.now
18
+ status, header, body = @app.call(env)
19
+ log(env, status, header, began_at)
20
+ [status, header, body]
21
+ end
22
+
23
+ private
24
+
25
+ def log(env, status, header, began_at)
26
+ now = Time.now
27
+ length = extract_content_length(header)
28
+
29
+ logger = @logger || env['rack.errors']
30
+ logger.write FORMAT % [
31
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
32
+ env["REMOTE_USER"] || "-",
33
+ now.strftime("%d/%b/%Y %H:%M:%S"),
34
+ env["REQUEST_METHOD"],
35
+ env["PATH_INFO"],
36
+ env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
37
+ env["HTTP_VERSION"],
38
+ status.to_s[0..3],
39
+ length,
40
+ now - began_at ]
41
+ end
42
+
43
+ def extract_content_length(headers)
44
+ headers.each do |key, value|
45
+ if key.downcase == 'content-length'
46
+ return value.to_s == '0' ? '-' : value
47
+ end
48
+ end
49
+ '-'
50
+ end
51
+ end
52
+ end