require 'test/spec' require 'rack/auth/digest/md5' require 'rack/mock' context 'Rack::Auth::Digest::MD5' do def realm 'WallysWorld' end def unprotected_app lambda do |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] end end def protected_app app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username| { 'Alice' => 'correct-password' }[username] end app.realm = realm app.opaque = 'this-should-be-secret' app end def protected_app_with_hashed_passwords app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username| username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil end app.realm = realm app.opaque = 'this-should-be-secret' app.passwords_hashed = true app end setup do @request = Rack::MockRequest.new(protected_app) end def request(path, headers = {}, &block) response = @request.get(path, headers) block.call(response) if block return response end class MockDigestRequest def initialize(params) @params = params end def method_missing(sym) if @params.has_key? k = sym.to_s return @params[k] end super end def method 'GET' end def response(password) Rack::Auth::Digest::MD5.new(nil).send :digest, self, password end end def request_with_digest_auth(path, username, password, options = {}, &block) response = request('/') return response unless response.status == 401 if wait = options.delete(:wait) sleep wait end challenge = response['WWW-Authenticate'].split(' ', 2).last params = Rack::Auth::Digest::Params.parse(challenge) params['username'] = username params['nc'] = '00000001' params['cnonce'] = 'nonsensenonce' params['uri'] = path params.update options params['response'] = MockDigestRequest.new(params).response(password) request(path, { 'HTTP_AUTHORIZATION' => "Digest #{params}" }, &block) end def assert_digest_auth_challenge(response) response.should.be.a.client_error response.status.should.equal 401 response.should.include 'WWW-Authenticate' response.headers['WWW-Authenticate'].should =~ /^Digest / response.body.should.be.empty end def assert_bad_request(response) response.should.be.a.client_error response.status.should.equal 400 response.should.not.include 'WWW-Authenticate' end specify 'should challenge when no credentials are specified' do request '/' do |response| assert_digest_auth_challenge response end end specify 'should return application output if correct credentials given' do request_with_digest_auth '/', 'Alice', 'correct-password' do |response| response.status.should.equal 200 response.body.to_s.should.equal 'Hi Alice' end end specify 'should return application output if correct credentials given (hashed passwords)' do @request = Rack::MockRequest.new(protected_app_with_hashed_passwords) request_with_digest_auth '/', 'Alice', 'correct-password' do |response| response.status.should.equal 200 response.body.to_s.should.equal 'Hi Alice' end end specify 'should rechallenge if incorrect username given' do request_with_digest_auth '/', 'Bob', 'correct-password' do |response| assert_digest_auth_challenge response end end specify 'should rechallenge if incorrect password given' do request_with_digest_auth '/', 'Alice', 'wrong-password' do |response| assert_digest_auth_challenge response end end specify 'should rechallenge with stale parameter if nonce is stale' do begin Rack::Auth::Digest::Nonce.time_limit = 1 request_with_digest_auth '/', 'Alice', 'correct-password', :wait => 2 do |response| assert_digest_auth_challenge response response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/ end ensure Rack::Auth::Digest::Nonce.time_limit = nil end end specify 'should return 400 Bad Request if incorrect qop given' do request_with_digest_auth '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response| assert_bad_request response end end specify 'should return 400 Bad Request if incorrect uri given' do request_with_digest_auth '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response| assert_bad_request response end end specify 'should return 400 Bad Request if different auth scheme used' do request '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response| assert_bad_request response end end end