diff options
| author | Azul <azul@leap.se> | 2012-11-12 19:16:55 +0100 | 
|---|---|---|
| committer | Azul <azul@leap.se> | 2012-11-12 19:16:55 +0100 | 
| commit | c886cc17b6f37ddd556e70fe2d76a3ea28db52bf (patch) | |
| tree | e27fe040bfaba5840730f466d4c6f90213759d5e | |
| parent | ca2e1b9f379ccba068ad0ebb852d855f1639cd3a (diff) | |
| parent | 5b300b554682c232c0955bdb0dd3d8263dde901e (diff) | |
Merge branch 'feature-warden-srp' into develop
| -rw-r--r-- | Gemfile.lock | 5 | ||||
| -rw-r--r-- | core/lib/extensions/testing.rb | 13 | ||||
| -rw-r--r-- | help/test/functional/tickets_controller_test.rb | 27 | ||||
| -rw-r--r-- | users/app/controllers/controller_extension/authentication.rb | 4 | ||||
| -rw-r--r-- | users/app/controllers/sessions_controller.rb | 18 | ||||
| -rw-r--r-- | users/app/views/sessions/new.json.erb | 3 | ||||
| -rw-r--r-- | users/config/initializers/warden.rb | 7 | ||||
| -rw-r--r-- | users/leap_web_users.gemspec | 1 | ||||
| -rw-r--r-- | users/lib/leap_web_users/engine.rb | 4 | ||||
| -rw-r--r-- | users/lib/warden/session_serializer.rb | 13 | ||||
| -rw-r--r-- | users/lib/warden/strategies/secure_remote_password.rb | 57 | ||||
| -rw-r--r-- | users/test/functional/application_controller_test.rb | 7 | ||||
| -rw-r--r-- | users/test/functional/helper_methods_test.rb | 15 | ||||
| -rw-r--r-- | users/test/functional/sessions_controller_test.rb | 84 | ||||
| -rw-r--r-- | users/test/integration/api/account_flow_test.rb | 32 | ||||
| -rw-r--r-- | users/test/support/auth_test_helper.rb | 21 | ||||
| -rw-r--r-- | users/test/unit/warden_strategy_secure_remote_password_test.rb | 61 | 
17 files changed, 265 insertions, 107 deletions
| diff --git a/Gemfile.lock b/Gemfile.lock index 6792476..01a2291 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,6 +25,7 @@ PATH    specs:      leap_web_users (0.1.0)        leap_web_core (= 0.1.0) +      rails_warden        ruby-srp (~> 0.1.4)  GEM @@ -125,6 +126,8 @@ GEM        activesupport (= 3.2.8)        bundler (~> 1.0)        railties (= 3.2.8) +    rails_warden (0.5.7) +      warden (>= 1.0.0)      railties (3.2.8)        actionpack (= 3.2.8)        activesupport (= 3.2.8) @@ -167,6 +170,8 @@ GEM      uglifier (1.2.7)        execjs (>= 0.3.0)        multi_json (~> 1.3) +    warden (1.2.1) +      rack (>= 1.0)  PLATFORMS    ruby diff --git a/core/lib/extensions/testing.rb b/core/lib/extensions/testing.rb index 14a5698..86a059f 100644 --- a/core/lib/extensions/testing.rb +++ b/core/lib/extensions/testing.rb @@ -1,15 +1,22 @@  module LeapWebCore    module AssertResponses +    # response that works with different TestCases: +    # ActionController::TestCase has @response +    # ActionDispatch::IntegrationTest has @response +    # Rack::Test::Methods defines last_response +    def get_response +      @response || last_response +    end +      def assert_attachement_filename(name)        assert_equal %Q(attachment; filename="#{name}"), -        @response.headers["Content-Disposition"] +        get_response.headers["Content-Disposition"]      end -      def assert_json_response(object)        object.stringify_keys! if object.respond_to? :stringify_keys! -      assert_equal object, JSON.parse(@response.body) +      assert_equal object, JSON.parse(get_response.body)      end    end diff --git a/help/test/functional/tickets_controller_test.rb b/help/test/functional/tickets_controller_test.rb index 7a03a86..6bdb6c7 100644 --- a/help/test/functional/tickets_controller_test.rb +++ b/help/test/functional/tickets_controller_test.rb @@ -21,13 +21,13 @@ class TicketsControllerTest < ActionController::TestCase      assert_difference('Ticket.count') do        post :create, :ticket => params      end -     +      assert_response :redirect      #assert_equal assigns(:ticket).email, User.current.email      #assert_equal User.find(assigns(:ticket).created_by).login, User.current.login      assert_nil assigns(:ticket).created_by -    assert_equal assigns(:ticket).comments.count, 1 +    assert_equal 1, assigns(:ticket).comments.count    end @@ -35,28 +35,29 @@ class TicketsControllerTest < ActionController::TestCase      params = {:title => "ticket test title", :comments_attributes => {"0" => {"body" =>"body of test ticket"}}} -    #todo: should redo this and actually authorize -    user = User.last -    session[:user_id] = user.id +    login User.last      assert_difference('Ticket.count') do        post :create, :ticket => params      end      assert_response :redirect -    assert_equal assigns(:ticket).created_by, user.id -    assert_equal assigns(:ticket).email, user.email +    ticket = assigns(:ticket) +    assert ticket +    assert_equal @current_user.id, ticket.created_by +    assert_equal @current_user.email, ticket.email -    assert_equal assigns(:ticket).comments.count, 1 +    assert_equal 1, assigns(:ticket).comments.count    end    test "add comment to ticket" do -    t = Ticket.last -    comment_count = t.comments.count -    put :update, :id => t.id, :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } -    assert_equal(comment_count + 1, assigns(:ticket).comments.count) -    #assert_difference block isn't working +    ticket = Ticket.last +    assert_difference('Ticket.last.comments.count') do +      put :update, :id => ticket.id, +        :ticket => {:comments_attributes => {"0" => {"body" =>"NEWER comment"}} } +    end +    assert_equal ticket, assigns(:ticket)    end diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb index c3342f3..87f7921 100644 --- a/users/app/controllers/controller_extension/authentication.rb +++ b/users/app/controllers/controller_extension/authentication.rb @@ -7,8 +7,8 @@ module ControllerExtension::Authentication      helper_method :current_user, :logged_in?, :admin?    end -  def current_user -    @current_user ||= User.find(session[:user_id]) if session[:user_id] +  def authentication_error +    warden.winning_strategy.try(:message)    end    def logged_in? diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb index 4a1107d..486f67e 100644 --- a/users/app/controllers/sessions_controller.rb +++ b/users/app/controllers/sessions_controller.rb @@ -3,28 +3,20 @@ class SessionsController < ApplicationController    skip_before_filter :verify_authenticity_token    def new +    @errors = authentication_error    end    def create -    @user = User.find_by_param(params[:login]) -    session[:handshake] = @user.initialize_auth(params['A'].hex) -    render :json => session[:handshake] -  rescue RECORD_NOT_FOUND -    render :json => {:errors => {:login => ["unknown user"]}} +    authenticate!    end    def update -    @srp_session = session.delete(:handshake) -    @user = @srp_session.authenticate!(params[:client_auth].hex) -    session[:user_id] = @user.id -    render :json => @srp_session -  rescue WRONG_PASSWORD -    session[:handshake] = nil -    render :json => {:errors => {"password" => ["wrong password"]}} +    authenticate! +    render :json => session.delete(:handshake)    end    def destroy -    session[:user_id] = nil +    logout      redirect_to root_path    end  end diff --git a/users/app/views/sessions/new.json.erb b/users/app/views/sessions/new.json.erb new file mode 100644 index 0000000..36154b8 --- /dev/null +++ b/users/app/views/sessions/new.json.erb @@ -0,0 +1,3 @@ +{ +"errors": <%= raw @errors.to_json %> +} diff --git a/users/config/initializers/warden.rb b/users/config/initializers/warden.rb new file mode 100644 index 0000000..45feb6c --- /dev/null +++ b/users/config/initializers/warden.rb @@ -0,0 +1,7 @@ +Rails.configuration.middleware.use RailsWarden::Manager do |config| +  config.default_strategies :secure_remote_password +  config.failure_app = SessionsController +end + +RailsWarden.unauthenticated_action = :new + diff --git a/users/leap_web_users.gemspec b/users/leap_web_users.gemspec index dec5a71..0682a99 100644 --- a/users/leap_web_users.gemspec +++ b/users/leap_web_users.gemspec @@ -18,4 +18,5 @@ Gem::Specification.new do |s|    s.add_dependency "leap_web_core", LeapWeb::VERSION    s.add_dependency "ruby-srp", "~> 0.1.4" +  s.add_dependency "rails_warden"  end diff --git a/users/lib/leap_web_users/engine.rb b/users/lib/leap_web_users/engine.rb index 9b7545e..7033576 100644 --- a/users/lib/leap_web_users/engine.rb +++ b/users/lib/leap_web_users/engine.rb @@ -1,8 +1,12 @@  # thou shall require all your dependencies in an engine.  require "leap_web_core"  require "leap_web_core/ui_dependencies" +require "rails_warden"  require "ruby-srp" +require "warden/session_serializer" +require "warden/strategies/secure_remote_password" +  module LeapWebUsers    class Engine < ::Rails::Engine diff --git a/users/lib/warden/session_serializer.rb b/users/lib/warden/session_serializer.rb new file mode 100644 index 0000000..81d7076 --- /dev/null +++ b/users/lib/warden/session_serializer.rb @@ -0,0 +1,13 @@ +module Warden +  # Setup Session Serialization +  class SessionSerializer +    def serialize(record) +      [record.class.name, record.id] +    end + +    def deserialize(keys) +      klass, id = keys +      klass.constantize.find(id) +    end +  end +end diff --git a/users/lib/warden/strategies/secure_remote_password.rb b/users/lib/warden/strategies/secure_remote_password.rb new file mode 100644 index 0000000..8266e2d --- /dev/null +++ b/users/lib/warden/strategies/secure_remote_password.rb @@ -0,0 +1,57 @@ +module Warden +  module Strategies +    class SecureRemotePassword < Warden::Strategies::Base + +      def valid? +        handshake? || authentication? +      end + +      def authenticate! +        if authentication? +          validate! +        else  # handshake +          initialize! +        end +      end + +      protected + +      def handshake? +        params['A'] && params['login'] +      end + +      def authentication? +        params['client_auth'] && session[:handshake] +      end + +      def validate! +        user = session[:handshake].authenticate(params['client_auth'].hex) +        user ? success!(user) : fail!(:password => "Could not log in") +      end + +      def initialize! +        user = User.find_by_param(id) +        session[:handshake] = user.initialize_auth(params['A'].hex) +        custom! json_response(session[:handshake]) +      rescue RECORD_NOT_FOUND +        fail! :login => "User not found!" +      end + +      def json_response(object) +        [ 200, +          {"Content-Type" => "application/json; charset=utf-8"}, +          [object.to_json] +        ] +      end + +      def id +        params["id"] || params["login"] +      end +    end +  end +  Warden::Strategies.add :secure_remote_password, +    Warden::Strategies::SecureRemotePassword + +end + + diff --git a/users/test/functional/application_controller_test.rb b/users/test/functional/application_controller_test.rb index 69bcb2f..857bae5 100644 --- a/users/test/functional/application_controller_test.rb +++ b/users/test/functional/application_controller_test.rb @@ -8,20 +8,19 @@ class ApplicationControllerTest < ActionController::TestCase    end    def test_authorize_redirect -    stub_logged_out      @controller.send(:authorize)      assert_access_denied    end    def test_authorized -    @user = stub_logged_in +    login      @controller.send(:authorize)      assert_access_denied(false)    end    def test_authorize_admin -    @user = stub_logged_in -    @user.expects(:is_admin?).returns(false) +    login +    @current_user.expects(:is_admin?).returns(false)      @controller.send(:authorize_admin)      assert_access_denied    end diff --git a/users/test/functional/helper_methods_test.rb b/users/test/functional/helper_methods_test.rb index c0eaf61..2b2375c 100644 --- a/users/test/functional/helper_methods_test.rb +++ b/users/test/functional/helper_methods_test.rb @@ -16,26 +16,23 @@ class HelperMethodsTest < ActionController::TestCase      @controller    end -  def test_current_user_with_caching -    @user = stub_logged_in -    assert_equal @user, current_user -    assert_equal @user, current_user # tests caching +  def test_current_user +    login +    assert_equal @current_user, current_user    end    def test_logged_in -    @user = stub_logged_in +    login      assert logged_in?    end    def test_logged_out -    stub_logged_out      assert !logged_in?    end    def test_admin -    bool = stub -    @user = stub_logged_in -    @user.expects(:is_admin?).returns(bool) +    login +    @current_user.expects(:is_admin?).returns(bool = stub)      assert_equal bool, admin?    end diff --git a/users/test/functional/sessions_controller_test.rb b/users/test/functional/sessions_controller_test.rb index 47d7052..8f2d95c 100644 --- a/users/test/functional/sessions_controller_test.rb +++ b/users/test/functional/sessions_controller_test.rb @@ -1,75 +1,71 @@  require 'test_helper' +# This is a simple controller unit test. +# We're stubbing out both warden and srp. +# There's an integration test testing the full rack stack and srp  class SessionsControllerTest < ActionController::TestCase -  def setup +  setup do      @user = stub :login => "me", :id => 123      @client_hex = 'a123' -    @client_rnd = @client_hex.hex -    @server_hex = 'b123' -    @server_rnd = @server_hex.hex -    @server_rnd_exp = 'e123'.hex -    @salt = 'stub user salt' -    @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp -    @server_auth = 'adfe'    end    test "should get login screen" do +    request.env['warden'].expects(:winning_strategy)      get :new      assert_response :success +    assert_equal "text/html", response.content_type +    assert_template "sessions/new"    end -  test "should perform handshake" do -    @user.expects(:initialize_auth). -      with(@client_rnd). -      returns(@server_handshake) -    @server_handshake.expects(:to_json). -     returns({'B' => @server_hex, 'salt' => @salt}.to_json) -    User.expects(:find_by_param).with(@user.login).returns(@user) -    post :create, :login => @user.login, 'A' => @client_hex -    assert_equal @server_handshake, session[:handshake] +  test "renders json" do +    request.env['warden'].expects(:winning_strategy) +    get :new, :format => :json      assert_response :success -    assert_json_response :B => @server_hex, :salt => @salt +    assert_json_response :errors => nil    end -  test "should report user not found" do -    unknown = "login_that_does_not_exist" -    User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND) -    post :create, :login => unknown +  test "renders warden errors" do +    strategy = stub :message => "Warden auth did not work" +    request.env['warden'].expects(:winning_strategy).returns(strategy) +    get :new, :format => :json      assert_response :success -    assert_json_response :errors => {"login" => ["unknown user"]} +    assert_json_response :errors => strategy.message    end -  test "should authorize" do -    session[:handshake] = @server_handshake -    @server_handshake.expects(:authenticate!). -      with(@client_rnd). -      returns(@user) -    @server_handshake.expects(:to_json). -      returns({:M2 => @server_auth}.to_json) -    post :update, :id => @user.login, :client_auth => @client_hex -    assert_nil session[:handshake] -    assert_json_response :M2 => @server_auth -    assert_equal @user.id, session[:user_id] +  # Warden takes care of parsing the params and +  # rendering the response. So not much to test here. +  test "should perform handshake" do +    request.env['warden'].expects(:authenticate!) +    # make sure we don't get a template missing error: +    @controller.stubs(:render) +    post :create, :login => @user.login, 'A' => @client_hex    end -  test "should report wrong password" do -    session[:handshake] = @server_handshake -    @server_handshake.expects(:authenticate!). -      with(@client_rnd). -      raises(WRONG_PASSWORD) +  test "should authorize" do +    request.env['warden'].expects(:authenticate!) +    handshake = stub(:to_json => "JSON") +    session[:handshake] = handshake      post :update, :id => @user.login, :client_auth => @client_hex      assert_nil session[:handshake] -    assert_nil session[:user_id] -    assert_json_response :errors => {"password" => ["wrong password"]} +    assert_response :success +    assert_equal handshake.to_json, @response.body    end -  test "logout should reset sessions user_id" do -    session[:user_id] = "set" +  test "logout should reset warden user" do +    expect_warden_logout      delete :destroy -    assert_nil session[:user_id]      assert_response :redirect      assert_redirected_to root_url    end +  def expect_warden_logout +    raw = mock('raw session') do +      expects(:inspect) +    end +    request.env['warden'].expects(:raw_session).returns(raw) +    request.env['warden'].expects(:logout) +  end + +  end diff --git a/users/test/integration/api/account_flow_test.rb b/users/test/integration/api/account_flow_test.rb index 5800d46..c9a7109 100644 --- a/users/test/integration/api/account_flow_test.rb +++ b/users/test/integration/api/account_flow_test.rb @@ -1,12 +1,26 @@  require 'test_helper' -class AccountFlowTest < ActionDispatch::IntegrationTest +CONFIG_RU = (Rails.root + 'config.ru').to_s +OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first + +class AccountFlowTest < ActiveSupport::TestCase +  include Rack::Test::Methods +  include Warden::Test::Helpers +  include LeapWebCore::AssertResponses + +  def app +    OUTER_APP +  end + +  def teardown +    Warden.test_reset! +  end    # this test wraps the api and implements the interface the ruby-srp client.    def handshake(login, aa) -    post "sessions", :login => login, 'A' => aa.to_s(16) -    assert_response :success -    response = JSON.parse(@response.body) +    post "/sessions.json", :login => login, 'A' => aa.to_s(16), :format => :json +    assert last_response.successful? +    response = JSON.parse(last_response.body)      if response['errors']        raise RECORD_NOT_FOUND.new(response['errors'])      else @@ -15,9 +29,9 @@ class AccountFlowTest < ActionDispatch::IntegrationTest    end    def validate(m) -    put "sessions/" + @login, :client_auth => m.to_s(16) -    assert_response :success -    return JSON.parse(@response.body) +    put "/sessions/" + @login + '.json', :client_auth => m.to_s(16), :format => :json +    assert last_response.successful? +    return JSON.parse(last_response.body)    end    def setup @@ -40,7 +54,7 @@ class AccountFlowTest < ActionDispatch::IntegrationTest    test "signup response" do      assert_json_response :login => @login, :ok => true -    assert_response :success +    assert last_response.successful?    end    test "signup and login with srp via api" do @@ -52,7 +66,7 @@ class AccountFlowTest < ActionDispatch::IntegrationTest    test "signup and wrong password login attempt" do      srp = SRP::Client.new(@login, "wrong password")      server_auth = srp.authenticate(self) -    assert_equal ["wrong password"], server_auth["errors"]['password'] +    assert_equal "Could not log in", server_auth["errors"]['password']      assert_nil server_auth["M2"]    end diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb index 9412058..f211597 100644 --- a/users/test/support/auth_test_helper.rb +++ b/users/test/support/auth_test_helper.rb @@ -1,17 +1,18 @@  module AuthTestHelper +  extend ActiveSupport::Concern -  def stub_logged_in -    @user_id = stub -    @user = stub -    session[:user_id] = @user_id -    User.expects(:find).once.with(@user_id).returns(@user) -    return @user +  # Controller will fetch current user from warden. +  # Make it pick up our current_user +  included do +    setup do +      request.env['warden'] ||= stub :user => nil +    end    end -  def stub_logged_out -    @user_id = stub -    session[:user_id] = @user_id -    User.expects(:find).once.with(@user_id).returns(nil) +  def login(user = nil) +    @current_user = user || stub +    request.env['warden'] = stub :user => @current_user +    return @current_user    end    def assert_access_denied(denied = true) diff --git a/users/test/unit/warden_strategy_secure_remote_password_test.rb b/users/test/unit/warden_strategy_secure_remote_password_test.rb new file mode 100644 index 0000000..ee68fe7 --- /dev/null +++ b/users/test/unit/warden_strategy_secure_remote_password_test.rb @@ -0,0 +1,61 @@ +class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase + +  setup do +    @user = stub :login => "me", :id => 123 +    @client_hex = 'a123' +    @client_rnd = @client_hex.hex +    @server_hex = 'b123' +    @server_rnd = @server_hex.hex +    @server_rnd_exp = 'e123'.hex +    @salt = 'stub user salt' +    @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp +    @server_auth = 'adfe' +  end + + +  test "should perform handshake" do +    @user.expects(:initialize_auth). +      with(@client_rnd). +      returns(@server_handshake) +    @server_handshake.expects(:to_json). +     returns({'B' => @server_hex, 'salt' => @salt}.to_json) +    User.expects(:find_by_param).with(@user.login).returns(@user) +    assert_equal @server_handshake, session[:handshake] +    assert_response :success +    assert_json_response :B => @server_hex, :salt => @salt +  end + +  test "should report user not found" do +    unknown = "login_that_does_not_exist" +    User.expects(:find_by_param).with(unknown).raises(RECORD_NOT_FOUND) +    post :create, :login => unknown +    assert_response :success +    assert_json_response :errors => {"login" => ["unknown user"]} +  end + +  test "should authorize" do +    session[:handshake] = @server_handshake +    @server_handshake.expects(:authenticate!). +      with(@client_rnd). +      returns(@user) +    @server_handshake.expects(:to_json). +      returns({:M2 => @server_auth}.to_json) +    post :update, :id => @user.login, :client_auth => @client_hex +    assert_nil session[:handshake] +    assert_json_response :M2 => @server_auth +    assert_equal @user.id, session[:user_id] +  end + +  test "should report wrong password" do +    session[:handshake] = @server_handshake +    @server_handshake.expects(:authenticate!). +      with(@client_rnd). +      raises(WRONG_PASSWORD) +    post :update, :id => @user.login, :client_auth => @client_hex +    assert_nil session[:handshake] +    assert_nil session[:user_id] +    assert_json_response :errors => {"password" => ["wrong password"]} +  end + + +end | 
