diff options
| author | azul <azul@riseup.net> | 2014-04-17 10:12:05 +0200 | 
|---|---|---|
| committer | azul <azul@riseup.net> | 2014-04-17 10:12:05 +0200 | 
| commit | 3513ad74f950b113af1ba1e3d06bc6a55c48fde5 (patch) | |
| tree | db49ebd4428053d5c8d720275b77594a531a1ad1 /test | |
| parent | cb6442c344d6bdaf52c3878b2de2fcf4d85f2648 (diff) | |
| parent | 3d3688647fab7049e5b531c45b85c1e46a1d528f (diff) | |
Merge pull request #146 from azul/refactor/engines
Refactor/engines
Diffstat (limited to 'test')
50 files changed, 2384 insertions, 2 deletions
| diff --git a/test/factories.rb b/test/factories.rb index 6c671f8..ac9333c 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -1,3 +1,39 @@ -Dir.glob(Rails.root.join('**','test','factories.rb')) do |factory_file| +ENGINE_FACTORY_FILES = Rails.root.join('engines','*','test','factories.rb') +Dir.glob(ENGINE_FACTORY_FILES) do |factory_file|    require factory_file  end + +FactoryGirl.define do + +  factory :user do +    login { Faker::Internet.user_name } +    password_verifier "1234ABCD" +    password_salt "4321AB" + +    factory :user_with_settings do +      email_forward { Faker::Internet.email } +      email_aliases_attributes do +        {:a => Faker::Internet.user_name + '@' + APP_CONFIG[:domain]} +      end +    end + +    factory :admin_user do +      after(:build) do |admin| +        admin.stubs(:is_admin?).returns(true) +      end +    end +  end + +  factory :token do +    user +  end + +  factory :pgp_key do +    keyblock <<-EOPGP +-----BEGIN PGP PUBLIC KEY BLOCK----- ++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+ +#{SecureRandom.base64(4032)} +-----END PGP PUBLIC KEY BLOCK----- +    EOPGP +  end +end diff --git a/test/files/ca.crt b/test/files/ca.crt new file mode 100644 index 0000000..8393eee --- /dev/null +++ b/test/files/ca.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYDCCAcCgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA7MREwDwYDVQQKDAh0ZXN0 +IG9yZzESMBAGA1UECwwJdGVzdCB1bml0MRIwEAYDVQQDDAl0ZXN0IG5hbWUwIBcN +MTMwMjA1MDAwMDAwWhgPMjExMzAyMDUwMDAwMDBaMDsxETAPBgNVBAoMCHRlc3Qg +b3JnMRIwEAYDVQQLDAl0ZXN0IHVuaXQxEjAQBgNVBAMMCXRlc3QgbmFtZTCBqDAN +BgkqhkiG9w0BAQEFAAOBlgAwgZICgYoAx076Dz8zswvCLuz0HP3Y3PWOgFDo9+8o +H4uXRcTpd+yw+5B79xjtQ7ojQy2465Jq00nkzHI6V1otM2uvVVIOcNk0t1HEjmK0 +T/r96dDHc59YvVQ+XPrzuQ4t3iREy8IAPNbc3r29PVZkMdGpeSYxyY1mUKza4DcY +My4SVko9pcP8zJBD4bHgEa0CAwEAAaNgMF4wHQYDVR0OBBYEFOQ+d2EUwBpi93TJ +9AX4Okew5/UIMA4GA1UdDwEB/wQEAwICBDAMBgNVHRMEBTADAQH/MB8GA1UdIwQY +MBaAFOQ+d2EUwBpi93TJ9AX4Okew5/UIMA0GCSqGSIb3DQEBDQUAA4GKAJW9/39P +VbVjH9C7F0XMOpd9nWBe9NUoiw36ZFZw95dqfUm6j5f3nejWG4lEtyMFu5i5rAw6 +GdDSXmq4sUqWTaJmQmZyY+WggQR4UGWJ0I18HRDiPxuA++OfkGzA20Gmvk+CIw/J +QLHlVjLyyUwaA+EO88rEcdc9VnGL/Xgjh8C/PYH2DpWw/kJa +-----END CERTIFICATE----- diff --git a/test/files/ca.key b/test/files/ca.key new file mode 100644 index 0000000..125997f --- /dev/null +++ b/test/files/ca.key @@ -0,0 +1,16 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIChAIBAAKBigDHTvoPPzOzC8Iu7PQc/djc9Y6AUOj37ygfi5dFxOl37LD7kHv3 +GO1DuiNDLbjrkmrTSeTMcjpXWi0za69VUg5w2TS3UcSOYrRP+v3p0Mdzn1i9VD5c ++vO5Di3eJETLwgA81tzevb09VmQx0al5JjHJjWZQrNrgNxgzLhJWSj2lw/zMkEPh +seARrQIDAQABAoGJIvn0HircOsaMfEmvCUtu/E/HgzMvvxrkMqz/jgnhYt9Rq8QO +TS29rY4D1C0473ZRcuTb1xkQrfWwSv7R1SpCSIGFo8obtGb0NjNaYGyQ0IrYDjk8 +H5kYFEY4X4oqFhgy3owewaZZLxLD336ARRj2HhsLzA+4nD/wF7Q+bggpuMdkM2Uj +tn12rIECRQ/XqIGF8jLw9IDMkr9kkfT+n03p8sOd4g7iSw0sknlzaZZpIDvibkyN +SDKM7VX4VQa7u58+sCF4ylwi0UQu7/VT7Smp4QJFDJSoEOKplBvaT9fTfdVKjE4P +QyCAWEsb6Up8KKswhtDqiWeFtktIvx1Mkxn25erLms3cUEBde//rwNB+6ItBR/N8 +4RlNAkUPLsc3Gn+7gmFQ7r3U3zViboON0B/wiWcUjJsQzR6zdoBCvg0+VwsOIniG +ubjbI1uZUGHHg/SYn4KQOm4DwlgF7aDkxQECRQjVZMEedlXxzLOdZvoHBuZHdT38 +F0Jn0rxXOaDQuy0eimBamS+r4vOWngr4Az3jRH15KMYMu9dyllX3z/R2uyrLVBc2 +TQJFBEHIjoMVgP2h+N6VUDgPOhnxnnLvowOtX23J1y2foKwfZrHH38LNcWmuaGUi +fz6EYeUO20D174GfhqB0j6yR50ejPjYD +-----END RSA PRIVATE KEY----- diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb new file mode 100644 index 0000000..c4c922b --- /dev/null +++ b/test/functional/application_controller_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class ApplicationControllerTest < ActionController::TestCase + +  def setup +    # so we can test the effect on the response +    @controller.response = @response +  end + +  def test_require_login_redirect +    @controller.send(:require_login) +    assert_access_denied(true, false) +  end + +  def test_require_login +    login +    @controller.send(:require_login) +    assert_access_denied(false) +  end + +  def test_require_admin +    login +    @current_user.expects(:is_admin?).returns(false) +    @controller.send(:require_admin) +    assert_access_denied +  end + +end diff --git a/test/functional/helper_methods_test.rb b/test/functional/helper_methods_test.rb new file mode 100644 index 0000000..44226ae --- /dev/null +++ b/test/functional/helper_methods_test.rb @@ -0,0 +1,39 @@ +# +# Testing and documenting the helper methods available from +# ApplicationController +# + +require 'test_helper' + +class HelperMethodsTest < ActionController::TestCase +  tests ApplicationController + +  # we test them right in here... +  include ApplicationController._helpers + +  # the helpers all reference the controller. +  def controller +    @controller +  end + +  def test_current_user +    login +    assert_equal @current_user, current_user +  end + +  def test_logged_in +    login +    assert logged_in? +  end + +  def test_logged_out +    assert !logged_in? +  end + +  def test_admin +    login +    @current_user.expects(:is_admin?).returns(bool = stub) +    assert_equal bool, admin? +  end + +end diff --git a/test/functional/keys_controller_test.rb b/test/functional/keys_controller_test.rb new file mode 100644 index 0000000..863be93 --- /dev/null +++ b/test/functional/keys_controller_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class KeysControllerTest < ActionController::TestCase + +  test "get existing public key" do +    public_key = 'my public key' +    @user = stub_record :user, :public_key => public_key +    User.stubs(:find_by_login).with(@user.login).returns(@user) +    get :show, :login => @user.login +    assert_response :success +    assert_equal "text/text", response.content_type +    assert_equal public_key, response.body +  end + +  test "get non-existing public key for user" do +    # this isn't a scenerio that should generally occur. +    @user = stub_record :user +    User.stubs(:find_by_login).with(@user.login).returns(@user) +    get :show, :login => @user.login +    assert_response :success +    assert_equal "text/text", response.content_type +    assert_equal '', response.body.strip +  end + +  test "get public key for non-existing user" do +    # raise 404 error if user doesn't exist (doesn't need to be this routing error, but seems fine to assume for now): +    assert_raise(ActionController::RoutingError) { +      get :show, :login => 'asdkljslksjfdlskfj' +    } +  end + +end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb new file mode 100644 index 0000000..fe7903f --- /dev/null +++ b/test/functional/sessions_controller_test.rb @@ -0,0 +1,59 @@ +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 + +  setup do +    @user = stub :login => "me", :id => 123 +    @client_hex = 'a123' +  end + +  test "should get login screen" do +    get :new +    assert_response :success +    assert_equal "text/html", response.content_type +    assert_template "sessions/new" +  end + +  test "redirect to home_url if logged in" do +    login +    get :new +    assert_response :redirect +    assert_redirected_to home_url +  end + +  test "renders json" do +    get :new, :format => :json +    assert_response :success +    assert_json_error nil +  end + +  test "renders warden errors" do +    request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} +    strategy = stub :message => {:field => :translate_me} +    request.env['warden'].stubs(:winning_strategy).returns(strategy) +    I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") +    get :new, :format => :json +    assert_response 422 +    assert_json_error :field => "translation stub" +  end + +  test "renders failed attempt message" do +    request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} +    request.env['warden'].stubs(:winning_strategy).returns(nil) +    get :new, :format => :json +    assert_response 422 +    assert_json_error :login => I18n.t(:all_strategies_failed) +  end + +  test "destory should logout" do +    login +    expect_logout +    delete :destroy +    assert_response :redirect +    assert_redirected_to home_url +  end + +end diff --git a/test/functional/test_helpers_test.rb b/test/functional/test_helpers_test.rb new file mode 100644 index 0000000..845e516 --- /dev/null +++ b/test/functional/test_helpers_test.rb @@ -0,0 +1,38 @@ +# +# There are a few test helpers for dealing with login etc. +# We test them here and also document their behaviour. +# + +require 'test_helper' + +class TestHelpersTest < ActionController::TestCase +  tests ApplicationController # testing no controller in particular + +  def test_login_stubs_warden +    login +    assert_equal @current_user, request.env['warden'].user +  end + +  def test_login_token_authenticates +    login +    assert_equal @current_user, @controller.send(:token_authenticate) +  end + +  def test_login_stubs_token +    login +    assert @token +    assert_equal @current_user, @token.authenticate +  end + +  def test_login_adds_token_header +    login +    token_present = @controller.authenticate_with_http_token do |token, options| +      assert_equal @token.id, token +    end +    # authenticate_with_http_token just returns nil and does not +    # execute the block if there is no token. So we have to also +    # ensure it was run: +    assert token_present +  end +end + diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb new file mode 100644 index 0000000..0713836 --- /dev/null +++ b/test/functional/users_controller_test.rb @@ -0,0 +1,165 @@ +require 'test_helper' + +class UsersControllerTest < ActionController::TestCase + +  test "should get new" do +    get :new +    assert_equal User, assigns(:user).class +    assert_response :success +  end + +  test "new should redirect logged in users" do +    login +    get :new +    assert_response :redirect +    assert_redirected_to home_path +  end + +  test "failed show without login" do +    user = find_record :user +    get :show, :id => user.id +    assert_response :redirect +    assert_redirected_to login_path +  end + +  test "user can see user" do +    user = find_record :user, +      :most_recent_tickets => [] +    login user +    get :show, :id => user.id +    assert_response :success +  end + +  test "admin can see other user" do +    user = find_record :user, +      :most_recent_tickets => [] +    login :is_admin? => true +    get :show, :id => user.id +    assert_response :success + +  end + +  test "user cannot see other user" do +    user = find_record :user, +      :most_recent_tickets => [] +    login +    get :show, :id => user.id +    assert_response :redirect +    assert_access_denied +  end + +  test "may not show non-existing user without auth" do +    nonid = 'thisisnotanexistinguserid' + +    get :show, :id => nonid +    assert_access_denied(true, false) +  end + +  test "may not show non-existing user without admin" do +    nonid = 'thisisnotanexistinguserid' +    login + +    get :show, :id => nonid +    assert_access_denied +  end + +  test "redirect admin to user list for non-existing user" do +    nonid = 'thisisnotanexistinguserid' +    login :is_admin? => true +    get :show, :id => nonid +    assert_response :redirect +    assert_equal({:alert => "No such user."}, flash.to_hash) +    assert_redirected_to users_path +  end + +  test "should get edit view" do +    user = find_record :user + +    login user +    get :edit, :id => user.id + +    assert_equal user, assigns[:user] +  end + +  test "admin can destroy user" do +    user = find_record :user + +    # we destroy the user record and the associated data... +    user.expects(:destroy) +    Identity.expects(:disable_all_for).with(user) +    Ticket.expects(:destroy_all_from).with(user) + +    login :is_admin? => true +    delete :destroy, :id => user.id + +    assert_response :redirect +    assert_redirected_to users_path +  end + +  test "user can cancel account" do +    user = find_record :user + +    # we destroy the user record and the associated data... +    user.expects(:destroy) +    Identity.expects(:disable_all_for).with(user) +    Ticket.expects(:destroy_all_from).with(user) + +    login user +    expect_logout +    delete :destroy, :id => @current_user.id + +    assert_response :redirect +    assert_redirected_to bye_url +  end + +  test "non-admin can't destroy user" do +    user = find_record :user + +    login +    delete :destroy, :id => user.id + +    assert_access_denied +  end + +  test "admin can list users" do +    login :is_admin? => true +    get :index + +    assert_response :success +    assert assigns(:users) +  end + +  test "non-admin can't list users" do +    login +    get :index + +    assert_access_denied +  end + +  test "admin can search users" do +    login :is_admin? => true +    get :index, :query => "a" + +    assert_response :success +    assert assigns(:users) +  end + +  test "user cannot enable own account" do +    user = find_record :user +    login +    post :enable, :id => user.id +    assert_access_denied +  end + +  test "admin can deactivate user" do +    user = find_record :user +    assert user.enabled? +    user.expects(:save).returns(true) + +    login :is_admin? => true + +    post :deactivate, :id => user.id +    assert !assigns(:user).enabled? +  end + +end diff --git a/test/functional/v1/certs_controller_test.rb b/test/functional/v1/certs_controller_test.rb new file mode 100644 index 0000000..2c70e52 --- /dev/null +++ b/test/functional/v1/certs_controller_test.rb @@ -0,0 +1,44 @@ +require 'test_helper' + +class V1::CertsControllerTest < ActionController::TestCase + +  test "send limited cert without login" do +    with_config allow_limited_certs: true, allow_anonymous_certs: true do +      cert = stub :to_s => "limited cert" +      ClientCertificate.expects(:new).with(:prefix => APP_CONFIG[:limited_cert_prefix]).returns(cert) +      get :show +      assert_response :success +      assert_equal cert.to_s, @response.body +    end +  end + +  test "send unlimited cert" do +    with_config allow_unlimited_certs: true do +      login +      cert = stub :to_s => "unlimited cert" +      ClientCertificate.expects(:new).with(:prefix => APP_CONFIG[:unlimited_cert_prefix]).returns(cert) +      get :show +      assert_response :success +      assert_equal cert.to_s, @response.body +    end +  end + +  test "login required if anonymous certs disabled" do +    with_config allow_anonymous_certs: false do +      get :show +      assert_response :redirect +    end +  end + +  test "send limited cert" do +    with_config allow_limited_certs: true, allow_unlimited_certs: false do +      login +      cert = stub :to_s => "real cert" +      ClientCertificate.expects(:new).with(:prefix => APP_CONFIG[:limited_cert_prefix]).returns(cert) +      get :show +      assert_response :success +      assert_equal cert.to_s, @response.body +    end +  end + +end diff --git a/test/functional/v1/messages_controller_test.rb b/test/functional/v1/messages_controller_test.rb new file mode 100644 index 0000000..24a5b1f --- /dev/null +++ b/test/functional/v1/messages_controller_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class V1::MessagesControllerTest < ActionController::TestCase + +  setup do +    @user = FactoryGirl.build(:user) +    @user.save +    @message = Message.new(:text => 'a test message') +    @message.user_ids_to_show << @user.id +    @message.save +  end + +  teardown do +    @message.destroy +    @user.destroy +  end + +  test "get messages for user" do +    login @user +    get :index +    assert response.body.include? @message.text +    assert response.body.include? @message.id +  end + +  test "mark message read for user" do +    login @user +    assert @message.user_ids_to_show.include?(@user.id) +    assert !@message.user_ids_have_shown.include?(@user.id) +    put :update, :id => @message.id +    @message.reload +    assert !@message.user_ids_to_show.include?(@user.id) +    assert @message.user_ids_have_shown.include?(@user.id) +    assert_json_response true +  end + +  test "do not get seen messages" do +    login @user +    put :update, :id => @message.id +    @message.reload +    get :index +    assert !(response.body.include? @message.text) +    assert !(response.body.include? @message.id) +  end + + +  test "mark read responds even with bad inputs" do +    login @user +    put :update, :id => 'more nonsense' +    assert_json_response false + end + +  test "fails if not authenticated" do +    get :index, :format => :json +    assert_access_denied +  end + +end diff --git a/test/functional/v1/sessions_controller_test.rb b/test/functional/v1/sessions_controller_test.rb new file mode 100644 index 0000000..df0d681 --- /dev/null +++ b/test/functional/v1/sessions_controller_test.rb @@ -0,0 +1,62 @@ +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 V1::SessionsControllerTest < ActionController::TestCase + +  setup do +    @request.env['HTTP_HOST'] = 'api.lvh.me' +    @user = stub_record :user, {}, true +    @client_hex = 'a123' +  end + +  test "renders json" do +    get :new, :format => :json +    assert_response :success +    assert_json_error nil +  end + +  test "renders warden errors" do +    request.env['warden.options'] = {attempted_path: 'path/to/controller'} +    strategy = stub :message => {:field => :translate_me} +    request.env['warden'].stubs(:winning_strategy).returns(strategy) +    I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") +    get :new, :format => :json +    assert_response 422 +    assert_json_error :field => "translation stub" +  end + +  # 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 authenticate" do +    request.env['warden'].expects(:authenticate!) +    @controller.stubs(:current_user).returns(@user) +    handshake = stub(:to_hash => {h: "ash"}) +    session[:handshake] = handshake + +    post :update, :id => @user.login, :client_auth => @client_hex + +    assert_nil session[:handshake] +    assert_response :success +    assert json_response.keys.include?("id") +    assert json_response.keys.include?("token") +    assert token = Token.find(json_response['token']) +    assert_equal @user.id, token.user_id +  end + +  test "destroy should logout" do +    login +    expect_logout +    delete :destroy +    assert_response 204 +  end + +end diff --git a/test/functional/v1/users_controller_test.rb b/test/functional/v1/users_controller_test.rb new file mode 100644 index 0000000..7cd9b0c --- /dev/null +++ b/test/functional/v1/users_controller_test.rb @@ -0,0 +1,74 @@ +require 'test_helper' + +class V1::UsersControllerTest < ActionController::TestCase + +  test "user can change settings" do +    user = find_record :user +    changed_attribs = record_attributes_for :user_with_settings +    account_settings = stub +    account_settings.expects(:update).with(changed_attribs) +    Account.expects(:new).with(user).returns(account_settings) + +    login user +    put :update, :user => changed_attribs, :id => user.id, :format => :json + +    assert_equal user, assigns[:user] +    assert_response 204 +    assert_equal " ", @response.body +  end + +  test "admin can update user" do +    user = find_record :user +    changed_attribs = record_attributes_for :user_with_settings +    account_settings = stub +    account_settings.expects(:update).with(changed_attribs) +    Account.expects(:new).with(user).returns(account_settings) + +    login :is_admin? => true +    put :update, :user => changed_attribs, :id => user.id, :format => :json + +    assert_equal user, assigns[:user] +    assert_response 204 +  end + +  test "user cannot update other user" do +    user = find_record :user +    login +    put :update, :user => record_attributes_for(:user_with_settings), :id => user.id, :format => :json +    assert_access_denied +  end + +  test "should create new user" do +    user_attribs = record_attributes_for :user +    user = User.new(user_attribs) +    Account.expects(:create).with(user_attribs).returns(user) + +    post :create, :user => user_attribs, :format => :json + +    assert_nil session[:user_id] +    assert_json_response user +    assert_response :success +  end + +  test "should redirect to signup form on failed attempt" do +    user_attribs = record_attributes_for :user +    user_attribs.slice!('login') +    user = User.new(user_attribs) +    assert !user.valid? +    Account.expects(:create).with(user_attribs).returns(user) + +    post :create, :user => user_attribs, :format => :json + +    assert_json_error user.errors.messages +    assert_response 422 +  end + +  test "admin can autocomplete users" do +    login :is_admin? => true +    get :index, :query => 'a', :format => :json + +    assert_response :success +    assert assigns(:users) +  end + +end diff --git a/test/functional/webfinger_controller_test.rb b/test/functional/webfinger_controller_test.rb new file mode 100644 index 0000000..6597b69 --- /dev/null +++ b/test/functional/webfinger_controller_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class WebfingerControllerTest < ActionController::TestCase + +  test "get host meta xml" do +    get :host_meta, :format => :xml +    assert_response :success +    assert_equal "application/xml", response.content_type +  end + +  test "get host meta json" do +    get :host_meta, :format => :json +    assert_response :success +    assert_equal "application/json", response.content_type +  end + +  test "get user webfinger xml" do +    @user = stub_record :user, :public_key => 'my public key' +    User.stubs(:find_by_login).with(@user.login).returns(@user) +    get :search, :q => @user.email_address.to_s, :format => :xml +    assert_response :success +    assert_equal "application/xml", response.content_type +  end + +  test "get user webfinger json" do +    @user = stub_record :user, :public_key => 'my public key' +    User.stubs(:find_by_login).with(@user.login).returns(@user) +    get :search, :q => @user.email_address.to_s, :format => :json +    assert_response :success +    assert_equal "application/json", response.content_type +  end + +end diff --git a/test/integration/api/Readme.md b/test/integration/api/Readme.md new file mode 100644 index 0000000..04363bd --- /dev/null +++ b/test/integration/api/Readme.md @@ -0,0 +1,23 @@ +API tests +========== + + +Testing the restful api from a simple python client as that's what we'll be using. + +This test so far mostly demoes the API. We have no SRP calc in there. + +TODO: keep track of the cookies during login. The server uses the session to keep track of the random numbers A and B. + +The output of signup_and_login_wrong_password pretty well describes the SRP API: + +``` +POST: http://localhost:9292/users.json +    {"user[password_salt]": "54321", "user[password_verifier]": "12345", "user[login]": "SWQ055"} + -> {"password_salt":"54321","login":"SWQ055"} +POST: http://localhost:9292/sessions +    {"A": "12345", "login": "SWQ055"} + -> {"B":"1778367531e93a4c7713c76f67649f35a4211ebc520926ae8c3848cd66171651"} +PUT: http://localhost:9292/sessions/SWQ055 +    {"M": "123ABC"} + -> {"errors":[{"login":"Not a valid username/password combination"},{"password":"Not a valid username/password combination"}]} +``` diff --git a/test/integration/api/login_test.rb b/test/integration/api/login_test.rb new file mode 100644 index 0000000..92d153f --- /dev/null +++ b/test/integration/api/login_test.rb @@ -0,0 +1,50 @@ +require 'test_helper' +require_relative 'srp_test' + +class LoginTest < SrpTest + +  setup do +    register_user +  end + +  test "requires handshake before validation" do +    validate("bla") +    assert_json_error login: I18n.t(:all_strategies_failed) +  end + +  test "login with srp" do +    authenticate +    assert_equal ["M2", "id", "token"], server_auth.keys +    assert last_response.successful? +    assert_nil server_auth["errors"] +    assert server_auth["M2"] +  end + +  test "wrong password login attempt" do +    authenticate password: "wrong password" +    assert_json_error "base" => "Not a valid username/password combination" +    assert !last_response.successful? +    assert_nil server_auth["M2"] +  end + +  test "wrong username login attempt" do +    assert_raises RECORD_NOT_FOUND do +      authenticate login: "wrong login" +    end +    assert_json_error "base" => "Not a valid username/password combination" +    assert !last_response.successful? +    assert_nil server_auth +  end + +  test "logout" do +    authenticate +    logout +    assert_equal 204, last_response.status +  end + +  test "logout requires token" do +    authenticate +    logout(nil, {}) +    assert_equal 422, last_response.status +  end +end diff --git a/test/integration/api/pgp_key_test.rb b/test/integration/api/pgp_key_test.rb new file mode 100644 index 0000000..4c7fb4c --- /dev/null +++ b/test/integration/api/pgp_key_test.rb @@ -0,0 +1,35 @@ +require 'test_helper' +require_relative 'srp_test' + +class PgpKeyTest < SrpTest + +  setup do +    # todo: prepare user and login without doing the srp dance +    register_user +    authenticate +  end + +  test "upload pgp key" do +    update_user public_key: key +    assert_equal key, Identity.for(@user).keys[:pgp] +  end + +  # eventually probably want to remove most of this into a non-integration +  # functional test +  test "prevent uploading invalid key" do +    update_user public_key: "invalid key" +    assert_nil Identity.for(@user).keys[:pgp] +  end + +  test "prevent emptying public key" do +    update_user public_key: key +    update_user public_key: "" +    assert_equal key, Identity.for(@user).keys[:pgp] +  end + +  protected + +  def key +    @key ||= FactoryGirl.build :pgp_key +  end +end diff --git a/test/integration/api/python/flow_with_srp.py b/test/integration/api/python/flow_with_srp.py new file mode 100755 index 0000000..9fc168b --- /dev/null +++ b/test/integration/api/python/flow_with_srp.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# under development + +import requests +import json +import string +import random +import srp._pysrp as srp +import binascii + +safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) + +# using globals for now +# server = 'https://dev.bitmask.net/1' +server = 'http://api.lvh.me:3000/1' + +def run_tests(): +  login = 'test_' + id_generator() +  password = id_generator() + id_generator() +  usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) +  print_and_parse(signup(login, password)) + +  auth = print_and_parse(authenticate(usr)) +  verify_or_debug(auth, usr) +  assert usr.authenticated() + +  usr = change_password(auth['id'], login, auth['token']) + +  auth = print_and_parse(authenticate(usr)) +  verify_or_debug(auth, usr) +  # At this point the authentication process is complete. +  assert usr.authenticated() + +# let's have some random name +def id_generator(size=6, chars=string.ascii_lowercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +# log the server communication +def print_and_parse(response): +  request = response.request +  print request.method + ': ' + response.url +  if hasattr(request, 'data'): +    print "    " + json.dumps(response.request.data) +  print " -> " + response.text +  try:  +    return json.loads(response.text) +  except ValueError: +    return None + +def signup(login, password): +  salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) +  user_params = { +      'user[login]': login, +      'user[password_verifier]': binascii.hexlify(vkey), +      'user[password_salt]': binascii.hexlify(salt) +      } +  return requests.post(server + '/users.json', data = user_params, verify = False) + +def change_password(user_id, login, token): +  password = id_generator() + id_generator() +  salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) +  user_params = { +      'user[password_verifier]': binascii.hexlify(vkey), +      'user[password_salt]': binascii.hexlify(salt) +      } +  auth_headers = { 'Authorization': 'Token token="' + token + '"'} +  print user_params +  print_and_parse(requests.put(server + '/users/' + user_id + '.json', data = user_params, verify = False, headers = auth_headers)) +  return srp.User( login, password, srp.SHA256, srp.NG_1024 ) + + +def authenticate(usr): +  session = requests.session() +  uname, A = usr.start_authentication() +  params = { +      'login': uname, +      'A': binascii.hexlify(A) +      } +  init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) +  M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) +  return session.put(server + '/sessions/' + uname, verify = False, +      data = {'client_auth': binascii.hexlify(M)}) + +def verify_or_debug(auth, usr): +  if ( 'errors' in auth ): +    print '    u = "%x"' % usr.u +    print '    x = "%x"' % usr.x +    print '    v = "%x"' % usr.v +    print '    S = "%x"' % usr.S +    print '    K = "' + binascii.hexlify(usr.K) + '"' +    print '    M = "' + binascii.hexlify(usr.M) + '"' +  else: +    usr.verify_session( safe_unhexlify(auth["M2"]) ) + +run_tests() diff --git a/test/integration/api/python/login_wrong_username.py b/test/integration/api/python/login_wrong_username.py new file mode 100755 index 0000000..390f250 --- /dev/null +++ b/test/integration/api/python/login_wrong_username.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +params = { +    'login': 'python_test_user_'+id_generator(), +    'A': '12345', +    } +r = requests.post(server + '/sessions', data = params) +print r.url +print r.text diff --git a/test/integration/api/python/signup.py b/test/integration/api/python/signup.py new file mode 100755 index 0000000..0d3a4e0 --- /dev/null +++ b/test/integration/api/python/signup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +user_params = { +    'user[login]': 'python_test_user_'+id_generator(), +    'user[password_verifier]': '12345', +    'user[password_salt]': '54321' +    } +r = requests.post(server + '/users.json', data = user_params) +print r.url +print r.text diff --git a/test/integration/api/python/signup_and_login.py b/test/integration/api/python/signup_and_login.py new file mode 100755 index 0000000..ac611d7 --- /dev/null +++ b/test/integration/api/python/signup_and_login.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# FAILS +# +# This test is currently failing for me because the session is not kept. +# Played with it a bunch - is probably messed up right now as well. + + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +def print_and_parse(response): +  print response.request.method + ': ' + response.url +  print "    " + json.dumps(response.request.data) +  print " -> " + response.text +  return json.loads(response.text) + +def signup(session): +  user_params = { +      'user[login]': id_generator(), +      'user[password_verifier]': '12345', +      'user[password_salt]': 'AB54321' +      } +  return session.post(server + '/users.json', data = user_params) + +def authenticate(session, login): +  params = { +      'login': login, +      'A': '12345', +      } +  init = print_and_parse(session.post(server + '/sessions', data = params)) +  return session.put(server + '/sessions/' + login, data = {'client_auth': '123'}) + +session = requests.session() +user = print_and_parse(signup(session)) +# SRP signup would happen here and calculate M hex +auth = print_and_parse(authenticate(session, user['login'])) diff --git a/test/integration/api/python/signup_and_login_wrong_password.py b/test/integration/api/python/signup_and_login_wrong_password.py new file mode 100755 index 0000000..9efffa1 --- /dev/null +++ b/test/integration/api/python/signup_and_login_wrong_password.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +server = 'http://localhost:9292' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +def print_and_parse(response): +  print response.request.method + ': ' + response.url +  print "    " + json.dumps(response.request.data) +  print " -> " + response.text +#  print " () " + json.dumps(requests.utils.dict_from_cookiejar(response.cookies)) +  return json.loads(response.text) + +def signup(): +  user_params = { +      'user[login]': id_generator(), +      'user[password_verifier]': '12345', +      'user[password_salt]': '54321' +      } +  return requests.post(server + '/users.json', data = user_params) + +def handshake(login): +  params = { +      'login': login, +      'A': '12345', +      } +  return requests.post(server + '/sessions', data = params) + +def authenticate(login, M): +  return requests.put(server + '/sessions/' + login, data = {'M': M}) + + +user = print_and_parse(signup()) +handshake = print_and_parse(handshake(user['login'])) +# SRP signup would happen here and calculate M hex +M = '123ABC' +auth = print_and_parse(authenticate(user['login'], M)) diff --git a/test/integration/api/python/umlauts.py b/test/integration/api/python/umlauts.py new file mode 100755 index 0000000..96fecbf --- /dev/null +++ b/test/integration/api/python/umlauts.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# coding: utf-8 + +# under development + +import requests +import json +import string +import random +import srp._pysrp as srp +import binascii + +safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) + +# using globals for now +# server = 'https://dev.bitmask.net/1' +server = 'http://api.lvh.me:3000/1' + +def run_tests(): +  login = 'test_' + id_generator() +  password = id_generator() + "äöì" + id_generator() +  usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) +  print_and_parse(signup(login, password)) + +  auth = print_and_parse(authenticate(usr)) +  verify_or_debug(auth, usr) +  assert usr.authenticated() + + +# let's have some random name +def id_generator(size=6, chars=string.ascii_lowercase + string.digits): +  return ''.join(random.choice(chars) for x in range(size)) + +# log the server communication +def print_and_parse(response): +  request = response.request +  print request.method + ': ' + response.url +  if hasattr(request, 'data'): +    print "    " + json.dumps(response.request.data) +  print " -> " + response.text +  try:  +    return json.loads(response.text) +  except ValueError: +    return None + +def signup(login, password): +  salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) +  user_params = { +      'user[login]': login, +      'user[password_verifier]': binascii.hexlify(vkey), +      'user[password_salt]': binascii.hexlify(salt) +      } +  print json.dumps(user_params) +  return requests.post(server + '/users.json', data = user_params, verify = False) + +def authenticate(usr): +  session = requests.session() +  uname, A = usr.start_authentication() +  params = { +      'login': uname, +      'A': binascii.hexlify(A) +      } +  init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) +  M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) +  return session.put(server + '/sessions/' + uname, verify = False, +      data = {'client_auth': binascii.hexlify(M)}) + +def verify_or_debug(auth, usr): +  if ( 'errors' in auth ): +    print '    u = "%x"' % usr.u +    print '    x = "%x"' % usr.x +    print '    v = "%x"' % usr.v +    print '    S = "%x"' % usr.S +    print '    K = "' + binascii.hexlify(usr.K) + '"' +    print '    M = "' + binascii.hexlify(usr.M) + '"' +  else: +    usr.verify_session( safe_unhexlify(auth["M2"]) ) + +run_tests() diff --git a/test/integration/api/signup_test.rb b/test/integration/api/signup_test.rb new file mode 100644 index 0000000..236c547 --- /dev/null +++ b/test/integration/api/signup_test.rb @@ -0,0 +1,20 @@ +require 'test_helper' +require_relative 'srp_test' + +class SignupTest < SrpTest + +  setup do +    register_user +  end + +  test "signup response" do +    assert_json_response :login => @login, :ok => true +    assert last_response.successful? +  end + +  test "signup creates user" do +    assert @user +    assert_equal @login, @user.login +  end +end + diff --git a/test/integration/api/srp_test.rb b/test/integration/api/srp_test.rb new file mode 100644 index 0000000..26adc8c --- /dev/null +++ b/test/integration/api/srp_test.rb @@ -0,0 +1,105 @@ +class SrpTest < RackTest +  include AssertResponses + +  teardown do +    if @user +      cleanup_user +    end +    Warden.test_reset! +  end + +  # this test wraps the api and implements the interface the ruby-srp client. +  def handshake(login, aa) +    post "http://api.lvh.me:3000/1/sessions.json", +      :login => login, +      'A' => aa, +      :format => :json +    response = JSON.parse(last_response.body) +    if response['errors'] +      raise RECORD_NOT_FOUND.new(response['errors']) +    else +      return response['B'] +    end +  end + +  def validate(m) +    put "http://api.lvh.me:3000/1/sessions/" + @login + '.json', +      :client_auth => m, +      :format => :json +    return JSON.parse(last_response.body) +  end + +  protected + +  attr_reader :server_auth + +  def register_user(login = "integration_test_user", password = 'srp, verify me!') +    cleanup_user(login) +    post 'http://api.lvh.me:3000/1/users.json', +      user_params(login: login, password: password) +    @user = User.find_by_login(login) +    @login = login +    @password = password +  end + +  def update_user(params) +    put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', +      user_params(params), +      auth_headers +  end + +  def authenticate(params = nil) +    @server_auth = srp(params).authenticate(self) +  end + +  def auth_headers +    return {} if @server_auth.nil? +    { +      "HTTP_AUTHORIZATION" => encoded_token +    } +  end + +  def encoded_token +    ActionController::HttpAuthentication::Token.encode_credentials(server_auth["token"]) +  end + +  def logout(params=nil, headers=nil) +    delete "http://api.lvh.me:3000/1/logout.json", +      params || {format: :json}, +      headers || auth_headers +  end + +  def cleanup_user(login = nil) +    login ||= @user.login +    Identity.by_address.key(login + '@' + APP_CONFIG[:domain]).each do |identity| +      identity.destroy +    end +    if user = User.find_by_login(login) +      user.destroy +    end +  end + +  def user_params(params) +    if params.keys.include?(:password) +      srp_process_password(params) +    end +    return { user: params, format: :json } +  end + +  def srp_process_password(params) +    params.reverse_merge! login: @login, salt: @salt +    @srp = SRP::Client.new params[:login], password: params.delete(:password) +    @salt = srp.salt.to_s(16) +    params.merge! :password_verifier => srp.verifier.to_s(16), +      :password_salt => @salt +  end + +  def srp(params = nil) +    if params.nil? +      @srp +    else +      params.reverse_merge! password: @password +      SRP::Client.new(params.delete(:login) || @login, params) +    end +  end +end diff --git a/test/integration/api/update_account_test.rb b/test/integration/api/update_account_test.rb new file mode 100644 index 0000000..63429e7 --- /dev/null +++ b/test/integration/api/update_account_test.rb @@ -0,0 +1,51 @@ +require 'test_helper' +require_relative 'srp_test' + +class UpdateAccountTest < SrpTest + +  setup do +    register_user +  end + +  test "require authentication" do +    update_user password: "No! Verify me instead." +    assert_access_denied +  end + +  test "require token" do +    authenticate +    put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', +      user_params(password: "No! Verify me instead.") +    assert_access_denied +  end + +  test "update password via api" do +    authenticate +    update_user password: "No! Verify me instead." +    authenticate +    assert last_response.successful? +    assert_nil server_auth["errors"] +    assert server_auth["M2"] +  end + +  test "change login with password_verifier" do +    authenticate +    new_login = 'zaph' +    cleanup_user new_login +    update_user login: new_login, password: @password +    authenticate +    assert last_response.successful? +    assert_equal new_login, @user.reload.login +  end + +  test "prevent changing login without changing password_verifier" do +    authenticate +    original_login = @user.login +    new_login = 'zaph' +    cleanup_user new_login +    update_user login: new_login +    assert last_response.successful? +    # does not change login if no password_verifier is present +    assert_equal original_login, @user.reload.login +  end +end diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb new file mode 100644 index 0000000..a5677ad --- /dev/null +++ b/test/integration/browser/account_test.rb @@ -0,0 +1,147 @@ +require 'test_helper' + +class AccountTest < BrowserIntegrationTest + +  teardown do +    Identity.destroy_all_disabled +  end + +  test "normal account workflow" do +    username, password = submit_signup +    assert page.has_content?("Welcome #{username}") +    click_on 'Logout' +    assert page.has_content?("Log In") +    assert_equal '/', current_path +    assert user = User.find_by_login(username) +    user.account.destroy +  end + +  test "successful login" do +    username, password = submit_signup +    click_on 'Logout' +    attempt_login(username, password) +    assert page.has_content?("Welcome #{username}") +    within('.sidenav li.active') do +      assert page.has_content?("Overview") +    end +    User.find_by_login(username).account.destroy +  end + +  test "failed login" do +    visit '/' +    attempt_login("username", "wrong password") +    assert_invalid_login(page) +  end + +  test "account destruction" do +    username, password = submit_signup +    click_on I18n.t('account_settings') +    click_on I18n.t('destroy_my_account') +    assert page.has_content?(I18n.t('account_destroyed')) +    attempt_login(username, password) +    assert_invalid_login(page) +  end + +  test "handle blocked after account destruction" do +    username, password = submit_signup +    click_on I18n.t('account_settings') +    click_on I18n.t('destroy_my_account') +    submit_signup(username) +    assert page.has_content?('has already been taken') +  end + +  test "default user actions" do +    username, password = submit_signup +    click_on "Account Settings" +    assert page.has_content? I18n.t('destroy_my_account') +    assert page.has_no_css? '#update_login_and_password' +    assert page.has_no_css? '#update_pgp_key' +  end + +  test "default admin actions" do +    username, password = submit_signup +    with_config admins: [username] do +      click_on "Account Settings" +      assert page.has_content? I18n.t('destroy_my_account') +      assert page.has_no_css? '#update_login_and_password' +      assert page.has_css? '#update_pgp_key' +    end +  end + +  test "change password" do +    with_config user_actions: ['change_password'] do +      username, password = submit_signup +      click_on "Account Settings" +      within('#update_login_and_password') do +        fill_in 'Password', with: "other password" +        fill_in 'Password confirmation', with: "other password" +        click_on 'Save' +      end +      click_on 'Logout' +      attempt_login(username, "other password") +      assert page.has_content?("Welcome #{username}") +      User.find_by_login(username).account.destroy +    end +  end + +  test "change pgp key" do +    with_config user_actions: ['change_pgp_key'] do +      pgp_key = FactoryGirl.build :pgp_key +      username, password = submit_signup +      click_on "Account Settings" +      within('#update_pgp_key') do +        fill_in 'Public key', with: pgp_key +        click_on 'Save' +      end +      page.assert_selector 'input[value="Saving..."]' +      # at some point we're done: +      page.assert_no_selector 'input[value="Saving..."]' +      assert page.has_field? 'Public key', with: pgp_key.to_s +      user = User.find_by_login(username) +      assert_equal pgp_key, user.public_key +      user.account.destroy +    end +  end + + +  # trying to seed an invalid A for srp login +  test "detects attempt to circumvent SRP" do +    user = FactoryGirl.create :user +    visit '/login' +    fill_in 'Username', with: user.login +    fill_in 'Password', with: "password" +    inject_malicious_js +    click_on 'Log In' +    assert page.has_content?("Invalid random key") +    assert page.has_no_content?("Welcome") +    user.destroy +  end + +  test "reports internal server errors" do +    V1::UsersController.any_instance.stubs(:create).raises +    submit_signup +    assert page.has_content?("server failed") +  end + +  def attempt_login(username, password) +    click_on 'Log In' +    fill_in 'Username', with: username +    fill_in 'Password', with: password +    click_on 'Log In' +  end + +  def assert_invalid_login(page) +    assert page.has_selector? 'input.btn-primary.disabled' +    assert page.has_content? I18n.t(:invalid_user_pass) +    assert page.has_no_selector? 'input.btn-primary.disabled' +  end + +  def inject_malicious_js +    page.execute_script <<-EOJS +      var calc = new srp.Calculate(); +      calc.A = function(_a) {return "00";}; +      calc.S = calc.A; +      srp.session = new srp.Session(null, calc); +    EOJS +  end +end diff --git a/test/integration/browser/session_test.rb b/test/integration/browser/session_test.rb new file mode 100644 index 0000000..3a41b3a --- /dev/null +++ b/test/integration/browser/session_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class SessionTest < BrowserIntegrationTest + +  setup do +    @username, password = submit_signup +  end + +  teardown do +    user = User.find_by_login(@username) +    id = user.identity +    id.destroy +    user.destroy +  end + +  test "valid session" do +    assert page.has_content?("Welcome #{@username}") +  end + +  test "expired session" do +    assert page.has_content?("Welcome #{@username}") +    pretend_now_is(Time.now + 40.minutes) do +      visit '/' +      assert page.has_no_content?("Welcome #{@username}") +    end +  end +end diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb new file mode 100644 index 0000000..eec8c0e --- /dev/null +++ b/test/integration/navigation_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + +  # test "the truth" do +  #   assert true +  # end +end + diff --git a/test/leap_web_users_test.rb b/test/leap_web_users_test.rb new file mode 100644 index 0000000..f142e54 --- /dev/null +++ b/test/leap_web_users_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class LeapWebUsersTest < ActiveSupport::TestCase +  test "module exists" do +    assert_kind_of Module, LeapWebUsers +  end +end diff --git a/test/support/assert_responses.rb b/test/support/assert_responses.rb new file mode 100644 index 0000000..b01166f --- /dev/null +++ b/test/support/assert_responses.rb @@ -0,0 +1,46 @@ +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}"), +      get_response.headers["Content-Disposition"] +  end + +  def json_response +    response = JSON.parse(get_response.body) +    response.respond_to?(:with_indifferent_access) ? +      response.with_indifferent_access : +      response +  end + +  def assert_json_response(object) +    assert_equal 'application/json', +      get_response.content_type.to_s.split(';').first +    if object.is_a? Hash +      object.stringify_keys! if object.respond_to? :stringify_keys! +      assert_equal object, json_response +    else +      assert_equal object.to_json, get_response.body +    end +  end + +  def assert_json_error(object) +    object.stringify_keys! if object.respond_to? :stringify_keys! +    assert_json_response :errors => object +  end +end + +class ::ActionController::TestCase +  include AssertResponses +end + +class ::ActionDispatch::IntegrationTest +  include AssertResponses +end diff --git a/test/support/auth_test_helper.rb b/test/support/auth_test_helper.rb new file mode 100644 index 0000000..57f9f9b --- /dev/null +++ b/test/support/auth_test_helper.rb @@ -0,0 +1,65 @@ +module AuthTestHelper +  extend ActiveSupport::Concern + +  # 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 login(user_or_method_hash = {}) +    if user_or_method_hash.respond_to?(:reverse_merge) +      user_or_method_hash.reverse_merge! :is_admin? => false +    end +    @current_user = stub_record(:user, user_or_method_hash) +    request.env['warden'] = stub :user => @current_user +    request.env['HTTP_AUTHORIZATION'] = header_for_token_auth +    return @current_user +  end + +  def assert_access_denied(denied = true, logged_in = true) +    if denied +      if @response.content_type == 'application/json' +        assert_json_response('error' => I18n.t(:not_authorized)) +        assert_response :unprocessable_entity +      else +        if logged_in +          assert_equal({:alert => I18n.t(:not_authorized)}, flash.to_hash) +          assert_redirected_to home_url +        else +          assert_equal({:alert => I18n.t(:not_authorized_login)}, flash.to_hash) +          assert_redirected_to login_url +        end +      end +    else +      assert flash[:alert].blank? +    end +  end + +  def expect_logout +    expect_warden_logout +    @token.expects(:destroy) if @token +  end + +  protected + +  def header_for_token_auth +    @token = find_record(:token, :authenticate => @current_user) +    ActionController::HttpAuthentication::Token.encode_credentials @token.id +  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 + +class ActionController::TestCase +  include AuthTestHelper +end diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb new file mode 100644 index 0000000..2885c3a --- /dev/null +++ b/test/support/browser_integration_test.rb @@ -0,0 +1,81 @@ +# +# BrowserIntegrationTest +# +# Use this class for capybara based integration tests for the ui. +# + +class BrowserIntegrationTest < ActionDispatch::IntegrationTest + +  CONFIG_RU = (Rails.root + 'config.ru').to_s +  OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first + +  require 'capybara/poltergeist' + +  Capybara.register_driver :rack_test do |app| +    Capybara::RackTest::Driver.new(app) +  end + +  Capybara.register_driver :poltergeist do |app| +    Capybara::Poltergeist::Driver.new(app) +  end + +  # this is integration testing. So let's make the whole +  # rack stack available... +  Capybara.app = OUTER_APP +  Capybara.run_server = true +  Capybara.app_host = 'http://lvh.me:3003' +  Capybara.server_port = 3003 +  Capybara.javascript_driver = :poltergeist +  Capybara.default_wait_time = 5 + + +  # Make the Capybara DSL available +  include Capybara::DSL + +  setup do +    Capybara.current_driver = Capybara.javascript_driver +    page.driver.add_headers 'ACCEPT-LANGUAGE' => 'en-EN' +  end + +  teardown do +    Capybara.reset_sessions!    # Forget the (simulated) browser state +    Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver +  end + +  def submit_signup(username = nil, password = nil) +    username ||= "test_#{SecureRandom.urlsafe_base64}".downcase +    password ||= SecureRandom.base64 +    visit '/users/new' +    fill_in 'Username', with: username +    fill_in 'Password', with: password +    fill_in 'Password confirmation', with: password +    click_on 'Sign Up' +    return username, password +  end + +  add_teardown_hook do |testcase| +    unless testcase.passed? +      testcase.save_state +    end +  end + +  def save_state +    page.save_screenshot screenshot_path +    File.open(logfile_path, 'w') do |test_log| +      test_log.puts self.class.name +      test_log.puts "=========================" +      test_log.puts __name__ +      test_log.puts Time.now +      test_log.puts current_path +      test_log.puts page.status_code +      test_log.puts page.response_headers +      test_log.puts "page.html" +      test_log.puts "------------------------" +      test_log.puts page.html +      test_log.puts "server log" +      test_log.puts "------------------------" +      test_log.puts `tail log/test.log -n 200` +    end +  end + +end diff --git a/test/support/rack_test.rb b/test/support/rack_test.rb new file mode 100644 index 0000000..806339a --- /dev/null +++ b/test/support/rack_test.rb @@ -0,0 +1,38 @@ +require_relative 'assert_responses' + +class RackTest < ActiveSupport::TestCase +  include Rack::Test::Methods +  include Warden::Test::Helpers + +  CONFIG_RU = (Rails.root + 'config.ru').to_s +  OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first + +  def app +    OUTER_APP +  end + +  def assert_access_denied +    assert_json_response('error' => I18n.t(:not_authorized)) +    assert_response :unprocessable_entity +  end + +  # inspired by rails 4 +  # -> actionpack/lib/action_dispatch/testing/assertions/response.rb +  def assert_response(type, message = nil) +    # RackTest does not know @response +    response_code = last_response.status +    message ||= "Expected response to be a <#{type}>, but was <#{response_code}>" + +    if Symbol === type +      if [:success, :missing, :redirect, :error].include?(type) +        assert last_response.send("#{type}?"), message +      else +        code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type] +        assert_equal code, response_code, message +      end +    else +      assert_equal type, response_code, message +    end +  end + +end diff --git a/test/support/stub_record_helper.rb b/test/support/stub_record_helper.rb new file mode 100644 index 0000000..25138a0 --- /dev/null +++ b/test/support/stub_record_helper.rb @@ -0,0 +1,53 @@ +module StubRecordHelper + +  # +  # We will stub find when called on the records class and +  # return the record given. +  # +  # If no record is given but a hash or nil will create a stub based on +  # that instead and returns the stub. +  # +  def find_record(factory, record_or_attribs_hash = {}) +    record = stub_record factory, record_or_attribs_hash, true +    klass = record.class +    # find is just an alias for get with CouchRest Model +    klass.stubs(:get).with(record.to_param.to_s).returns(record) +    klass.stubs(:find).with(record.to_param.to_s).returns(record) +    return record +  end + +  # Create a stub that has the usual functions of a database record. +  # It won't fail on rendering a form for example. +  # +  # If the second parameter is a record we return the record itself. +  # This way you can build functions that either take a record or a +  # method hash to stub from. See find_record for an example. +  def stub_record(factory, record_or_method_hash = {}, persisted=false) +    if record_or_method_hash && !record_or_method_hash.is_a?(Hash) +      return record_or_method_hash +    end +    FactoryGirl.build_stubbed(factory).tap do |record| +      if persisted or record.persisted? +        record_or_method_hash.reverse_merge! :created_at => Time.now, +          :updated_at => Time.now, :id => Random.rand(100000).to_s +      end +      record.stubs(record_or_method_hash) if record_or_method_hash.present? +    end +  end + +  # returns deep stringified attributes so they can be compared to +  # what the controller receives as params +  def record_attributes_for(factory, attribs_hash = nil) +    FactoryGirl.attributes_for(factory, attribs_hash).tap do |attribs| +      attribs.keys.each do |key| +        val = attribs.delete(key) +        attribs[key.to_s] = val.is_a?(Hash) ? val.stringify_keys! : val +      end +    end +  end + +end + +class ActionController::TestCase +  include StubRecordHelper +end diff --git a/test/support/time_test_helper.rb b/test/support/time_test_helper.rb new file mode 100644 index 0000000..f673f12 --- /dev/null +++ b/test/support/time_test_helper.rb @@ -0,0 +1,30 @@ +# Extend the Time class so that we can offset the time that 'now' +# returns.  This should allow us to effectively time warp for functional +# tests that require limits per hour, what not. +class Time #:nodoc: +  class <<self +    attr_accessor :testing_offset + +    def now_with_testing_offset +      now_without_testing_offset - testing_offset +    end +    alias_method_chain :now, :testing_offset +  end +end +Time.testing_offset = 0 + +module TimeTestHelper +  # Time warp to the specified time for the duration of the passed block +  def pretend_now_is(time) +    begin +      Time.testing_offset = Time.now - time +      yield +    ensure +      Time.testing_offset = 0 +    end +  end +end + +class ActiveSupport::TestCase +  include TimeTestHelper +end diff --git a/test/support/with_config_helper.rb b/test/support/with_config_helper.rb new file mode 100644 index 0000000..65eb7bc --- /dev/null +++ b/test/support/with_config_helper.rb @@ -0,0 +1,16 @@ +module WithConfigHelper +  extend ActiveSupport::Concern + +  def with_config(options) +    old_config = APP_CONFIG.dup +    APP_CONFIG.merge! options +    yield +  ensure +    APP_CONFIG.replace old_config +  end + +end + +class ActiveSupport::TestCase +  include WithConfigHelper +end diff --git a/test/test_helper.rb b/test/test_helper.rb index f63591f..d001ac7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,8 +4,11 @@ require 'rails/test_help'  require 'mocha/setup' +# Load support files from toplevel +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } +  # Load support files from all engines -Dir["#{File.dirname(__FILE__)}/../*/test/support/**/*.rb"].each { |f| require f } +Dir["#{File.dirname(__FILE__)}/../engines/*/test/support/**/*.rb"].each { |f| require f }  class ActiveSupport::TestCase    # Add more helper methods to be used by all tests here... diff --git a/test/unit/account_test.rb b/test/unit/account_test.rb new file mode 100644 index 0000000..4fb3c3d --- /dev/null +++ b/test/unit/account_test.rb @@ -0,0 +1,47 @@ +require 'test_helper' + +class AccountTest < ActiveSupport::TestCase + +  teardown do +    Identity.destroy_all_disabled +  end + +  test "create a new account" do +    user = Account.create(FactoryGirl.attributes_for(:user)) +    assert user.valid? +    assert user.persisted? +    assert id = user.identity +    assert_equal user.email_address, id.address +    assert_equal user.email_address, id.destination +    user.account.destroy +  end + +  test "create and remove a user account" do +    # We keep an identity that will block the handle from being reused. +    assert_difference "Identity.count" do +      assert_no_difference "User.count" do +        user = Account.create(FactoryGirl.attributes_for(:user)) +        user.account.destroy +      end +    end +  end + +  test "change username and create alias" do +    user = Account.create(FactoryGirl.attributes_for(:user)) +    old_id = user.identity +    old_email = user.email_address +    user.account.update(FactoryGirl.attributes_for(:user)) +    user.reload +    old_id.reload +    assert user.valid? +    assert user.persisted? +    assert id = user.identity +    assert id.persisted? +    assert_equal user.email_address, id.address +    assert_equal user.email_address, id.destination +    assert_equal user.email_address, old_id.destination +    assert_equal old_email, old_id.address +    user.account.destroy +  end + +end diff --git a/test/unit/client_certificate_test.rb b/test/unit/client_certificate_test.rb new file mode 100644 index 0000000..036e724 --- /dev/null +++ b/test/unit/client_certificate_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +class ClientCertificateTest < ActiveSupport::TestCase + +  test "new cert has all we need" do +    sample = ClientCertificate.new +    assert sample.key +    assert sample.cert +    assert sample.to_s +  end + +  test "cert has configured prefix" do +    prefix = "PREFIX" +    sample = ClientCertificate.new(:prefix => prefix) +    assert sample.cert.subject.common_name.starts_with?(prefix) +  end + +  test "cert issuer matches ca subject" do +    sample = ClientCertificate.new +    cert = OpenSSL::X509::Certificate.new(sample.cert.to_pem) +    assert_equal ClientCertificate.root_ca.openssl_body.subject, cert.issuer +  end + +end diff --git a/test/unit/helpers/session_helper_test.rb b/test/unit/helpers/session_helper_test.rb new file mode 100644 index 0000000..2824733 --- /dev/null +++ b/test/unit/helpers/session_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class SessionHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/users_helper_test.rb b/test/unit/helpers/users_helper_test.rb new file mode 100644 index 0000000..96af37a --- /dev/null +++ b/test/unit/helpers/users_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class UsersHelperTest < ActionView::TestCase +end diff --git a/test/unit/identity_test.rb b/test/unit/identity_test.rb new file mode 100644 index 0000000..eca104f --- /dev/null +++ b/test/unit/identity_test.rb @@ -0,0 +1,133 @@ +require 'test_helper' + +class IdentityTest < ActiveSupport::TestCase +  include StubRecordHelper + +  setup do +    @user = find_record :user +  end + +  test "initial identity for a user" do +    id = Identity.for(@user) +    assert_equal @user.email_address, id.address +    assert_equal @user.email_address, id.destination +    assert_equal @user, id.user +  end + +  test "add alias" do +    id = Identity.for @user, address: alias_name +    assert_equal LocalEmail.new(alias_name), id.address +    assert_equal @user.email_address, id.destination +    assert_equal @user, id.user +  end + +  test "add forward" do +    id = Identity.for @user, destination: forward_address +    assert_equal @user.email_address, id.address +    assert_equal Email.new(forward_address), id.destination +    assert_equal @user, id.user +  end + +  test "forward alias" do +    id = Identity.for @user, address: alias_name, destination: forward_address +    assert_equal LocalEmail.new(alias_name), id.address +    assert_equal Email.new(forward_address), id.destination +    assert_equal @user, id.user +  end + +  test "prevents duplicates" do +    id = Identity.create_for @user, address: alias_name, destination: forward_address +    dup = Identity.build_for @user, address: alias_name, destination: forward_address +    assert !dup.valid? +    assert_equal ["This alias already exists"], dup.errors[:base] +    id.destroy +  end + +  test "validates availability" do +    other_user = find_record :user +    id = Identity.create_for @user, address: alias_name, destination: forward_address +    taken = Identity.build_for other_user, address: alias_name +    assert !taken.valid? +    assert_equal ["This email has already been taken"], taken.errors[:base] +    id.destroy +  end + +  test "setting and getting pgp key" do +    id = Identity.for(@user) +    id.set_key(:pgp, pgp_key_string) +    assert_equal pgp_key_string, id.keys[:pgp] +  end + +  test "querying pgp key via couch" do +    id = Identity.for(@user) +    id.set_key(:pgp, pgp_key_string) +    id.save +    view = Identity.pgp_key_by_email.key(id.address) +    assert_equal 1, view.rows.count +    assert result = view.rows.first +    assert_equal id.address, result["key"] +    assert_equal id.keys[:pgp], result["value"] +    id.destroy +  end + +  test "fail to add non-local email address as identity address" do +    id = Identity.for @user, address: forward_address +    assert !id.valid? +    assert_match /needs to end in/, id.errors[:address].first +  end + +  test "alias must meet same conditions as login" do +    id = Identity.create_for @user, address: alias_name.capitalize +    assert !id.valid? +    #hacky way to do this, but okay for now: +    assert id.errors.messages.flatten(2).include? "Must begin with a lowercase letter" +    assert id.errors.messages.flatten(2).include? "Only lowercase letters, digits, . - and _ allowed." +  end + +  test "destination must be valid email address" do +    id = Identity.create_for @user, address: @user.email_address, destination: 'ASKJDLFJD' +    assert !id.valid? +    assert id.errors.messages[:destination].include? "needs to be a valid email address" +  end + +  test "disabled identity" do +    id = Identity.for(@user) +    id.disable +    assert_equal @user.email_address, id.address +    assert_equal nil, id.destination +    assert_equal nil, id.user +    assert !id.enabled? +    assert id.valid? +  end + +  test "disabled identity blocks handle" do +    id = Identity.for(@user) +    id.disable +    id.save +    other_user = find_record :user +    taken = Identity.build_for other_user, address: id.address +    assert !taken.valid? +    Identity.destroy_all_disabled +  end + +  test "destroy all disabled identities" do +    id = Identity.for(@user) +    id.disable +    id.save +    assert Identity.count > 0 +    Identity.destroy_all_disabled +    assert_equal 0, Identity.disabled.count +  end + +  def alias_name +    @alias_name ||= Faker::Internet.user_name +  end + +  def forward_address +    @forward_address ||= Faker::Internet.email +  end + +  def pgp_key_string +    @pgp_key ||= "DUMMY PGP KEY ... "+SecureRandom.base64(4096) +  end +end diff --git a/test/unit/local_email_test.rb b/test/unit/local_email_test.rb new file mode 100644 index 0000000..20ee7f1 --- /dev/null +++ b/test/unit/local_email_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +class LocalEmailTest < ActiveSupport::TestCase + +  test "appends domain" do +    local = LocalEmail.new(handle) +    assert_equal LocalEmail.new(email), local +    assert local.valid? +  end + +  test "returns handle" do +    local = LocalEmail.new(email) +    assert_equal handle, local.handle +  end + +  test "prints full email" do +    local = LocalEmail.new(handle) +    assert_equal email, "#{local}" +  end + +  test "validates domain" do +    local = LocalEmail.new(Faker::Internet.email) +    assert !local.valid? +    assert_equal ["needs to end in @#{LocalEmail.domain}"], local.errors[:email] +  end + +  test "blacklists rfc2142" do +    black_listed = LocalEmail.new('hostmaster') +    assert !black_listed.valid? +  end + +  test "blacklists etc passwd" do +    black_listed = LocalEmail.new('nobody') +    assert !black_listed.valid? +  end + +  test "whitelist overwrites automatic blacklists" do +    with_config handle_whitelist: ['nobody', 'hostmaster'] do +      white_listed = LocalEmail.new('nobody') +      assert white_listed.valid? +      white_listed = LocalEmail.new('hostmaster') +      assert white_listed.valid? +    end +  end + +  test "blacklists from config" do +    black_listed = LocalEmail.new('www-data') +    assert !black_listed.valid? +  end + +  test "blacklist from config overwrites whitelist" do +    with_config handle_whitelist: ['www-data'] do +      black_listed = LocalEmail.new('www-data') +      assert !black_listed.valid? +    end +  end + +  def handle +    @handle ||= Faker::Internet.user_name +  end + +  def email +    handle + "@" + APP_CONFIG[:domain] +  end +end diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb new file mode 100644 index 0000000..a3c6cf6 --- /dev/null +++ b/test/unit/token_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +class ClientCertificateTest < ActiveSupport::TestCase +  include StubRecordHelper + +  setup do +    @user = find_record :user +  end + +  test "new token for user" do +    sample = Token.new(:user_id => @user.id) +    assert sample.valid? +    assert_equal @user.id, sample.user_id +    assert_equal @user, sample.authenticate +  end + +  test "token id is secure" do +    sample = Token.new(:user_id => @user.id) +    other = Token.new(:user_id => @user.id) +    assert sample.id, +      "id is set on initialization" +    assert sample.id[0..10] != other.id[0..10], +      "token id prefixes should not repeat" +    assert /[g-zG-Z]/.match(sample.id), +      "should use non hex chars in the token id" +    assert sample.id.size > 16, +      "token id should be more than 16 chars long" +  end + +  test "token checks for user" do +    sample = Token.new +    assert !sample.valid?, "Token should require a user record" +  end + +  test "token updates timestamps" do +    sample = Token.new(user_id: @user.id) +    sample.last_seen_at = 1.minute.ago +    sample.expects(:save) +    assert_equal @user, sample.authenticate +    assert Time.now - sample.last_seen_at < 1.minute, "last_seen_at has not been updated" +  end + +  test "token will not expire if token_expires_after is not set" do +    sample = Token.new(user_id: @user.id) +    sample.last_seen_at = 2.years.ago +    with_config auth: {} do +      sample.expects(:save) +      assert_equal @user, sample.authenticate +    end +  end + +  test "expired token returns nil on authenticate" do +    sample = Token.new(user_id: @user.id) +    sample.last_seen_at = 2.hours.ago +    with_config auth: {token_expires_after: 60} do +      sample.expects(:destroy) +      assert_nil sample.authenticate +    end +  end + +  test "Token.destroy_all_expired is noop if no expiry is set" do +    expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago +    with_config auth: {} do +      Token.destroy_all_expired +    end +    assert_equal expired, Token.find(expired.id) +  end + +  test "Token.destroy_all_expired cleans up expired tokens only" do +    expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago +    fresh = FactoryGirl.create :token +    with_config auth: {token_expires_after: 60} do +      Token.destroy_all_expired +    end +    assert_nil Token.find(expired.id) +    assert_equal fresh, Token.find(fresh.id) +    fresh.destroy +  end + + +  test "Token.destroy_all_expired does not interfere with expired.authenticate" do +    expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago +    with_config auth: {token_expires_after: 60} do +      Token.destroy_all_expired +    end +    assert_nil expired.authenticate +  end + +end diff --git a/test/unit/unauthenticated_user_test.rb b/test/unit/unauthenticated_user_test.rb new file mode 100644 index 0000000..e5fafb8 --- /dev/null +++ b/test/unit/unauthenticated_user_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UnauthenticatedUserTest < ActiveSupport::TestCase +  # test "the truth" do +  #   assert true +  # end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..ffbb7d8 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + +  include SRP::Util +  setup do +    @user = FactoryGirl.build(:user) +  end + +  test "design docs in database are authorative" do +    assert !User.design_doc.auto_update, +      "Automatic update of design docs should be disabled" +  end + +  test "test set of attributes should be valid" do +    @user.valid? +    assert_equal Hash.new, @user.errors.messages +  end + +  test "test require hex for password_verifier" do +    @user.password_verifier = "QWER" +    assert !@user.valid? +  end + +  test "test require alphanumerical for login" do +    @user.login = "qw#r" +    assert !@user.valid? +  end + +  test "verifier returns number for the hex in password_verifier" do +    assert_equal @user.password_verifier.hex, @user.verifier +  end + +  test "salt returns number for the hex in password_salt" do +    assert_equal @user.password_salt.hex, @user.salt +  end + +  test 'normal user is no admin' do +    assert !@user.is_admin? +  end + +  test 'user with login in APP_CONFIG is an admin' do +    admin_login = APP_CONFIG['admins'].first +    @user.login = admin_login +    assert @user.is_admin? +  end + +  test "login needs to be unique" do +    other_user = FactoryGirl.create :user, login: @user.login +    assert !@user.valid? +    other_user.destroy +  end + +  test "login needs to be unique amongst aliases" do +    other_user = FactoryGirl.create :user +    id = Identity.create_for other_user, address: @user.login +    assert !@user.valid? +    id.destroy +    other_user.destroy +  end + +  test "deprecated public key api still works" do +    key = SecureRandom.base64(4096) +    @user.public_key = key +    assert_equal key, @user.public_key +  end + +end diff --git a/test/unit/warden_strategy_secure_remote_password_test.rb b/test/unit/warden_strategy_secure_remote_password_test.rb new file mode 100644 index 0000000..e6fcfbe --- /dev/null +++ b/test/unit/warden_strategy_secure_remote_password_test.rb @@ -0,0 +1,63 @@ +class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase + +# TODO : turn this into sth. real +=begin +  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).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).with(unknown).raises(RECORD_NOT_FOUND) +    post :create, :login => unknown +    assert_response :success +    assert_json_error "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_error "password" => ["wrong password"] +  end + +=end +end diff --git a/test/unit/webfinger/host_meta_presenter_test.rb b/test/unit/webfinger/host_meta_presenter_test.rb new file mode 100644 index 0000000..af86404 --- /dev/null +++ b/test/unit/webfinger/host_meta_presenter_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' +require 'webfinger' +require 'json' + +class Webfinger::HostMetaPresenterTest < ActiveSupport::TestCase + +  setup do +    @request = stub( +      url: "https://#{APP_CONFIG[:domain]}/.well-known/host-meta" +    ) +    @meta = Webfinger::HostMetaPresenter.new(@request) +  end + +  test "creates proper json" do +    hash = JSON.parse @meta.to_json +    assert_equal ["subject", "links"].sort, hash.keys.sort +    hash.each do |key, value| +      assert_equal @meta.send(key.to_sym).to_json, value.to_json +    end +  end + +end + + diff --git a/test/unit/webfinger/user_presenter_test.rb b/test/unit/webfinger/user_presenter_test.rb new file mode 100644 index 0000000..04aeb22 --- /dev/null +++ b/test/unit/webfinger/user_presenter_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' +require 'webfinger' +require 'json' + +class Webfinger::UserPresenterTest < ActiveSupport::TestCase + + +  setup do +    @user = stub( +      username: 'testuser', +      email_address: "testuser@#{APP_CONFIG[:domain]}" +    ) +    @request = stub( +      host: APP_CONFIG[:domain] +    ) +  end + +  test "user without key has no links" do +    @user.stubs :public_key => nil +    presenter = Webfinger::UserPresenter.new(@user, @request) +    assert_equal Hash.new, presenter.links +  end + +  test "user with key has corresponding link" do +    @user.stubs :public_key => "here's a key" +    presenter = Webfinger::UserPresenter.new(@user, @request) +    assert_equal [:public_key], presenter.links.keys +    assert_equal "PGP", presenter.links[:public_key][:type] +    assert_equal presenter.send(:key), presenter.links[:public_key][:href] +  end + +  test "key is base64 encoded" do +    @user.stubs :public_key => "here's a key" +    presenter = Webfinger::UserPresenter.new(@user, @request) +    assert_equal Base64.encode64(@user.public_key), presenter.send(:key) +  end + +  test "creates proper json representation" do +    @user.stubs :public_key => "here's a key" +    presenter = Webfinger::UserPresenter.new(@user, @request) +    hash = JSON.parse presenter.to_json +    assert_equal ["subject", "links"].sort, hash.keys.sort +    hash.each do |key, value| +      assert_equal presenter.send(key.to_sym).to_json, value.to_json +    end +  end + + +end | 
