Capcode 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|