summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/factories.rb34
-rw-r--r--test/functional/application_controller_test.rb28
-rw-r--r--test/functional/helper_methods_test.rb39
-rw-r--r--test/functional/keys_controller_test.rb32
-rw-r--r--test/functional/sessions_controller_test.rb59
-rw-r--r--test/functional/test_helpers_test.rb38
-rw-r--r--test/functional/users_controller_test.rb165
-rw-r--r--test/functional/v1/messages_controller_test.rb57
-rw-r--r--test/functional/v1/sessions_controller_test.rb62
-rw-r--r--test/functional/v1/users_controller_test.rb74
-rw-r--r--test/functional/webfinger_controller_test.rb33
-rw-r--r--test/integration/api/Readme.md23
-rw-r--r--test/integration/api/login_test.rb50
-rw-r--r--test/integration/api/pgp_key_test.rb35
-rwxr-xr-xtest/integration/api/python/flow_with_srp.py96
-rwxr-xr-xtest/integration/api/python/login_wrong_username.py19
-rwxr-xr-xtest/integration/api/python/signup.py20
-rwxr-xr-xtest/integration/api/python/signup_and_login.py44
-rwxr-xr-xtest/integration/api/python/signup_and_login_wrong_password.py43
-rwxr-xr-xtest/integration/api/python/umlauts.py79
-rw-r--r--test/integration/api/signup_test.rb20
-rw-r--r--test/integration/api/srp_test.rb104
-rw-r--r--test/integration/api/update_account_test.rb51
-rw-r--r--test/integration/browser/account_test.rb147
-rw-r--r--test/integration/browser/session_test.rb27
-rw-r--r--test/integration/navigation_test.rb9
-rw-r--r--test/leap_web_users_test.rb7
-rw-r--r--test/support/auth_test_helper.rb65
-rw-r--r--test/support/stub_record_helper.rb53
-rw-r--r--test/support/time_test_helper.rb30
-rw-r--r--test/unit/account_test.rb47
-rw-r--r--test/unit/helpers/session_helper_test.rb4
-rw-r--r--test/unit/helpers/users_helper_test.rb4
-rw-r--r--test/unit/identity_test.rb133
-rw-r--r--test/unit/local_email_test.rb65
-rw-r--r--test/unit/token_test.rb89
-rw-r--r--test/unit/unauthenticated_user_test.rb7
-rw-r--r--test/unit/user_test.rb68
-rw-r--r--test/unit/warden_strategy_secure_remote_password_test.rb63
-rw-r--r--test/unit/webfinger/host_meta_presenter_test.rb24
-rw-r--r--test/unit/webfinger/user_presenter_test.rb49
41 files changed, 2096 insertions, 0 deletions
diff --git a/test/factories.rb b/test/factories.rb
index 6c671f8..98bb39b 100644
--- a/test/factories.rb
+++ b/test/factories.rb
@@ -1,3 +1,37 @@
Dir.glob(Rails.root.join('**','test','factories.rb')) 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/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/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..946450e
--- /dev/null
+++ b/test/integration/api/srp_test.rb
@@ -0,0 +1,104 @@
+class SrpTest < RackTest
+
+ 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/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/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/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/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