Capcode 0.8.4 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +58 -0
- data/doc/rdoc/classes/Capcode.html +938 -0
- data/doc/rdoc/classes/Capcode/Base.html +136 -0
- data/doc/rdoc/classes/Capcode/HTTPError.html +134 -0
- data/doc/rdoc/classes/Capcode/Helpers.html +608 -0
- data/doc/rdoc/classes/Capcode/Helpers/Authorization.html +188 -0
- data/doc/rdoc/classes/Capcode/Mab.html +118 -0
- data/doc/rdoc/classes/Capcode/Resource.html +111 -0
- data/doc/rdoc/classes/Capcode/Views.html +112 -0
- data/doc/rdoc/created.rid +1 -0
- data/doc/rdoc/files/AUTHORS.html +107 -0
- data/doc/rdoc/files/COPYING.html +531 -0
- data/doc/rdoc/files/README_rdoc.html +601 -0
- data/doc/rdoc/files/lib/capcode/base/db_rb.html +101 -0
- data/doc/rdoc/files/lib/capcode/helpers/auth_rb.html +132 -0
- data/doc/rdoc/files/lib/capcode/render/erb_rb.html +108 -0
- data/doc/rdoc/files/lib/capcode/render/haml_rb.html +108 -0
- data/doc/rdoc/files/lib/capcode/render/json_rb.html +108 -0
- data/doc/rdoc/files/lib/capcode/render/markaby_rb.html +108 -0
- data/doc/rdoc/files/lib/capcode/render/sass_rb.html +108 -0
- data/doc/rdoc/files/lib/capcode/render/static_rb.html +101 -0
- data/doc/rdoc/files/lib/capcode/render/text_rb.html +101 -0
- data/doc/rdoc/files/lib/capcode/render/webdav_rb.html +124 -0
- data/doc/rdoc/files/lib/capcode/render/xml_rb.html +101 -0
- data/doc/rdoc/files/lib/capcode_rb.html +119 -0
- data/doc/rdoc/fr_class_index.html +34 -0
- data/doc/rdoc/fr_file_index.html +41 -0
- data/doc/rdoc/fr_method_index.html +44 -0
- data/doc/rdoc/index.html +24 -0
- data/doc/rdoc/rdoc-style.css +208 -0
- data/examples/auth-basic.rb +46 -0
- data/examples/auth-digest.rb +47 -0
- data/examples/auth-webdav.rb +29 -0
- data/examples/blog-couchdb-run.rb +10 -0
- data/examples/blog-couchdb.rb +8 -8
- data/examples/blog-couchdb.ru +12 -0
- data/examples/render-static.rb +1 -1
- data/examples/render-static.ru +21 -0
- data/examples/render-webdav.rb +26 -0
- data/examples/rest-run.rb +3 -0
- data/examples/rest.rb +1 -1
- data/examples/rest.ru +3 -0
- data/lib/capcode.rb +196 -100
- data/lib/capcode/helpers/auth.rb +130 -0
- data/lib/capcode/render/webdav.rb +45 -0
- data/lib/capcode/version.rb +1 -1
- metadata +45 -3
data/examples/rest.rb
CHANGED
data/examples/rest.ru
ADDED
data/lib/capcode.rb
CHANGED
@@ -7,11 +7,13 @@ require 'irb'
|
|
7
7
|
require 'mime/types'
|
8
8
|
require 'capcode/version'
|
9
9
|
require 'capcode/core_ext'
|
10
|
+
require 'capcode/helpers/auth'
|
10
11
|
require 'capcode/render/text'
|
11
12
|
|
12
13
|
module Capcode
|
13
14
|
@@__ROUTES = {}
|
14
15
|
@@__STATIC_DIR = nil
|
16
|
+
@@__APP = nil
|
15
17
|
|
16
18
|
# @@__FILTERS = []
|
17
19
|
# def self.before_filter( opts, &b )
|
@@ -51,9 +53,13 @@ module Capcode
|
|
51
53
|
# * :json => MyObject : this suppose that's MyObject respond to .to_json
|
52
54
|
# * :static => "my_file.xxx" : this suppose that's my_file.xxx exist in the static directory
|
53
55
|
# * :xml => :my_func : :my_func must be defined in Capcode::Views
|
56
|
+
# * :webdav => /path/to/root
|
54
57
|
#
|
55
58
|
# If you want to use a specific layout, you can specify it with option
|
56
59
|
# :layout
|
60
|
+
#
|
61
|
+
# If you use the WebDav renderer, you can use the option
|
62
|
+
# :resource_class (see http://github.com/georgi/rack_dav for more informations)
|
57
63
|
def render( hash )
|
58
64
|
if hash.class == Hash
|
59
65
|
render_type = nil
|
@@ -167,7 +173,7 @@ module Capcode
|
|
167
173
|
path = klass
|
168
174
|
end
|
169
175
|
|
170
|
-
path+((a.size>0)?("/"+a.join("/")):(""))
|
176
|
+
(ENV['RACK_BASE_URI']||'')+path+((a.size>0)?("/"+a.join("/")):(""))
|
171
177
|
end
|
172
178
|
|
173
179
|
# Calling content_for stores a block of markup in an identifier.
|
@@ -218,6 +224,8 @@ module Capcode
|
|
218
224
|
:path => File.expand_path( File.join(".", Capcode.static ) )
|
219
225
|
}
|
220
226
|
end
|
227
|
+
|
228
|
+
include Authorization
|
221
229
|
end
|
222
230
|
|
223
231
|
include Rack
|
@@ -254,7 +262,7 @@ module Capcode
|
|
254
262
|
end
|
255
263
|
|
256
264
|
class << self
|
257
|
-
attr :
|
265
|
+
attr :__auth__, true
|
258
266
|
|
259
267
|
# Add routes to a controller class
|
260
268
|
#
|
@@ -348,55 +356,86 @@ module Capcode
|
|
348
356
|
# puts "call #{proc} for #{__k}"
|
349
357
|
# end
|
350
358
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
359
|
+
# Check authz
|
360
|
+
authz_options = nil
|
361
|
+
if Capcode.__auth__.size > 0
|
362
|
+
authz_options = Capcode.__auth__[@request.path]||nil
|
363
|
+
if authz_options.nil?
|
364
|
+
route = nil
|
356
365
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
if finalNArgs.nil? or finalNArgs > diffNArgs
|
364
|
-
finalPath = p
|
365
|
-
finalNArgs = diffNArgs
|
366
|
-
finalArgs = diffArgs
|
366
|
+
Capcode.__auth__.each do |r, o|
|
367
|
+
regexp = "^#{r.gsub(/\/$/, "")}([/]{1}.*)?$"
|
368
|
+
if Regexp.new(regexp).match( @request.path )
|
369
|
+
if route.nil? or r.size > route.size
|
370
|
+
route = r
|
371
|
+
authz_options = o
|
367
372
|
end
|
368
|
-
end
|
369
|
-
|
373
|
+
end
|
370
374
|
end
|
375
|
+
end
|
376
|
+
end
|
371
377
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
378
|
+
r = catch(:halt) {
|
379
|
+
unless authz_options.nil?
|
380
|
+
http_authentication( :type => authz_options[:type], :realm => authz_options[:realm], :opaque => authz_options[:realm] ) {
|
381
|
+
authz_options[:autz]
|
382
|
+
}
|
383
|
+
end
|
384
|
+
|
385
|
+
case @env["REQUEST_METHOD"]
|
386
|
+
when "GET"
|
387
|
+
finalPath = nil
|
388
|
+
finalArgs = nil
|
389
|
+
finalNArgs = nil
|
390
|
+
|
391
|
+
aPath = @request.path.gsub( /^\//, "" ).split( "/" )
|
392
|
+
self.class.__urls__[0].each do |p, r|
|
393
|
+
xPath = p.gsub( /^\//, "" ).split( "/" )
|
394
|
+
if (xPath - aPath).size == 0
|
395
|
+
diffArgs = aPath - xPath
|
396
|
+
diffNArgs = diffArgs.size
|
397
|
+
if finalNArgs.nil? or finalNArgs > diffNArgs
|
398
|
+
finalPath = p
|
399
|
+
finalNArgs = diffNArgs
|
400
|
+
finalArgs = diffArgs
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
nargs = self.class.__urls__[1]
|
407
|
+
regexp = Regexp.new( self.class.__urls__[0][finalPath] )
|
408
|
+
args = regexp.match( Rack::Utils.unescape(@request.path).gsub( Regexp.new( "^#{finalPath}" ), "" ).gsub( /^\//, "" ) )
|
409
|
+
if args.nil?
|
410
|
+
raise Capcode::ParameterError, "Path info `#{@request.path_info}' does not match route regexp `#{regexp.source}'"
|
411
|
+
else
|
412
|
+
args = args.captures.map { |x| (x.size == 0)?nil:x }
|
413
|
+
end
|
414
|
+
|
415
|
+
while args.size < nargs
|
416
|
+
args << nil
|
417
|
+
end
|
418
|
+
|
419
|
+
get( *args )
|
420
|
+
when "POST"
|
421
|
+
_method = params.delete( "_method" ) { |_| "post" }
|
422
|
+
send( _method.downcase.to_sym )
|
377
423
|
else
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
args << nil
|
383
|
-
end
|
384
|
-
|
385
|
-
get( *args )
|
386
|
-
when "POST"
|
387
|
-
_method = params.delete( "_method" ) { |_| "post" }
|
388
|
-
send( _method.downcase.to_sym )
|
389
|
-
end
|
424
|
+
_method = @env["REQUEST_METHOD"]
|
425
|
+
send( _method.downcase.to_sym )
|
426
|
+
end
|
427
|
+
}
|
390
428
|
if r.respond_to?(:to_ary)
|
391
|
-
@response.status = r[0]
|
392
|
-
r[1].each do |k,v|
|
429
|
+
@response.status = r.shift #r[0]
|
430
|
+
#r[1].each do |k,v|
|
431
|
+
r.shift.each do |k,v|
|
393
432
|
@response[k] = v
|
394
433
|
end
|
395
|
-
@response.body = r[2]
|
434
|
+
@response.body = r.shift #r[2]
|
396
435
|
else
|
397
436
|
@response.write r
|
398
437
|
end
|
399
|
-
|
438
|
+
|
400
439
|
@response.finish
|
401
440
|
end
|
402
441
|
|
@@ -413,26 +452,46 @@ module Capcode
|
|
413
452
|
def map( route, &b )
|
414
453
|
@@__ROUTES[route] = yield
|
415
454
|
end
|
416
|
-
|
417
|
-
#
|
455
|
+
|
456
|
+
# Allow you to add and HTTP Authentication (Basic or Digest) to controllers for or specific route
|
418
457
|
#
|
419
458
|
# Options :
|
420
|
-
# * <tt>:
|
421
|
-
# * <tt>:
|
422
|
-
# * <tt>:
|
423
|
-
# * <tt>:
|
424
|
-
#
|
425
|
-
#
|
426
|
-
#
|
427
|
-
#
|
428
|
-
#
|
429
|
-
#
|
430
|
-
|
431
|
-
|
459
|
+
# * <tt>:type</tt> : Authentication type (<tt>:basic</tt> or <tt>:digest</tt>) - default : <tt>:basic</tt>
|
460
|
+
# * <tt>:realm</tt> : realm ;) - default : "Capcode.app"
|
461
|
+
# * <tt>:opaque</tt> : Your secret passphrase. You MUST set it if you use Digest Auth - default : "opaque"
|
462
|
+
# * <tt>:routes</tt> : Routes - default : "/"
|
463
|
+
#
|
464
|
+
# The block must return a Hash of username => password like that :
|
465
|
+
# {
|
466
|
+
# "user1" => "pass1",
|
467
|
+
# "user2" => "pass2",
|
468
|
+
# # ...
|
469
|
+
# }
|
470
|
+
def http_authentication( opts = {}, &b )
|
471
|
+
options = {
|
472
|
+
:type => :basic,
|
473
|
+
:realm => "Capcode.app",
|
474
|
+
:opaque => "opaque",
|
475
|
+
:routes => "/"
|
476
|
+
}.merge( opts )
|
477
|
+
|
478
|
+
options[:autz] = b.call()
|
479
|
+
|
480
|
+
@__auth__ ||= {}
|
432
481
|
|
433
|
-
|
482
|
+
if options[:routes].class == Array
|
483
|
+
options[:routes].each do |r|
|
484
|
+
@__auth__[r] = options
|
485
|
+
end
|
486
|
+
else
|
487
|
+
@__auth__[options[:routes]] = options
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def configuration( args = {} ) #:nodoc:
|
492
|
+
{
|
434
493
|
:port => args[:port]||3000,
|
435
|
-
:host => args[:host]||"
|
494
|
+
:host => args[:host]||"0.0.0.0",
|
436
495
|
:server => args[:server]||nil,
|
437
496
|
:log => args[:log]||$stdout,
|
438
497
|
:session => args[:session]||{},
|
@@ -441,9 +500,82 @@ module Capcode
|
|
441
500
|
:db_config => File.expand_path(args[:db_config]||"database.yml"),
|
442
501
|
:static => args[:static]||nil,
|
443
502
|
:root => args[:root]||File.expand_path(File.dirname($0)),
|
444
|
-
|
503
|
+
:static => args[:static]||args[:root]||File.expand_path(File.dirname($0)),
|
504
|
+
:verbose => args[:verbose]||false,
|
445
505
|
:console => false
|
446
506
|
}
|
507
|
+
end
|
508
|
+
|
509
|
+
# Return the Rack App.
|
510
|
+
#
|
511
|
+
# Options : same has Capcode.run
|
512
|
+
def application( args = {} )
|
513
|
+
conf = configuration(args)
|
514
|
+
|
515
|
+
Capcode.constants.each do |k|
|
516
|
+
begin
|
517
|
+
if eval "Capcode::#{k}.public_methods(true).include?( '__urls__' )"
|
518
|
+
hash_of_routes, max_captures_for_routes, klass = eval "Capcode::#{k}.__urls__"
|
519
|
+
hash_of_routes.keys.each do |current_route_path|
|
520
|
+
raise Capcode::RouteError, "Route `#{current_route_path}' already define !", caller if @@__ROUTES.keys.include?(current_route_path)
|
521
|
+
@@__ROUTES[current_route_path] = klass.new
|
522
|
+
end
|
523
|
+
end
|
524
|
+
rescue => e
|
525
|
+
raise e.message
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Set Static directory
|
530
|
+
@@__STATIC_DIR = (conf[:static][0].chr == "/")?conf[:static]:"/"+conf[:static] unless conf[:static].nil?
|
531
|
+
|
532
|
+
# Initialize Rack App
|
533
|
+
puts "** Map routes." if conf[:verbose]
|
534
|
+
app = Rack::URLMap.new(@@__ROUTES)
|
535
|
+
puts "** Initialize static directory (#{conf[:static]})" if conf[:verbose]
|
536
|
+
app = Rack::Static.new(
|
537
|
+
app,
|
538
|
+
:urls => [@@__STATIC_DIR],
|
539
|
+
:root => File.expand_path(conf[:root])
|
540
|
+
) unless conf[:static].nil?
|
541
|
+
puts "** Initialize session" if conf[:verbose]
|
542
|
+
app = Rack::Session::Cookie.new( app, conf[:session] )
|
543
|
+
app = Capcode::HTTPError.new(app)
|
544
|
+
app = Rack::ContentLength.new(app)
|
545
|
+
app = Rack::Lint.new(app)
|
546
|
+
app = Rack::ShowExceptions.new(app)
|
547
|
+
#app = Rack::Reloader.new(app) ## -- NE RELOAD QUE capcode.rb -- So !!!
|
548
|
+
# app = Rack::CommonLogger.new( app, Logger.new(conf[:log]) )
|
549
|
+
|
550
|
+
# Start database
|
551
|
+
if self.methods.include? "db_connect"
|
552
|
+
db_connect( conf[:db_config], conf[:log] )
|
553
|
+
end
|
554
|
+
|
555
|
+
if block_given?
|
556
|
+
yield( self )
|
557
|
+
end
|
558
|
+
|
559
|
+
return app
|
560
|
+
end
|
561
|
+
|
562
|
+
# Start your application.
|
563
|
+
#
|
564
|
+
# Options :
|
565
|
+
# * <tt>:port</tt> = Listen port (default: 3000)
|
566
|
+
# * <tt>:host</tt> = Listen host (default: 0.0.0.0)
|
567
|
+
# * <tt>:server</tt> = Server type (webrick or mongrel)
|
568
|
+
# * <tt>:log</tt> = Output logfile (default: STDOUT)
|
569
|
+
# * <tt>:session</tt> = Session parameters. See Rack::Session for more informations
|
570
|
+
# * <tt>:pid</tt> = PID file (default: $0.pid)
|
571
|
+
# * <tt>:daemonize</tt> = Daemonize application (default: false)
|
572
|
+
# * <tt>:db_config</tt> = database configuration file (default: database.yml)
|
573
|
+
# * <tt>:static</tt> = Static directory (default: none -- relative to the working directory)
|
574
|
+
# * <tt>:root</tt> = Root directory (default: directory of the main.rb) -- This is also the working directory !
|
575
|
+
# * <tt>:verbose</tt> = run in verbose mode
|
576
|
+
# * <tt>:auth</tt> = HTTP Basic Authentication options
|
577
|
+
def run( args = {} )
|
578
|
+
conf = configuration(args)
|
447
579
|
|
448
580
|
# Parse options
|
449
581
|
opts = OptionParser.new do |opts|
|
@@ -482,7 +614,7 @@ module Capcode
|
|
482
614
|
exit
|
483
615
|
end
|
484
616
|
opts.on_tail( "-V", "--verbose", "Run in verbose mode" ) do
|
485
|
-
|
617
|
+
conf[:verbose] = true
|
486
618
|
end
|
487
619
|
end
|
488
620
|
|
@@ -495,7 +627,7 @@ module Capcode
|
|
495
627
|
end
|
496
628
|
|
497
629
|
# Run in the Working directory
|
498
|
-
puts "** Go on root directory (#{File.expand_path(conf[:root])})" if
|
630
|
+
puts "** Go on root directory (#{File.expand_path(conf[:root])})" if conf[:verbose]
|
499
631
|
Dir.chdir( conf[:root] ) do
|
500
632
|
|
501
633
|
# Check that mongrel exists
|
@@ -508,41 +640,6 @@ module Capcode
|
|
508
640
|
conf[:server] = "webrick"
|
509
641
|
end
|
510
642
|
end
|
511
|
-
|
512
|
-
Capcode.constants.each do |k|
|
513
|
-
begin
|
514
|
-
if eval "Capcode::#{k}.public_methods(true).include?( '__urls__' )"
|
515
|
-
hash_of_routes, max_captures_for_routes, klass = eval "Capcode::#{k}.__urls__"
|
516
|
-
hash_of_routes.keys.each do |current_route_path|
|
517
|
-
raise Capcode::RouteError, "Route `#{current_route_path}' already define !", caller if @@__ROUTES.keys.include?(current_route_path)
|
518
|
-
@@__ROUTES[current_route_path] = klass.new
|
519
|
-
end
|
520
|
-
end
|
521
|
-
rescue => e
|
522
|
-
raise e.message
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
# Set Static directory
|
527
|
-
@@__STATIC_DIR = (conf[:static][0].chr == "/")?conf[:static]:"/"+conf[:static] unless conf[:static].nil?
|
528
|
-
|
529
|
-
# Initialize Rack App
|
530
|
-
puts "** Map routes." if __VERBOSE
|
531
|
-
app = Rack::URLMap.new(@@__ROUTES)
|
532
|
-
puts "** Initialize static directory (#{conf[:static]})" if __VERBOSE
|
533
|
-
app = Rack::Static.new(
|
534
|
-
app,
|
535
|
-
:urls => [@@__STATIC_DIR],
|
536
|
-
:root => File.expand_path(conf[:root])
|
537
|
-
) unless conf[:static].nil?
|
538
|
-
puts "** Initialize session" if __VERBOSE
|
539
|
-
app = Rack::Session::Cookie.new( app, conf[:session] )
|
540
|
-
app = Capcode::HTTPError.new(app)
|
541
|
-
app = Rack::ContentLength.new(app)
|
542
|
-
app = Rack::Lint.new(app)
|
543
|
-
app = Rack::ShowExceptions.new(app)
|
544
|
-
# app = Rack::Reloader.new(app) ## -- NE RELOAD QUE capcode.rb -- So !!!
|
545
|
-
app = Rack::CommonLogger.new( app, Logger.new(conf[:log]) )
|
546
643
|
|
547
644
|
# From rackup !!!
|
548
645
|
if conf[:daemonize]
|
@@ -567,14 +664,13 @@ module Capcode
|
|
567
664
|
at_exit { File.delete(conf[:pid]) if File.exist?(conf[:pid]) }
|
568
665
|
end
|
569
666
|
|
570
|
-
|
571
|
-
if self.methods.include? "db_connect"
|
572
|
-
db_connect( conf[:db_config], conf[:log] )
|
573
|
-
end
|
574
|
-
|
667
|
+
app = nil
|
575
668
|
if block_given?
|
576
|
-
yield( self )
|
669
|
+
app = application(conf) { yield( self ) }
|
670
|
+
else
|
671
|
+
app = application(conf)
|
577
672
|
end
|
673
|
+
app = Rack::CommonLogger.new( app, Logger.new(conf[:log]) )
|
578
674
|
|
579
675
|
if conf[:console]
|
580
676
|
puts "Run console..."
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Because this helper was trully inspired by this post :
|
2
|
+
# http://www.gittr.com/index.php/archive/sinatra-basic-authentication-selectively-applied/
|
3
|
+
# and because the code in this post was extracted out of Wink, this file follow the
|
4
|
+
# Wink license :
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
module Capcode
|
25
|
+
module Helpers
|
26
|
+
module Authorization
|
27
|
+
def auth #:nodoc:
|
28
|
+
if @auth_type == :basic
|
29
|
+
@auth ||= Rack::Auth::Basic::Request.new(env)
|
30
|
+
else
|
31
|
+
@auth ||= Rack::Auth::Digest::Request.new(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def basic_unauthorized!(realm) #:nodoc:
|
36
|
+
response['WWW-Authenticate'] = %(Basic realm="#{realm}")
|
37
|
+
throw :halt, [ 401, {}, 'Authorization Required' ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def digest_unauthorized!(realm, opaque) #:nodoc:
|
41
|
+
response['WWW-Authenticate'] = %(Digest realm="#{realm}", qop="auth", nonce="#{Rack::Auth::Digest::Nonce.new.to_s}", opaque="#{H(opaque)}")
|
42
|
+
throw :halt, [ 401, {}, 'Authorization Required' ]
|
43
|
+
end
|
44
|
+
|
45
|
+
def H(data) #:nodoc:
|
46
|
+
::Digest::MD5.hexdigest(data)
|
47
|
+
end
|
48
|
+
|
49
|
+
def KD(secret, data) #:nodoc:
|
50
|
+
H([secret, data] * ':')
|
51
|
+
end
|
52
|
+
|
53
|
+
def A1(auth, password) #:nodoc:
|
54
|
+
[ auth.username, auth.realm, password ] * ':'
|
55
|
+
end
|
56
|
+
|
57
|
+
def A2(auth) #:nodoc:
|
58
|
+
[ auth.method, auth.uri ] * ':'
|
59
|
+
end
|
60
|
+
|
61
|
+
def digest(auth, password) #:nodoc:
|
62
|
+
password_hash = H(A1(auth, password))
|
63
|
+
|
64
|
+
KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, "auth", H(A2(auth)) ] * ':')
|
65
|
+
end
|
66
|
+
|
67
|
+
def digest_authorize( ) #:nodoc:
|
68
|
+
h = @authorizations.call( )
|
69
|
+
|
70
|
+
user = auth.username
|
71
|
+
pass = h[user]||false
|
72
|
+
|
73
|
+
(pass && (digest(auth, pass) == auth.response))
|
74
|
+
end
|
75
|
+
|
76
|
+
def basic_authorize(username, password) #:nodoc:
|
77
|
+
h = @authorizations.call( )
|
78
|
+
|
79
|
+
user = username
|
80
|
+
pass = h[user]||false
|
81
|
+
|
82
|
+
(pass == password)
|
83
|
+
end
|
84
|
+
|
85
|
+
def bad_request! #:nodoc:
|
86
|
+
throw :halt, [ 400, {}, 'Bad Request' ]
|
87
|
+
end
|
88
|
+
|
89
|
+
def authorized? #:nodoc:
|
90
|
+
request.env['REMOTE_USER']
|
91
|
+
end
|
92
|
+
|
93
|
+
# Allow you to add and HTTP Authentication (Basic or Digest) to a controller
|
94
|
+
#
|
95
|
+
# Options :
|
96
|
+
# * <tt>:type</tt> : Authentication type (<tt>:basic</tt> or <tt>:digest</tt>) - default : <tt>:basic</tt>
|
97
|
+
# * <tt>:realm</tt> : realm ;) - default : "Capcode.app"
|
98
|
+
# * <tt>:opaque</tt> : Your secret passphrase. You MUST set it if you use Digest Auth - default : "opaque"
|
99
|
+
#
|
100
|
+
# The block must return a Hash of username => password like that :
|
101
|
+
# {
|
102
|
+
# "user1" => "pass1",
|
103
|
+
# "user2" => "pass2",
|
104
|
+
# # ...
|
105
|
+
# }
|
106
|
+
def http_authentication( opts = {}, &b )
|
107
|
+
@auth = nil
|
108
|
+
|
109
|
+
@auth_type = opts[:type]||:basic
|
110
|
+
realm = opts[:realm]||"Capcode.app"
|
111
|
+
opaque = opts[:opaque]||"opaque"
|
112
|
+
@authorizations = b
|
113
|
+
|
114
|
+
return if authorized?
|
115
|
+
|
116
|
+
if @auth_type == :basic
|
117
|
+
basic_unauthorized!(realm) unless auth.provided?
|
118
|
+
bad_request! unless auth.basic?
|
119
|
+
basic_unauthorized!(realm) unless basic_authorize(*auth.credentials)
|
120
|
+
else
|
121
|
+
digest_unauthorized!(realm, opaque) unless auth.provided?
|
122
|
+
bad_request! unless auth.digest?
|
123
|
+
digest_unauthorized!(realm, opaque) unless digest_authorize
|
124
|
+
end
|
125
|
+
|
126
|
+
request.env['REMOTE_USER'] = auth.username
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|