diff --git a/lib/grape.rb b/lib/grape.rb index 98018d878..2a7d82d27 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -17,6 +17,7 @@ module Middleware module Auth autoload :OAuth2, 'grape/middleware/auth/oauth2' autoload :Basic, 'grape/middleware/auth/basic' + autoload :Digest, 'grape/middleware/auth/digest' end end end diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 6367f7469..f513cd393 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,5 +1,6 @@ require 'rack/mount' require 'rack/auth/basic' +require 'rack/auth/digest/md5' require 'logger' module Grape @@ -138,7 +139,7 @@ def helpers(&block) end # Add an authentication type to the API. Currently - # only `:http_basic` and `:oauth2` are supported. + # only `:http_basic`, `:http_digest` and `:oauth2` are supported. def auth(type = nil, options = {}, &block) if type set(:auth, {:type => type.to_sym, :proc => block}.merge(options)) @@ -155,6 +156,12 @@ def http_basic(options = {}, &block) options[:realm] ||= "API Authorization" auth :http_basic, options, &block end + + def http_digest(options = {}, &block) + options[:realm] ||= "API Authorization" + options[:opaque] ||= "secret" + auth :http_digest, options, &block + end # Defines a route that will be recognized # by the Grape API. @@ -257,6 +264,7 @@ def build_endpoint(&block) :format => settings[:error_format] || :txt, :rescue_options => settings[:rescue_options] b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic + b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version b.use Grape::Middleware::Formatter, :default_format => default_format || :json diff --git a/lib/grape/middleware/auth/digest.rb b/lib/grape/middleware/auth/digest.rb new file mode 100644 index 000000000..b3e22083c --- /dev/null +++ b/lib/grape/middleware/auth/digest.rb @@ -0,0 +1,30 @@ +require 'rack/auth/digest/md5' + +module Grape + module Middleware + module Auth + class Digest < Grape::Middleware::Base + attr_reader :authenticator + + def initialize(app, options = {}, &authenticator) + super(app, options) + @authenticator = authenticator + end + + def digest_request + Rack::Auth::Digest::Request.new(env) + end + + def credentials + digest_request.provided?? digest_request.credentials : [nil, nil] + end + + def before + unless authenticator.call(*credentials) + throw :error, :status => 401, :message => "API Authorization Failed." + end + end + end + end + end +end diff --git a/spec/grape/middleware/auth/digest_spec.rb b/spec/grape/middleware/auth/digest_spec.rb new file mode 100644 index 000000000..a6b206c3e --- /dev/null +++ b/spec/grape/middleware/auth/digest_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +RSpec::Matchers.define :be_challenge do + match do |actual_response| + actual_response.status == 401 && + actual_response['WWW-Authenticate'] =~ /^Digest / && + actual_response.body.empty? + end +end + +class Test < Grape::API + version '1' + + http_digest({:realm => 'Test Api', :opaque => 'secret'}) do |username| + {'foo' => 'bar'}[username] + end + + get '/test' do + [{:hey => 'you'},{:there => 'bar'},{:foo => 'baz'}] + end +end + +describe Grape::Middleware::Auth::Digest do + def app + Test + end + + it 'should be a digest authentication challenge' do + get '/1/test' + last_response.should be_challenge + end + + it 'should throw a 401 if no auth is given' do + get '/1/test' + last_response.status.should == 401 + end + + it 'should authenticate if given valid creds' do + digest_authorize "foo", "bar" + get '/1/test' + last_response.status.should == 200 + end + + it 'should throw a 401 if given invalid creds' do + digest_authorize "bar", "foo" + get '/1/test' + last_response.status.should == 401 + end +end