From 67f17e65b9e9e8ad2991b9c4002dba5203baa77f Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 8 Feb 2014 11:13:10 +0100 Subject: refactor tests to ease the testing of token only auth --- test/test_helper.rb | 73 ++++------------------------------------------------- 1 file changed, 5 insertions(+), 68 deletions(-) (limited to 'test') diff --git a/test/test_helper.rb b/test/test_helper.rb index 3fb2716..f63591f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,74 +10,6 @@ Dir["#{File.dirname(__FILE__)}/../*/test/support/**/*.rb"].each { |f| require f class ActiveSupport::TestCase # Add more helper methods to be used by all tests here... - def file_path(name) - File.join(Rails.root, 'test', 'files', name) - end - -end - -require 'capybara/poltergeist' - -CONFIG_RU = (Rails.root + 'config.ru').to_s -OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first - -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 - -class BrowserIntegrationTest < ActionDispatch::IntegrationTest - # Make the Capybara DSL available - include Capybara::DSL - include IntegrationTestHelper - - 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 - - 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 - protected def logfile_path @@ -87,4 +19,9 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest def screenshot_path Rails.root + 'tmp' + "#{self.class.name.underscore}.#{__name__}.png" end + + def file_path(name) + File.join(Rails.root, 'test', 'files', name) + end + end -- cgit v1.2.3 From ab820994950f8f43214bccd8dc4adf2cea40621c Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 27 Feb 2014 17:49:44 +0100 Subject: nagios test for logging into webapp --- test/nagios/webapp_login.py | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 test/nagios/webapp_login.py (limited to 'test') diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py new file mode 100755 index 0000000..c046750 --- /dev/null +++ b/test/nagios/webapp_login.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +# Test Authentication with the webapp API works. + +import requests +import json +import string +import random +import srp._pysrp as srp +import binascii +import yaml + + +safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) + +def read_config(): + stream = open("/etc/leap/hiera.yaml", 'r') + config = yaml.load(stream) + stream.close + user = config['webapp']['nagios_test_user'] + if ( 'username' not in user ): + fail('nagios test user lacks username') + if ( 'password' not in user ): + fail('nagios test user lacks password') + api = config['api'] + api['version'] = config['webapp']['api_version'] + return {'api': api, 'user': user} + +def run_tests(config): + user = config['user'] + api = config['api'] + usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) + try: + auth = parse(authenticate(api, usr)) + except requests.exceptions.ConnectionError: + fail('no connection to server') + exit(report(auth, usr)) + +# parse the server responses +def parse(response): + request = response.request + try: + return json.loads(response.text) + except ValueError: + return None + +def authenticate(api, usr): + api_url = 'https://' + api['domain'] + ':' + str(api['port']) + '/' + str(api['version']) + session = requests.session() + uname, A = usr.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + init = parse(session.post(api_url + '/sessions', data = params, verify=False)) + if ( 'errors' in init ): + fail('test user not found') + M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) + return session.put(api_url + '/sessions/' + uname, verify = False, + data = {'client_auth': binascii.hexlify(M)}) + +def report(auth, usr): + if ( 'errors' in auth ): + fail('srp password auth failed') + usr.verify_session( safe_unhexlify(auth["M2"]) ) + if usr.authenticated(): + print '0 webapp_login - OK - can login to webapp fine' + return 0 + print '1 webapp_login - WARNING - failed to verify webapp server' + return 1 + +def fail(reason): + print '2 webapp_login - CRITICAL - ' + reason + exit(2) + +run_tests(read_config()) -- cgit v1.2.3 From e4b42733d61887e2e7f01e9f159e963d50044465 Mon Sep 17 00:00:00 2001 From: Azul Date: Sun, 2 Mar 2014 08:25:24 +0100 Subject: autopep8 nagios webapp login test --- test/nagios/webapp_login.py | 109 ++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 50 deletions(-) mode change 100755 => 100644 test/nagios/webapp_login.py (limited to 'test') diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py old mode 100755 new mode 100644 index c046750..1239769 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -11,66 +11,75 @@ import binascii import yaml -safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) +safe_unhexlify = lambda x: binascii.unhexlify(x) if ( + len(x) % 2 == 0) else binascii.unhexlify('0' + x) + def read_config(): - stream = open("/etc/leap/hiera.yaml", 'r') - config = yaml.load(stream) - stream.close - user = config['webapp']['nagios_test_user'] - if ( 'username' not in user ): - fail('nagios test user lacks username') - if ( 'password' not in user ): - fail('nagios test user lacks password') - api = config['api'] - api['version'] = config['webapp']['api_version'] - return {'api': api, 'user': user} + open("/etc/leap/hiera.yaml", 'r') as stream + config = yaml.load(stream) + user = config['webapp']['nagios_test_user'] + if ('username' not in user): + fail('nagios test user lacks username') + if ('password' not in user): + fail('nagios test user lacks password') + api = config['api'] + api['version'] = config['webapp']['api_version'] + return {'api': api, 'user': user} + def run_tests(config): - user = config['user'] - api = config['api'] - usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) - try: - auth = parse(authenticate(api, usr)) - except requests.exceptions.ConnectionError: - fail('no connection to server') - exit(report(auth, usr)) + user = config['user'] + api = config['api'] + usr = srp.User(user['username'], user['password'], srp.SHA256, srp.NG_1024) + try: + auth = parse(authenticate(api, usr)) + except requests.exceptions.ConnectionError: + fail('no connection to server') + exit(report(auth, usr)) # parse the server responses + + def parse(response): - request = response.request - try: - return json.loads(response.text) - except ValueError: - return None + request = response.request + try: + return json.loads(response.text) + except ValueError: + return None + def authenticate(api, usr): - api_url = 'https://' + api['domain'] + ':' + str(api['port']) + '/' + str(api['version']) - session = requests.session() - uname, A = usr.start_authentication() - params = { - 'login': uname, - 'A': binascii.hexlify(A) - } - init = parse(session.post(api_url + '/sessions', data = params, verify=False)) - if ( 'errors' in init ): - fail('test user not found') - M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) - return session.put(api_url + '/sessions/' + uname, verify = False, - data = {'client_auth': binascii.hexlify(M)}) - -def report(auth, usr): - if ( 'errors' in auth ): - fail('srp password auth failed') - usr.verify_session( safe_unhexlify(auth["M2"]) ) - if usr.authenticated(): - print '0 webapp_login - OK - can login to webapp fine' - return 0 - print '1 webapp_login - WARNING - failed to verify webapp server' - return 1 + api_url = 'https://' + api['domain'] + ':' + \ + str(api['port']) + '/' + str(api['version']) + session = requests.session() + uname, A = usr.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + init = parse( + session.post(api_url + '/sessions', data=params, verify=False)) + if ('errors' in init): + fail('test user not found') + M = usr.process_challenge( + safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) + return session.put(api_url + '/sessions/' + uname, verify=False, + data={'client_auth': binascii.hexlify(M)}) + + def report(auth, usr): + if ('errors' in auth): + fail('srp password auth failed') + usr.verify_session(safe_unhexlify(auth["M2"])) + if usr.authenticated(): + print '0 webapp_login - OK - can login to webapp fine' + return 0 + print '1 webapp_login - WARNING - failed to verify webapp server' + return 1 + def fail(reason): - print '2 webapp_login - CRITICAL - ' + reason - exit(2) + print '2 webapp_login - CRITICAL - ' + reason + exit(2) run_tests(read_config()) -- cgit v1.2.3 From 35b3387b99024a69402cc0d7b5d0dc7fed37a0fa Mon Sep 17 00:00:00 2001 From: Azul Date: Sun, 2 Mar 2014 08:38:10 +0100 Subject: including chiiphs comments --- test/nagios/webapp_login.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) mode change 100644 => 100755 test/nagios/webapp_login.py (limited to 'test') diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py old mode 100644 new mode 100755 index 1239769..afa3edf --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -16,12 +16,12 @@ safe_unhexlify = lambda x: binascii.unhexlify(x) if ( def read_config(): - open("/etc/leap/hiera.yaml", 'r') as stream - config = yaml.load(stream) + with open("/etc/leap/hiera.yaml", 'r') as stream: + config = yaml.load(stream) user = config['webapp']['nagios_test_user'] - if ('username' not in user): + if 'username' not in user: fail('nagios test user lacks username') - if ('password' not in user): + if 'password' not in user: fail('nagios test user lacks password') api = config['api'] api['version'] = config['webapp']['api_version'] @@ -50,8 +50,7 @@ def parse(response): def authenticate(api, usr): - api_url = 'https://' + api['domain'] + ':' + \ - str(api['port']) + '/' + str(api['version']) + api_url = "https://{domain}:{port}/{version}".format(**api) session = requests.session() uname, A = usr.start_authentication() params = { @@ -67,9 +66,10 @@ def authenticate(api, usr): return session.put(api_url + '/sessions/' + uname, verify=False, data={'client_auth': binascii.hexlify(M)}) - def report(auth, usr): - if ('errors' in auth): - fail('srp password auth failed') + +def report(auth, usr): + if ('errors' in auth): + fail('srp password auth failed') usr.verify_session(safe_unhexlify(auth["M2"])) if usr.authenticated(): print '0 webapp_login - OK - can login to webapp fine' -- cgit v1.2.3 From 453f5e91593c8fcef4a330016bfb0128ca37c204 Mon Sep 17 00:00:00 2001 From: db Date: Thu, 6 Mar 2014 10:53:54 -0300 Subject: Add script to check if soledad is working (#5239). --- test/nagios/soledad_sync.py | 94 +++++++++++++++++++++++++++++++++++++++++++++ test/nagios/webapp_login.py | 3 +- 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100755 test/nagios/soledad_sync.py (limited to 'test') diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py new file mode 100755 index 0000000..3f176b5 --- /dev/null +++ b/test/nagios/soledad_sync.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Test Soledad sync +# +# This script performs a slightly modified U1DB sync to the Soledad server and +# returns whether that sync was succesful or not. + + +import tempfile +import requests +import os +import srp._pysrp as srp +import shutil +import u1db +from u1db.remote.http_target import HTTPSyncTarget +from webapp_login import read_config, parse, authenticate, fail + + +# monkey patch U1DB's HTTPSyncTarget to perform token based auth + +def set_token_credentials(self, uuid, token): + self._creds = {'token': (uuid, token)} + +def _sign_request(self, method, url_query, params): + uuid, token = self._creds['token'] + auth = '%s:%s' % (uuid, token) + return [('Authorization', 'Token %s' % auth.encode('base64')[:-1])] + +HTTPSyncTarget.set_token_credentials = set_token_credentials +HTTPSyncTarget._sign_request = _sign_request + + +# The following function could fetch all info needed to sync using soledad. +# Despite that, we won't use all that info because we are instead faking a +# Soledad sync by using U1DB slightly modified syncing capabilities. Part of +# the code is commented and left here for future reference, in case we decide +# to actually use the Soledad client in the future. + +def get_soledad_info(config, tempdir): + # get login and get user info + user = config['user'] + api = config['api'] + usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) + try: + auth = parse(authenticate(api, usr)) + except requests.exceptions.ConnectionError: + fail('no connection to server') + # get soledad server url + service_url = 'https://%s:%d/%d/config/soledad-service.json' % \ + (api['domain'], api['port'], api['version']) + soledad_hosts = requests.get(service_url).json['hosts'] + host = soledad_hosts.keys()[0] + server_url = 'https://%s:%d/user-%s' % \ + (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'], + auth['id']) + # get provider ca certificate + #ca_cert = requests.get('https://127.0.0.1/ca.crt', verify=False).text + #cert_file = os.path.join(tempdir, 'ca.crt') + cert_file = None # not used for now + #with open(cert_file, 'w') as f: + # f.write(ca_cert) + return auth['id'], user['password'], server_url, cert_file, auth['token'] + + +def run_tests(): + tempdir = tempfile.mkdtemp() + uuid, password, server_url, cert_file, token = \ + get_soledad_info(read_config(), tempdir) + exc = None + try: + # in the future, we can replace the following by an actual Soledad + # client sync, if needed + db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True) + creds = {'token': {'uuid': uuid, 'token': token}} + db.sync(server_url, creds=creds, autocreate=False) + except Exception as e: + exc = e + shutil.rmtree(tempdir) + exit(report(exc)) + + +def report(exc): + if exc is None: + print '0 soledad_sync - OK - can sync soledad fine' + return 0 + if isinstance(exc, u1db.errors.U1DBError): + print '2 soledad_sync - CRITICAL - ' + exc.message + else: + print '2 soledad_sync - CRITICAL - ' + str(exc) + return 2 + + +if __name__ == '__main__': + run_tests() diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index afa3edf..1711238 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -82,4 +82,5 @@ def fail(reason): print '2 webapp_login - CRITICAL - ' + reason exit(2) -run_tests(read_config()) +if __name__ == '__main__': + run_tests(read_config()) -- cgit v1.2.3 From 9cffdf44ad4b398b27a5c9527ce2080ab6ca2b44 Mon Sep 17 00:00:00 2001 From: db Date: Wed, 2 Apr 2014 11:56:45 -0300 Subject: Fix soledad sync nagios plugin returned check_name (#5430). --- test/nagios/soledad_sync.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py index 3f176b5..94679b1 100755 --- a/test/nagios/soledad_sync.py +++ b/test/nagios/soledad_sync.py @@ -12,8 +12,10 @@ import os import srp._pysrp as srp import shutil import u1db +import webapp_login + + from u1db.remote.http_target import HTTPSyncTarget -from webapp_login import read_config, parse, authenticate, fail # monkey patch U1DB's HTTPSyncTarget to perform token based auth @@ -30,6 +32,14 @@ HTTPSyncTarget.set_token_credentials = set_token_credentials HTTPSyncTarget._sign_request = _sign_request +def fail(reason): + print '2 soledad_sync - CRITICAL - ' + reason + exit(2) + +# monkey patch webapp_login's fail function to report as soledad +webapp_login.fail = fail + + # The following function could fetch all info needed to sync using soledad. # Despite that, we won't use all that info because we are instead faking a # Soledad sync by using U1DB slightly modified syncing capabilities. Part of @@ -42,7 +52,7 @@ def get_soledad_info(config, tempdir): api = config['api'] usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) try: - auth = parse(authenticate(api, usr)) + auth = webapp_login.parse(webapp_login.authenticate(api, usr)) except requests.exceptions.ConnectionError: fail('no connection to server') # get soledad server url @@ -65,7 +75,7 @@ def get_soledad_info(config, tempdir): def run_tests(): tempdir = tempfile.mkdtemp() uuid, password, server_url, cert_file, token = \ - get_soledad_info(read_config(), tempdir) + get_soledad_info(webapp_login.read_config(), tempdir) exc = None try: # in the future, we can replace the following by an actual Soledad -- cgit v1.2.3 From 53808b073f539ba2b442738b6abf97228488e311 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 8 Apr 2014 09:12:37 +0200 Subject: moving all of core into toplevel, tests fail. --- test/support/browser_integration_test.rb | 81 ++++++++++++++++++++++++++++++++ test/support/rack_test.rb | 37 +++++++++++++++ test/support/with_config_helper.rb | 16 +++++++ 3 files changed, 134 insertions(+) create mode 100644 test/support/browser_integration_test.rb create mode 100644 test/support/rack_test.rb create mode 100644 test/support/with_config_helper.rb (limited to 'test') 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..2d8e5c4 --- /dev/null +++ b/test/support/rack_test.rb @@ -0,0 +1,37 @@ +class RackTest < ActiveSupport::TestCase + include Rack::Test::Methods + include Warden::Test::Helpers + include LeapWebCore::AssertResponses + + 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/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 -- cgit v1.2.3 From 045237ff88ffd5f1fe23d9621b043a9e604e54fa Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 8 Apr 2014 09:58:41 +0200 Subject: fix requiring core extensions - most tests pass some message tests are failing for me right now. --- test/support/assert_responses.rb | 46 ++++++++++++++++++++++++++++++++++++++++ test/support/rack_test.rb | 2 +- test/test_helper.rb | 3 +++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/support/assert_responses.rb (limited to 'test') 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/rack_test.rb b/test/support/rack_test.rb index 2d8e5c4..35d191b 100644 --- a/test/support/rack_test.rb +++ b/test/support/rack_test.rb @@ -1,7 +1,7 @@ class RackTest < ActiveSupport::TestCase include Rack::Test::Methods include Warden::Test::Helpers - include LeapWebCore::AssertResponses + include AssertResponses CONFIG_RU = (Rails.root + 'config.ru').to_s OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first diff --git a/test/test_helper.rb b/test/test_helper.rb index f63591f..b844b90 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,6 +4,9 @@ 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 } -- cgit v1.2.3 From b6d14dc19dd350a807826e3e097738a36613e083 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 8 Apr 2014 11:49:14 +0200 Subject: moving users: app and test files --- test/factories.rb | 34 +++++ test/functional/application_controller_test.rb | 28 ++++ test/functional/helper_methods_test.rb | 39 +++++ test/functional/keys_controller_test.rb | 32 ++++ test/functional/sessions_controller_test.rb | 59 ++++++++ test/functional/test_helpers_test.rb | 38 +++++ test/functional/users_controller_test.rb | 165 +++++++++++++++++++++ test/functional/v1/messages_controller_test.rb | 57 +++++++ test/functional/v1/sessions_controller_test.rb | 62 ++++++++ test/functional/v1/users_controller_test.rb | 74 +++++++++ test/functional/webfinger_controller_test.rb | 33 +++++ test/integration/api/Readme.md | 23 +++ test/integration/api/login_test.rb | 50 +++++++ test/integration/api/pgp_key_test.rb | 35 +++++ test/integration/api/python/flow_with_srp.py | 96 ++++++++++++ .../integration/api/python/login_wrong_username.py | 19 +++ test/integration/api/python/signup.py | 20 +++ test/integration/api/python/signup_and_login.py | 44 ++++++ .../api/python/signup_and_login_wrong_password.py | 43 ++++++ test/integration/api/python/umlauts.py | 79 ++++++++++ test/integration/api/signup_test.rb | 20 +++ test/integration/api/srp_test.rb | 104 +++++++++++++ test/integration/api/update_account_test.rb | 51 +++++++ test/integration/browser/account_test.rb | 147 ++++++++++++++++++ test/integration/browser/session_test.rb | 27 ++++ test/integration/navigation_test.rb | 9 ++ test/leap_web_users_test.rb | 7 + test/support/auth_test_helper.rb | 65 ++++++++ test/support/stub_record_helper.rb | 53 +++++++ test/support/time_test_helper.rb | 30 ++++ test/unit/account_test.rb | 47 ++++++ test/unit/helpers/session_helper_test.rb | 4 + test/unit/helpers/users_helper_test.rb | 4 + test/unit/identity_test.rb | 133 +++++++++++++++++ test/unit/local_email_test.rb | 65 ++++++++ test/unit/token_test.rb | 89 +++++++++++ test/unit/unauthenticated_user_test.rb | 7 + test/unit/user_test.rb | 68 +++++++++ .../warden_strategy_secure_remote_password_test.rb | 63 ++++++++ test/unit/webfinger/host_meta_presenter_test.rb | 24 +++ test/unit/webfinger/user_presenter_test.rb | 49 ++++++ 41 files changed, 2096 insertions(+) create mode 100644 test/functional/application_controller_test.rb create mode 100644 test/functional/helper_methods_test.rb create mode 100644 test/functional/keys_controller_test.rb create mode 100644 test/functional/sessions_controller_test.rb create mode 100644 test/functional/test_helpers_test.rb create mode 100644 test/functional/users_controller_test.rb create mode 100644 test/functional/v1/messages_controller_test.rb create mode 100644 test/functional/v1/sessions_controller_test.rb create mode 100644 test/functional/v1/users_controller_test.rb create mode 100644 test/functional/webfinger_controller_test.rb create mode 100644 test/integration/api/Readme.md create mode 100644 test/integration/api/login_test.rb create mode 100644 test/integration/api/pgp_key_test.rb create mode 100755 test/integration/api/python/flow_with_srp.py create mode 100755 test/integration/api/python/login_wrong_username.py create mode 100755 test/integration/api/python/signup.py create mode 100755 test/integration/api/python/signup_and_login.py create mode 100755 test/integration/api/python/signup_and_login_wrong_password.py create mode 100755 test/integration/api/python/umlauts.py create mode 100644 test/integration/api/signup_test.rb create mode 100644 test/integration/api/srp_test.rb create mode 100644 test/integration/api/update_account_test.rb create mode 100644 test/integration/browser/account_test.rb create mode 100644 test/integration/browser/session_test.rb create mode 100644 test/integration/navigation_test.rb create mode 100644 test/leap_web_users_test.rb create mode 100644 test/support/auth_test_helper.rb create mode 100644 test/support/stub_record_helper.rb create mode 100644 test/support/time_test_helper.rb create mode 100644 test/unit/account_test.rb create mode 100644 test/unit/helpers/session_helper_test.rb create mode 100644 test/unit/helpers/users_helper_test.rb create mode 100644 test/unit/identity_test.rb create mode 100644 test/unit/local_email_test.rb create mode 100644 test/unit/token_test.rb create mode 100644 test/unit/unauthenticated_user_test.rb create mode 100644 test/unit/user_test.rb create mode 100644 test/unit/warden_strategy_secure_remote_password_test.rb create mode 100644 test/unit/webfinger/host_meta_presenter_test.rb create mode 100644 test/unit/webfinger/user_presenter_test.rb (limited to 'test') 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 < 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 -- cgit v1.2.3 From 20197129459d90642c50c27e601ef13ece4a873b Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 8 Apr 2014 14:30:57 +0200 Subject: only load */test/factories from test/factories prevent recursive loadign of test/factories.rb ** can be empty. --- test/factories.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/factories.rb b/test/factories.rb index 98bb39b..980e2aa 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -1,4 +1,4 @@ -Dir.glob(Rails.root.join('**','test','factories.rb')) do |factory_file| +Dir.glob(Rails.root.join('*','test','factories.rb')) do |factory_file| require factory_file end FactoryGirl.define do -- cgit v1.2.3 From c1486cb9688d53c5ae266ff22ab279ead12eaa36 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 10 Apr 2014 12:45:21 +0200 Subject: move certs into toplevel cleaned up all the engine stuff that was never really used. Afterwards there is not that much left that makes it into the toplevel. --- test/files/ca.crt | 15 ++++++++++ test/files/ca.key | 16 +++++++++++ test/functional/v1/certs_controller_test.rb | 44 +++++++++++++++++++++++++++++ test/unit/client_certificate_test.rb | 24 ++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 test/files/ca.crt create mode 100644 test/files/ca.key create mode 100644 test/functional/v1/certs_controller_test.rb create mode 100644 test/unit/client_certificate_test.rb (limited to 'test') 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/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/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 -- cgit v1.2.3 From 636692f9921bd695d726695d2d46c91f5a6e56f3 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 11 Apr 2014 10:03:19 +0200 Subject: move engines into engines directory Also renamed help to support so it's harder to confuse it with documentation --- test/factories.rb | 4 +++- test/test_helper.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/factories.rb b/test/factories.rb index 980e2aa..ac9333c 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -1,6 +1,8 @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index b844b90..d001ac7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -8,7 +8,7 @@ require 'mocha/setup' 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... -- cgit v1.2.3 From 63010ddb7cf4ef124376d8a6f35b6de35c99d175 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 11 Apr 2014 10:35:41 +0200 Subject: move include AssertResponses into test itself it may not have been required before the RackTest support class. --- test/integration/api/srp_test.rb | 1 + test/support/rack_test.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/integration/api/srp_test.rb b/test/integration/api/srp_test.rb index 946450e..26adc8c 100644 --- a/test/integration/api/srp_test.rb +++ b/test/integration/api/srp_test.rb @@ -1,4 +1,5 @@ class SrpTest < RackTest + include AssertResponses teardown do if @user diff --git a/test/support/rack_test.rb b/test/support/rack_test.rb index 35d191b..806339a 100644 --- a/test/support/rack_test.rb +++ b/test/support/rack_test.rb @@ -1,7 +1,8 @@ +require_relative 'assert_responses' + class RackTest < ActiveSupport::TestCase include Rack::Test::Methods include Warden::Test::Helpers - include AssertResponses CONFIG_RU = (Rails.root + 'config.ru').to_s OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first -- cgit v1.2.3 From a14f66c2642ff43c2cc497b0597bfb17d19a7139 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 15 Apr 2014 09:47:07 +0200 Subject: refactor nagios tests, remove parse --- test/nagios/soledad_sync.py | 2 +- test/nagios/webapp_login.py | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) (limited to 'test') diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py index 94679b1..faf552a 100755 --- a/test/nagios/soledad_sync.py +++ b/test/nagios/soledad_sync.py @@ -52,7 +52,7 @@ def get_soledad_info(config, tempdir): api = config['api'] usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) try: - auth = webapp_login.parse(webapp_login.authenticate(api, usr)) + auth = webapp_login.authenticate(api, usr) except requests.exceptions.ConnectionError: fail('no connection to server') # get soledad server url diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index 1711238..86a4045 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -33,21 +33,11 @@ def run_tests(config): api = config['api'] usr = srp.User(user['username'], user['password'], srp.SHA256, srp.NG_1024) try: - auth = parse(authenticate(api, usr)) + auth = authenticate(api, usr) except requests.exceptions.ConnectionError: fail('no connection to server') exit(report(auth, usr)) -# parse the server responses - - -def parse(response): - request = response.request - try: - return json.loads(response.text) - except ValueError: - return None - def authenticate(api, usr): api_url = "https://{domain}:{port}/{version}".format(**api) @@ -57,14 +47,15 @@ def authenticate(api, usr): 'login': uname, 'A': binascii.hexlify(A) } - init = parse( - session.post(api_url + '/sessions', data=params, verify=False)) + response = session.post(api_url + '/sessions', data=params, verify=False) + init = response.json() if ('errors' in init): fail('test user not found') M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) - return session.put(api_url + '/sessions/' + uname, verify=False, + response = session.put(api_url + '/sessions/' + uname, verify=False, data={'client_auth': binascii.hexlify(M)}) + return response.json() def report(auth, usr): -- cgit v1.2.3 From 44d2d031555c889b94e9738cb45740b16a4071ce Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 15 Apr 2014 12:51:06 +0200 Subject: refactor reporting in webapp login nagios test --- test/nagios/report.py | 19 +++++++++++++++++++ test/nagios/soledad_sync.py | 2 +- test/nagios/webapp_login.py | 35 ++++++++++++----------------------- 3 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 test/nagios/report.py (limited to 'test') diff --git a/test/nagios/report.py b/test/nagios/report.py new file mode 100644 index 0000000..d2720a5 --- /dev/null +++ b/test/nagios/report.py @@ -0,0 +1,19 @@ +system = 'undefined' + +def report(code, message): + codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} + print "%d %s - %s - %s" % \ + (code, system, codes[code], message) + exit(code) + +def fail(message): + report(2, message) + +def warn(message): + report(1, message) + +def ok(message): + report(0, message) + +def unknown(message): + report(3, message) diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py index faf552a..a3c2d5a 100755 --- a/test/nagios/soledad_sync.py +++ b/test/nagios/soledad_sync.py @@ -58,7 +58,7 @@ def get_soledad_info(config, tempdir): # get soledad server url service_url = 'https://%s:%d/%d/config/soledad-service.json' % \ (api['domain'], api['port'], api['version']) - soledad_hosts = requests.get(service_url).json['hosts'] + soledad_hosts = requests.get(service_url).json()['hosts'] host = soledad_hosts.keys()[0] server_url = 'https://%s:%d/user-%s' % \ (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'], diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index 86a4045..a7e3473 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -9,20 +9,21 @@ import random import srp._pysrp as srp import binascii import yaml - +import report safe_unhexlify = lambda x: binascii.unhexlify(x) if ( len(x) % 2 == 0) else binascii.unhexlify('0' + x) +report.system = 'webapp login' def read_config(): with open("/etc/leap/hiera.yaml", 'r') as stream: config = yaml.load(stream) user = config['webapp']['nagios_test_user'] if 'username' not in user: - fail('nagios test user lacks username') + report.fail('nagios test user lacks username') if 'password' not in user: - fail('nagios test user lacks password') + report.fail('nagios test user lacks password') api = config['api'] api['version'] = config['webapp']['api_version'] return {'api': api, 'user': user} @@ -35,9 +36,13 @@ def run_tests(config): try: auth = authenticate(api, usr) except requests.exceptions.ConnectionError: - fail('no connection to server') - exit(report(auth, usr)) - + report.fail('no connection to server') + if ('errors' in auth): + report.fail('srp password auth failed') + usr.verify_session(safe_unhexlify(auth["M2"])) + if usr.authenticated(): + report.ok('can login to webapp fine') + report.warn('failed to verify webapp server') def authenticate(api, usr): api_url = "https://{domain}:{port}/{version}".format(**api) @@ -50,28 +55,12 @@ def authenticate(api, usr): response = session.post(api_url + '/sessions', data=params, verify=False) init = response.json() if ('errors' in init): - fail('test user not found') + report.fail('test user not found') M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) response = session.put(api_url + '/sessions/' + uname, verify=False, data={'client_auth': binascii.hexlify(M)}) return response.json() - -def report(auth, usr): - if ('errors' in auth): - fail('srp password auth failed') - usr.verify_session(safe_unhexlify(auth["M2"])) - if usr.authenticated(): - print '0 webapp_login - OK - can login to webapp fine' - return 0 - print '1 webapp_login - WARNING - failed to verify webapp server' - return 1 - - -def fail(reason): - print '2 webapp_login - CRITICAL - ' + reason - exit(2) - if __name__ == '__main__': run_tests(read_config()) -- cgit v1.2.3 From be2971c2e615cc8a808822317d049e99f5183bdc Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 15 Apr 2014 17:17:36 +0200 Subject: refactor: move nagios specifs to nagios_test nagios_test.run takes a function and executes it. If it returns nothing or 0 and OK nagios message is printed. If it returns sth. else this will be printed a a warning If it raises an exception that will result in a CRITICAL report. This way we can keep the nagios things outside the test cases and just write simple functions that either return 0, a warnign or raise a meaningful exception --- test/nagios/nagios_report.py | 24 +++++++++++ test/nagios/nagios_test.py | 49 ++++++++++++++++++++++ test/nagios/report.py | 19 --------- test/nagios/soledad_sync.py | 96 +++++++++++++++++--------------------------- test/nagios/webapp_login.py | 28 ++++++------- 5 files changed, 121 insertions(+), 95 deletions(-) create mode 100644 test/nagios/nagios_report.py create mode 100644 test/nagios/nagios_test.py delete mode 100644 test/nagios/report.py (limited to 'test') diff --git a/test/nagios/nagios_report.py b/test/nagios/nagios_report.py new file mode 100644 index 0000000..13cd551 --- /dev/null +++ b/test/nagios/nagios_report.py @@ -0,0 +1,24 @@ +def functions_for_system(under_test): + """ + returns a set of functions to use for nagios reporting: + >>> ok, warn, critical, unknown = functions_for_system("tested system") + + each of them will print a nagios line with its argument and + return the exit code: + >>> warn("that looks strange") + 1 tested system - WARNING - that looks strange + 1 + """ + def report_function(code): + return lambda message : report(under_test, code, message) + return map(report_function, [0,1,2,3]) + +def report(system, code, message): + codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} + print "%d %s - %s - %s" % \ + (code, system, codes[code], message) + return code + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/test/nagios/nagios_test.py b/test/nagios/nagios_test.py new file mode 100644 index 0000000..3eb8d55 --- /dev/null +++ b/test/nagios/nagios_test.py @@ -0,0 +1,49 @@ +import __main__ as main +import os +import sys +import nagios_report + +def run(test): + """ + run takes a function and tries it out. + If it returns nothing or 0 everything is fine and run prints an OK message + with the function name. + >>> def this_works_fine(): return + >>> run(this_works_fine) + 0 nagios_test.py - OK - this_works_fine + 0 + >>> def this_also_works_fine(): return 0 + >>> run(this_also_works_fine) + 0 nagios_test.py - OK - this_also_works_fine + 0 + + If the function returns something else it will be printed as a warning. + >>> run(lambda : "this is a warning") + 1 nagios_test.py - WARNING - this is a warning + 1 + + Errors raised will result in a CRITICAL nagios string. + >>> def failure(): raise Exception("something went wrong") + >>> run(failure) + 2 nagios_test.py - CRITICAL - something went wrong + 2 + """ + try: + name = os.path.basename(main.__file__) + except AttributeError: + name = sys.argv[0] + ok, warn, fail, unknown = nagios_report.functions_for_system(name) + try: + warning = test() + if warning and warning != 0: + code = warn(warning) + else: + code = ok(test.__name__) + except Exception as exc: + code = fail(exc.message or str(exc)) + return code + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/test/nagios/report.py b/test/nagios/report.py deleted file mode 100644 index d2720a5..0000000 --- a/test/nagios/report.py +++ /dev/null @@ -1,19 +0,0 @@ -system = 'undefined' - -def report(code, message): - codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} - print "%d %s - %s - %s" % \ - (code, system, codes[code], message) - exit(code) - -def fail(message): - report(2, message) - -def warn(message): - report(1, message) - -def ok(message): - report(0, message) - -def unknown(message): - report(3, message) diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py index a3c2d5a..9f51fd1 100755 --- a/test/nagios/soledad_sync.py +++ b/test/nagios/soledad_sync.py @@ -32,14 +32,6 @@ HTTPSyncTarget.set_token_credentials = set_token_credentials HTTPSyncTarget._sign_request = _sign_request -def fail(reason): - print '2 soledad_sync - CRITICAL - ' + reason - exit(2) - -# monkey patch webapp_login's fail function to report as soledad -webapp_login.fail = fail - - # The following function could fetch all info needed to sync using soledad. # Despite that, we won't use all that info because we are instead faking a # Soledad sync by using U1DB slightly modified syncing capabilities. Part of @@ -47,58 +39,42 @@ webapp_login.fail = fail # to actually use the Soledad client in the future. def get_soledad_info(config, tempdir): - # get login and get user info - user = config['user'] - api = config['api'] - usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) - try: + # get login and get user info + user = config['user'] + api = config['api'] + usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) auth = webapp_login.authenticate(api, usr) - except requests.exceptions.ConnectionError: - fail('no connection to server') - # get soledad server url - service_url = 'https://%s:%d/%d/config/soledad-service.json' % \ - (api['domain'], api['port'], api['version']) - soledad_hosts = requests.get(service_url).json()['hosts'] - host = soledad_hosts.keys()[0] - server_url = 'https://%s:%d/user-%s' % \ - (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'], - auth['id']) - # get provider ca certificate - #ca_cert = requests.get('https://127.0.0.1/ca.crt', verify=False).text - #cert_file = os.path.join(tempdir, 'ca.crt') - cert_file = None # not used for now - #with open(cert_file, 'w') as f: - # f.write(ca_cert) - return auth['id'], user['password'], server_url, cert_file, auth['token'] - - -def run_tests(): - tempdir = tempfile.mkdtemp() - uuid, password, server_url, cert_file, token = \ - get_soledad_info(webapp_login.read_config(), tempdir) - exc = None - try: - # in the future, we can replace the following by an actual Soledad - # client sync, if needed - db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True) - creds = {'token': {'uuid': uuid, 'token': token}} - db.sync(server_url, creds=creds, autocreate=False) - except Exception as e: - exc = e - shutil.rmtree(tempdir) - exit(report(exc)) - - -def report(exc): - if exc is None: - print '0 soledad_sync - OK - can sync soledad fine' - return 0 - if isinstance(exc, u1db.errors.U1DBError): - print '2 soledad_sync - CRITICAL - ' + exc.message - else: - print '2 soledad_sync - CRITICAL - ' + str(exc) - return 2 - + # get soledad server url + service_url = 'https://%s:%d/%d/config/soledad-service.json' % \ + (api['domain'], api['port'], api['version']) + soledad_hosts = requests.get(service_url).json()['hosts'] + host = soledad_hosts.keys()[0] + server_url = 'https://%s:%d/user-%s' % \ + (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'], + auth['id']) + # get provider ca certificate + #ca_cert = requests.get('https://127.0.0.1/ca.crt', verify=False).text + #cert_file = os.path.join(tempdir, 'ca.crt') + cert_file = None # not used for now + #with open(cert_file, 'w') as f: + # f.write(ca_cert) + return auth['id'], user['password'], server_url, cert_file, auth['token'] + + +def can_sync_soledad_fine(): + tempdir = tempfile.mkdtemp() + try: + uuid, password, server_url, cert_file, token = \ + get_soledad_info(webapp_login.read_config(), tempdir) + # in the future, we can replace the following by an actual Soledad + # client sync, if needed + db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True) + creds = {'token': {'uuid': uuid, 'token': token}} + db.sync(server_url, creds=creds, autocreate=False) + finally: + shutil.rmtree(tempdir) if __name__ == '__main__': - run_tests() + import nagios_test + exit_code = nagios_test.run(can_sync_soledad_fine) + exit(exit_code) diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index a7e3473..6d06438 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -9,40 +9,34 @@ import random import srp._pysrp as srp import binascii import yaml -import report safe_unhexlify = lambda x: binascii.unhexlify(x) if ( len(x) % 2 == 0) else binascii.unhexlify('0' + x) -report.system = 'webapp login' - def read_config(): with open("/etc/leap/hiera.yaml", 'r') as stream: config = yaml.load(stream) user = config['webapp']['nagios_test_user'] if 'username' not in user: - report.fail('nagios test user lacks username') + raise Exception('nagios test user lacks username') if 'password' not in user: - report.fail('nagios test user lacks password') + raise Exception('nagios test user lacks password') api = config['api'] api['version'] = config['webapp']['api_version'] return {'api': api, 'user': user} -def run_tests(config): +def login_successfully(config=None): + config = config or read_config() user = config['user'] api = config['api'] usr = srp.User(user['username'], user['password'], srp.SHA256, srp.NG_1024) - try: - auth = authenticate(api, usr) - except requests.exceptions.ConnectionError: - report.fail('no connection to server') + auth = authenticate(api, usr) if ('errors' in auth): - report.fail('srp password auth failed') + raise Exception('srp password auth failed') usr.verify_session(safe_unhexlify(auth["M2"])) - if usr.authenticated(): - report.ok('can login to webapp fine') - report.warn('failed to verify webapp server') + if not usr.authenticated(): + return 'failed to verify webapp server' def authenticate(api, usr): api_url = "https://{domain}:{port}/{version}".format(**api) @@ -55,7 +49,7 @@ def authenticate(api, usr): response = session.post(api_url + '/sessions', data=params, verify=False) init = response.json() if ('errors' in init): - report.fail('test user not found') + raise Exception('test user not found') M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) response = session.put(api_url + '/sessions/' + uname, verify=False, @@ -63,4 +57,6 @@ def authenticate(api, usr): return response.json() if __name__ == '__main__': - run_tests(read_config()) + import nagios_test + exit_code = nagios_test.run(login_successfully) + exit(exit_code) -- cgit v1.2.3 From 73bdb9a9ea8932e9a14f996391d348690da1a63c Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 16 Apr 2014 10:13:28 +0200 Subject: nagios test: refactor webapp_login with classes --- test/nagios/support/nagios_report.py | 24 +++++++ test/nagios/support/nagios_test.py | 49 ++++++++++++++ test/nagios/webapp_login.py | 127 +++++++++++++++++++++++------------ 3 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 test/nagios/support/nagios_report.py create mode 100644 test/nagios/support/nagios_test.py (limited to 'test') diff --git a/test/nagios/support/nagios_report.py b/test/nagios/support/nagios_report.py new file mode 100644 index 0000000..13cd551 --- /dev/null +++ b/test/nagios/support/nagios_report.py @@ -0,0 +1,24 @@ +def functions_for_system(under_test): + """ + returns a set of functions to use for nagios reporting: + >>> ok, warn, critical, unknown = functions_for_system("tested system") + + each of them will print a nagios line with its argument and + return the exit code: + >>> warn("that looks strange") + 1 tested system - WARNING - that looks strange + 1 + """ + def report_function(code): + return lambda message : report(under_test, code, message) + return map(report_function, [0,1,2,3]) + +def report(system, code, message): + codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} + print "%d %s - %s - %s" % \ + (code, system, codes[code], message) + return code + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/test/nagios/support/nagios_test.py b/test/nagios/support/nagios_test.py new file mode 100644 index 0000000..3eb8d55 --- /dev/null +++ b/test/nagios/support/nagios_test.py @@ -0,0 +1,49 @@ +import __main__ as main +import os +import sys +import nagios_report + +def run(test): + """ + run takes a function and tries it out. + If it returns nothing or 0 everything is fine and run prints an OK message + with the function name. + >>> def this_works_fine(): return + >>> run(this_works_fine) + 0 nagios_test.py - OK - this_works_fine + 0 + >>> def this_also_works_fine(): return 0 + >>> run(this_also_works_fine) + 0 nagios_test.py - OK - this_also_works_fine + 0 + + If the function returns something else it will be printed as a warning. + >>> run(lambda : "this is a warning") + 1 nagios_test.py - WARNING - this is a warning + 1 + + Errors raised will result in a CRITICAL nagios string. + >>> def failure(): raise Exception("something went wrong") + >>> run(failure) + 2 nagios_test.py - CRITICAL - something went wrong + 2 + """ + try: + name = os.path.basename(main.__file__) + except AttributeError: + name = sys.argv[0] + ok, warn, fail, unknown = nagios_report.functions_for_system(name) + try: + warning = test() + if warning and warning != 0: + code = warn(warning) + else: + code = ok(test.__name__) + except Exception as exc: + code = fail(exc.message or str(exc)) + return code + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index 6d06438..7e2efd7 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -13,48 +13,91 @@ import yaml safe_unhexlify = lambda x: binascii.unhexlify(x) if ( len(x) % 2 == 0) else binascii.unhexlify('0' + x) -def read_config(): - with open("/etc/leap/hiera.yaml", 'r') as stream: - config = yaml.load(stream) - user = config['webapp']['nagios_test_user'] - if 'username' not in user: - raise Exception('nagios test user lacks username') - if 'password' not in user: - raise Exception('nagios test user lacks password') - api = config['api'] - api['version'] = config['webapp']['api_version'] - return {'api': api, 'user': user} - - -def login_successfully(config=None): - config = config or read_config() - user = config['user'] - api = config['api'] - usr = srp.User(user['username'], user['password'], srp.SHA256, srp.NG_1024) - auth = authenticate(api, usr) - if ('errors' in auth): - raise Exception('srp password auth failed') - usr.verify_session(safe_unhexlify(auth["M2"])) - if not usr.authenticated(): - return 'failed to verify webapp server' - -def authenticate(api, usr): - api_url = "https://{domain}:{port}/{version}".format(**api) - session = requests.session() - uname, A = usr.start_authentication() - params = { - 'login': uname, - 'A': binascii.hexlify(A) - } - response = session.post(api_url + '/sessions', data=params, verify=False) - init = response.json() - if ('errors' in init): - raise Exception('test user not found') - M = usr.process_challenge( - safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) - response = session.put(api_url + '/sessions/' + uname, verify=False, - data={'client_auth': binascii.hexlify(M)}) - return response.json() +class Config(): + def __init__(self, filename="/etc/leap/hiera.yaml"): + with open("/etc/leap/hiera.yaml", 'r') as stream: + config = yaml.load(stream) + self.user = config['webapp']['nagios_test_user'] + if 'username' not in self.user: + raise Exception('nagios test user lacks username') + if 'password' not in self.user: + raise Exception('nagios test user lacks password') + self.api = config['api'] + self.api['version'] = config['webapp']['api_version'] + +class Api(): + def __init__(self, config, verify=True): + self.config = config.api + self.session = requests.session() + self.verify = verify + + def api_url(self, path): + return self.api_root() + path + + def api_root(self): + return "https://{domain}:{port}/{version}/".format(**self.config) + + def get(self, path, **args): + response = self.session.get(self.api_url(path), + verify=self.verify, + **args) + return response.json() + + def post(self, path, **args): + response = self.session.post(self.api_url(path), + verify=self.verify, + **args) + return response.json() + + def put(self, path, **args): + response = self.session.put(self.api_url(path), + verify=self.verify, + **args) + return response.json() + +class User(): + def __init__(self, config): + self.config = config.user + self.srp_user = srp.User(self.config['username'], self.config['password'], srp.SHA256, srp.NG_1024) + + def login(self, api): + init=self.init_authentication(api) + if ('errors' in init): + raise Exception('test user not found') + auth=self.authenticate(api, init) + if ('errors' in auth): + raise Exception('srp password auth failed') + self.verify_server(auth) + if not self.is_authenticated(): + raise Exception('user is not authenticated') + + def init_authentication(self, api): + uname, A = self.srp_user.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + return api.post('sessions', data=params) + + def authenticate(self, api, init): + M = self.srp_user.process_challenge( + safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) + auth = api.put('sessions/' + self.config["username"], + data={'client_auth': binascii.hexlify(M)}) + return auth + + def verify_server(self, auth): + self.srp_user.verify_session(safe_unhexlify(auth["M2"])) + + def is_authenticated(self): + return self.srp_user.authenticated() + + +def login_successfully(): + config = Config() + user = User(config) + api = Api(config, verify=False) + user.login(api) if __name__ == '__main__': import nagios_test -- cgit v1.2.3 From 0f0057e65c6bfcb98ce53e1a48aa1460d3a6716a Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 16 Apr 2014 11:14:55 +0200 Subject: move support classes into their own package now the webapp_login test looks nice and clean. soledad next. --- test/nagios/nagios_report.py | 24 ----------- test/nagios/nagios_test.py | 49 ---------------------- test/nagios/support/__init__.py | 0 test/nagios/support/api.py | 33 +++++++++++++++ test/nagios/support/config.py | 14 +++++++ test/nagios/support/user.py | 43 +++++++++++++++++++ test/nagios/webapp_login.py | 93 ++--------------------------------------- 7 files changed, 94 insertions(+), 162 deletions(-) delete mode 100644 test/nagios/nagios_report.py delete mode 100644 test/nagios/nagios_test.py create mode 100644 test/nagios/support/__init__.py create mode 100644 test/nagios/support/api.py create mode 100644 test/nagios/support/config.py create mode 100644 test/nagios/support/user.py (limited to 'test') diff --git a/test/nagios/nagios_report.py b/test/nagios/nagios_report.py deleted file mode 100644 index 13cd551..0000000 --- a/test/nagios/nagios_report.py +++ /dev/null @@ -1,24 +0,0 @@ -def functions_for_system(under_test): - """ - returns a set of functions to use for nagios reporting: - >>> ok, warn, critical, unknown = functions_for_system("tested system") - - each of them will print a nagios line with its argument and - return the exit code: - >>> warn("that looks strange") - 1 tested system - WARNING - that looks strange - 1 - """ - def report_function(code): - return lambda message : report(under_test, code, message) - return map(report_function, [0,1,2,3]) - -def report(system, code, message): - codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} - print "%d %s - %s - %s" % \ - (code, system, codes[code], message) - return code - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/test/nagios/nagios_test.py b/test/nagios/nagios_test.py deleted file mode 100644 index 3eb8d55..0000000 --- a/test/nagios/nagios_test.py +++ /dev/null @@ -1,49 +0,0 @@ -import __main__ as main -import os -import sys -import nagios_report - -def run(test): - """ - run takes a function and tries it out. - If it returns nothing or 0 everything is fine and run prints an OK message - with the function name. - >>> def this_works_fine(): return - >>> run(this_works_fine) - 0 nagios_test.py - OK - this_works_fine - 0 - >>> def this_also_works_fine(): return 0 - >>> run(this_also_works_fine) - 0 nagios_test.py - OK - this_also_works_fine - 0 - - If the function returns something else it will be printed as a warning. - >>> run(lambda : "this is a warning") - 1 nagios_test.py - WARNING - this is a warning - 1 - - Errors raised will result in a CRITICAL nagios string. - >>> def failure(): raise Exception("something went wrong") - >>> run(failure) - 2 nagios_test.py - CRITICAL - something went wrong - 2 - """ - try: - name = os.path.basename(main.__file__) - except AttributeError: - name = sys.argv[0] - ok, warn, fail, unknown = nagios_report.functions_for_system(name) - try: - warning = test() - if warning and warning != 0: - code = warn(warning) - else: - code = ok(test.__name__) - except Exception as exc: - code = fail(exc.message or str(exc)) - return code - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/test/nagios/support/__init__.py b/test/nagios/support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/nagios/support/api.py b/test/nagios/support/api.py new file mode 100644 index 0000000..3b6a90f --- /dev/null +++ b/test/nagios/support/api.py @@ -0,0 +1,33 @@ +import requests +import json + +class Api(): + def __init__(self, config, verify=True): + self.config = config.api + self.session = requests.session() + self.verify = verify + + def api_url(self, path): + return self.api_root() + path + + def api_root(self): + return "https://{domain}:{port}/{version}/".format(**self.config) + + def get(self, path, **args): + response = self.session.get(self.api_url(path), + verify=self.verify, + **args) + return response.json() + + def post(self, path, **args): + response = self.session.post(self.api_url(path), + verify=self.verify, + **args) + return response.json() + + def put(self, path, **args): + response = self.session.put(self.api_url(path), + verify=self.verify, + **args) + return response.json() + diff --git a/test/nagios/support/config.py b/test/nagios/support/config.py new file mode 100644 index 0000000..afb4464 --- /dev/null +++ b/test/nagios/support/config.py @@ -0,0 +1,14 @@ +import yaml + +class Config(): + def __init__(self, filename="/etc/leap/hiera.yaml"): + with open("/etc/leap/hiera.yaml", 'r') as stream: + config = yaml.load(stream) + self.user = config['webapp']['nagios_test_user'] + if 'username' not in self.user: + raise Exception('nagios test user lacks username') + if 'password' not in self.user: + raise Exception('nagios test user lacks password') + self.api = config['api'] + self.api['version'] = config['webapp']['api_version'] + diff --git a/test/nagios/support/user.py b/test/nagios/support/user.py new file mode 100644 index 0000000..8e49c4b --- /dev/null +++ b/test/nagios/support/user.py @@ -0,0 +1,43 @@ +import srp._pysrp as srp +import binascii + +safe_unhexlify = lambda x: binascii.unhexlify(x) if ( + len(x) % 2 == 0) else binascii.unhexlify('0' + x) + +class User(): + def __init__(self, config): + self.config = config.user + self.srp_user = srp.User(self.config['username'], self.config['password'], srp.SHA256, srp.NG_1024) + + def login(self, api): + init=self.init_authentication(api) + if ('errors' in init): + raise Exception('test user not found') + auth=self.authenticate(api, init) + if ('errors' in auth): + raise Exception('srp password auth failed') + self.verify_server(auth) + if not self.is_authenticated(): + raise Exception('user is not authenticated') + + def init_authentication(self, api): + uname, A = self.srp_user.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + return api.post('sessions', data=params) + + def authenticate(self, api, init): + M = self.srp_user.process_challenge( + safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) + auth = api.put('sessions/' + self.config["username"], + data={'client_auth': binascii.hexlify(M)}) + return auth + + def verify_server(self, auth): + self.srp_user.verify_session(safe_unhexlify(auth["M2"])) + + def is_authenticated(self): + return self.srp_user.authenticated() + diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index 7e2efd7..4e78836 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -2,96 +2,11 @@ # Test Authentication with the webapp API works. -import requests -import json import string import random -import srp._pysrp as srp -import binascii -import yaml - -safe_unhexlify = lambda x: binascii.unhexlify(x) if ( - len(x) % 2 == 0) else binascii.unhexlify('0' + x) - -class Config(): - def __init__(self, filename="/etc/leap/hiera.yaml"): - with open("/etc/leap/hiera.yaml", 'r') as stream: - config = yaml.load(stream) - self.user = config['webapp']['nagios_test_user'] - if 'username' not in self.user: - raise Exception('nagios test user lacks username') - if 'password' not in self.user: - raise Exception('nagios test user lacks password') - self.api = config['api'] - self.api['version'] = config['webapp']['api_version'] - -class Api(): - def __init__(self, config, verify=True): - self.config = config.api - self.session = requests.session() - self.verify = verify - - def api_url(self, path): - return self.api_root() + path - - def api_root(self): - return "https://{domain}:{port}/{version}/".format(**self.config) - - def get(self, path, **args): - response = self.session.get(self.api_url(path), - verify=self.verify, - **args) - return response.json() - - def post(self, path, **args): - response = self.session.post(self.api_url(path), - verify=self.verify, - **args) - return response.json() - - def put(self, path, **args): - response = self.session.put(self.api_url(path), - verify=self.verify, - **args) - return response.json() - -class User(): - def __init__(self, config): - self.config = config.user - self.srp_user = srp.User(self.config['username'], self.config['password'], srp.SHA256, srp.NG_1024) - - def login(self, api): - init=self.init_authentication(api) - if ('errors' in init): - raise Exception('test user not found') - auth=self.authenticate(api, init) - if ('errors' in auth): - raise Exception('srp password auth failed') - self.verify_server(auth) - if not self.is_authenticated(): - raise Exception('user is not authenticated') - - def init_authentication(self, api): - uname, A = self.srp_user.start_authentication() - params = { - 'login': uname, - 'A': binascii.hexlify(A) - } - return api.post('sessions', data=params) - - def authenticate(self, api, init): - M = self.srp_user.process_challenge( - safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) - auth = api.put('sessions/' + self.config["username"], - data={'client_auth': binascii.hexlify(M)}) - return auth - - def verify_server(self, auth): - self.srp_user.verify_session(safe_unhexlify(auth["M2"])) - - def is_authenticated(self): - return self.srp_user.authenticated() - +from support.api import Api +from support.config import Config +from support.user import User def login_successfully(): config = Config() @@ -100,6 +15,6 @@ def login_successfully(): user.login(api) if __name__ == '__main__': - import nagios_test + from support import nagios_test exit_code = nagios_test.run(login_successfully) exit(exit_code) -- cgit v1.2.3 From bfda2c55bd4824d94d384a39960c91b6e1f8d14b Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 16 Apr 2014 11:16:00 +0200 Subject: remove unneeded imports --- test/nagios/webapp_login.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'test') diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py index 4e78836..7741325 100755 --- a/test/nagios/webapp_login.py +++ b/test/nagios/webapp_login.py @@ -2,8 +2,6 @@ # Test Authentication with the webapp API works. -import string -import random from support.api import Api from support.config import Config from support.user import User -- cgit v1.2.3 From 8907100f3ffe99a2a9110c90418c9e5844b4ab03 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 16 Apr 2014 11:26:13 +0200 Subject: nagios test: use support classes in soledad sync --- test/nagios/soledad_sync.py | 25 +++++++++++-------------- test/nagios/support/user.py | 1 + 2 files changed, 12 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py index 9f51fd1..617dd3a 100755 --- a/test/nagios/soledad_sync.py +++ b/test/nagios/soledad_sync.py @@ -7,12 +7,12 @@ import tempfile -import requests import os -import srp._pysrp as srp import shutil import u1db -import webapp_login +from support.api import Api +from support.config import Config +from support.user import User from u1db.remote.http_target import HTTPSyncTarget @@ -40,14 +40,11 @@ HTTPSyncTarget._sign_request = _sign_request def get_soledad_info(config, tempdir): # get login and get user info - user = config['user'] - api = config['api'] - usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 ) - auth = webapp_login.authenticate(api, usr) + user = User(config) + api = Api(config, verify=False) + auth = user.login(api) # get soledad server url - service_url = 'https://%s:%d/%d/config/soledad-service.json' % \ - (api['domain'], api['port'], api['version']) - soledad_hosts = requests.get(service_url).json()['hosts'] + soledad_hosts = api.get('config/soledad-service.json')['hosts'] host = soledad_hosts.keys()[0] server_url = 'https://%s:%d/user-%s' % \ (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'], @@ -58,14 +55,14 @@ def get_soledad_info(config, tempdir): cert_file = None # not used for now #with open(cert_file, 'w') as f: # f.write(ca_cert) - return auth['id'], user['password'], server_url, cert_file, auth['token'] + return auth['id'], server_url, cert_file, auth['token'] def can_sync_soledad_fine(): tempdir = tempfile.mkdtemp() try: - uuid, password, server_url, cert_file, token = \ - get_soledad_info(webapp_login.read_config(), tempdir) + uuid, server_url, cert_file, token = \ + get_soledad_info(Config(), tempdir) # in the future, we can replace the following by an actual Soledad # client sync, if needed db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True) @@ -75,6 +72,6 @@ def can_sync_soledad_fine(): shutil.rmtree(tempdir) if __name__ == '__main__': - import nagios_test + from support import nagios_test exit_code = nagios_test.run(can_sync_soledad_fine) exit(exit_code) diff --git a/test/nagios/support/user.py b/test/nagios/support/user.py index 8e49c4b..912de89 100644 --- a/test/nagios/support/user.py +++ b/test/nagios/support/user.py @@ -19,6 +19,7 @@ class User(): self.verify_server(auth) if not self.is_authenticated(): raise Exception('user is not authenticated') + return auth def init_authentication(self, api): uname, A = self.srp_user.start_authentication() -- cgit v1.2.3 From 36e99d8b23263cffcd58988c40ca3217349a94f2 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 16 Apr 2014 12:37:03 +0200 Subject: nagios test: also test registering new users --- test/nagios/support/user.py | 28 ++++++++++++++++++++++++---- test/nagios/webapp_signup.py | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100755 test/nagios/webapp_signup.py (limited to 'test') diff --git a/test/nagios/support/user.py b/test/nagios/support/user.py index 912de89..9bf1d0a 100644 --- a/test/nagios/support/user.py +++ b/test/nagios/support/user.py @@ -1,13 +1,33 @@ import srp._pysrp as srp import binascii +import string +import random safe_unhexlify = lambda x: binascii.unhexlify(x) if ( len(x) % 2 == 0) else binascii.unhexlify('0' + x) +# let's have some random name and password +def id_generator(size=6, chars=string.ascii_lowercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + class User(): - def __init__(self, config): - self.config = config.user - self.srp_user = srp.User(self.config['username'], self.config['password'], srp.SHA256, srp.NG_1024) + def __init__(self, config = None): + if config and config.user: + self.username = config.user["username"] + self.password = config.user["password"] + else: + self.username = 'test_' + id_generator() + self.password = id_generator() + id_generator() + self.srp_user = srp.User(self.username, self.password, srp.SHA256, srp.NG_1024) + + def signup(self, api): + salt, vkey = srp.create_salted_verification_key( self.username, self.password, srp.SHA256, srp.NG_1024 ) + user_params = { + 'user[login]': self.username, + 'user[password_verifier]': binascii.hexlify(vkey), + 'user[password_salt]': binascii.hexlify(salt) + } + return api.post('users.json', data = user_params) def login(self, api): init=self.init_authentication(api) @@ -32,7 +52,7 @@ class User(): def authenticate(self, api, init): M = self.srp_user.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B'])) - auth = api.put('sessions/' + self.config["username"], + auth = api.put('sessions/' + self.username, data={'client_auth': binascii.hexlify(M)}) return auth diff --git a/test/nagios/webapp_signup.py b/test/nagios/webapp_signup.py new file mode 100755 index 0000000..3e7283e --- /dev/null +++ b/test/nagios/webapp_signup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# Test Signup and Login with the webapp API works. + +from support.api import Api +from support.config import Config +from support.user import User + +def signup_successfully(): + config = Config() + user = User() + api = Api(config, verify=False) + user.signup(api) + user.login(api) + +if __name__ == '__main__': + from support import nagios_test + exit_code = nagios_test.run(signup_successfully) + exit(exit_code) -- cgit v1.2.3 From d639e0a48599b30777b80c2809ded1efb3a6d926 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 17 Apr 2014 10:04:12 +0200 Subject: add a try/except for older versions of requests they have response.json as a dict instead of response.json() --- test/nagios/support/api.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/test/nagios/support/api.py b/test/nagios/support/api.py index 3b6a90f..ec1af99 100644 --- a/test/nagios/support/api.py +++ b/test/nagios/support/api.py @@ -17,17 +17,23 @@ class Api(): response = self.session.get(self.api_url(path), verify=self.verify, **args) - return response.json() + return self.parse_json(response) def post(self, path, **args): response = self.session.post(self.api_url(path), verify=self.verify, **args) - return response.json() + return self.parse_json(response) def put(self, path, **args): response = self.session.put(self.api_url(path), verify=self.verify, **args) - return response.json() + return self.parse_json(response) + + def parse_json(self, response): + try: + return response.json() + except TypeError: + return response.json # older versions of requests -- cgit v1.2.3 From 8cc5ba134f6c5a1a06d91407aa78b962545c54ac Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 17 Apr 2014 11:42:13 +0200 Subject: initial commit for the service level api :api/service will return a hash of the current users service level This is failiing if the user is not logged in. Instead it should return the service description for an anonymous user. --- test/functional/v1/services_controller_test.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/functional/v1/services_controller_test.rb (limited to 'test') diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb new file mode 100644 index 0000000..35a9de6 --- /dev/null +++ b/test/functional/v1/services_controller_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +class V1::ServicesControllerTest < ActionController::TestCase + + test "anonymous user can request service info" do + get :show, format: :json + assert_json_response name: 'anonymous', + cert_prefix: 'LIMITED', + description: 'anonymous account, with rate limited VPN' + end + + test "user can see their service info" do + login + get :show, format: :json + assert_json_response name: 'free', + cert_prefix: 'LIMITED', + description: 'free account, with rate limited VPN', + cost: 0, + quota: 100 + end + +end + -- cgit v1.2.3 From 7a9ece43bd61246b450471ed6bb1089570321e38 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 17 Apr 2014 19:27:47 +0200 Subject: make use of the UnauthorizedUser Null Pattern for current_user - use it to get rid of some conditionals --- test/functional/v1/certs_controller_test.rb | 54 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) (limited to 'test') diff --git a/test/functional/v1/certs_controller_test.rb b/test/functional/v1/certs_controller_test.rb index 2c70e52..3631947 100644 --- a/test/functional/v1/certs_controller_test.rb +++ b/test/functional/v1/certs_controller_test.rb @@ -3,42 +3,42 @@ 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 + cert = expect_cert('LIMITED') + get :show + assert_response :success + assert_equal cert.to_s, @response.body + end + + test "send limited cert" do + login + cert = expect_cert('LIMITED') + get :show + assert_response :success + assert_equal cert.to_s, @response.body 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 + login effective_service_level: ServiceLevel.new(id: 2) + cert = expect_cert('UNLIMITED') + get :show + assert_response :success + assert_equal cert.to_s, @response.body end - test "login required if anonymous certs disabled" do - with_config allow_anonymous_certs: false do + test "redirect if no eip service offered" do + with_config({service_levels: {0 => {services: []}}}) 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 + protected + def expect_cert(prefix) + cert = stub :to_s => "#{prefix.downcase} cert" + ClientCertificate.expects(:new). + with(:prefix => prefix). + returns(cert) + return cert + end end -- cgit v1.2.3 From fca9752315a0b46b52facf0e54c35214198fe8ae Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 17 Apr 2014 20:03:47 +0200 Subject: adjust test to service list in config --- test/functional/v1/services_controller_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb index 35a9de6..bcb7abc 100644 --- a/test/functional/v1/services_controller_test.rb +++ b/test/functional/v1/services_controller_test.rb @@ -6,7 +6,8 @@ class V1::ServicesControllerTest < ActionController::TestCase get :show, format: :json assert_json_response name: 'anonymous', cert_prefix: 'LIMITED', - description: 'anonymous account, with rate limited VPN' + description: 'anonymous account, with rate limited VPN', + services: ["eip"] end test "user can see their service info" do @@ -16,7 +17,8 @@ class V1::ServicesControllerTest < ActionController::TestCase cert_prefix: 'LIMITED', description: 'free account, with rate limited VPN', cost: 0, - quota: 100 + quota: 100, + services: ["eip", "email"] end end -- cgit v1.2.3 From 9216ab8252246a263c5d17f6755a7d3887145f94 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 18 Apr 2014 11:55:40 +0200 Subject: change service level configuration strategy The changes to the configuration required some non minor changes to the platform and also added some flexibility we don't require yet - and thus some new possibilities for errors. So instead we still use the allow_..._certs and ..._cert_prefix options. They basically provide the framework in which service levels can operate. The service level configuration will not include the cert prefix anymore. It only states if the service level is rate limited or not. This avoids conflicts between the two configuration options. I also removed the anonymous service level entirely. It was also turning a boolean decision (do we provide anonymous eip or not) into something way more complex. Instead I added the AnonymousServiceLevel class to handle the corner cases for people who are not logged in. Furthermore i renamed the UnauthenticatedUser to AnonymousUser so it matches the Anonymous Service Level nicely. It's also shorter and more intuitive. --- test/functional/v1/certs_controller_test.rb | 30 ++++++++++++++------------ test/functional/v1/services_controller_test.rb | 23 ++++++++++++++------ test/unit/anonymous_user_test.rb | 23 ++++++++++++++++++++ test/unit/unauthenticated_user_test.rb | 7 ------ 4 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 test/unit/anonymous_user_test.rb delete mode 100644 test/unit/unauthenticated_user_test.rb (limited to 'test') diff --git a/test/functional/v1/certs_controller_test.rb b/test/functional/v1/certs_controller_test.rb index 3631947..fb8e9c4 100644 --- a/test/functional/v1/certs_controller_test.rb +++ b/test/functional/v1/certs_controller_test.rb @@ -2,19 +2,23 @@ require 'test_helper' class V1::CertsControllerTest < ActionController::TestCase - test "send limited cert without login" do - cert = expect_cert('LIMITED') - get :show - assert_response :success - assert_equal cert.to_s, @response.body + test "send unlimited cert without login" do + with_config allow_anonymous_certs: true do + cert = expect_cert('UNLIMITED') + get :show + assert_response :success + assert_equal cert.to_s, @response.body + end end test "send limited cert" do - login - cert = expect_cert('LIMITED') - get :show - assert_response :success - assert_equal cert.to_s, @response.body + with_config allow_limited_certs: true do + login + cert = expect_cert('LIMITED') + get :show + assert_response :success + assert_equal cert.to_s, @response.body + end end test "send unlimited cert" do @@ -26,10 +30,8 @@ class V1::CertsControllerTest < ActionController::TestCase end test "redirect if no eip service offered" do - with_config({service_levels: {0 => {services: []}}}) do - get :show - assert_response :redirect - end + get :show + assert_response :redirect end protected diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb index bcb7abc..b81103f 100644 --- a/test/functional/v1/services_controller_test.rb +++ b/test/functional/v1/services_controller_test.rb @@ -2,23 +2,32 @@ require 'test_helper' class V1::ServicesControllerTest < ActionController::TestCase - test "anonymous user can request service info" do + test "anonymous user gets login required service info" do get :show, format: :json assert_json_response name: 'anonymous', - cert_prefix: 'LIMITED', - description: 'anonymous account, with rate limited VPN', - services: ["eip"] + eip_rate_limit: false, + description: 'please login to access our services', + cost: 0 + end + + test "anonymous user gets vpn service info" do + with_config allow_anonymous_certs: true do + get :show, format: :json + assert_json_response name: 'anonymous', + eip_rate_limit: false, + description: 'anonymous access to the VPN', + cost: 0 + end end test "user can see their service info" do login get :show, format: :json assert_json_response name: 'free', - cert_prefix: 'LIMITED', + eip_rate_limit: true, description: 'free account, with rate limited VPN', cost: 0, - quota: 100, - services: ["eip", "email"] + quota: 100 end end diff --git a/test/unit/anonymous_user_test.rb b/test/unit/anonymous_user_test.rb new file mode 100644 index 0000000..6e94d39 --- /dev/null +++ b/test/unit/anonymous_user_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +class AnonymousUserTest < ActiveSupport::TestCase + + setup do + @anonymous = AnonymousUser.new + end + + test "has nil values" do + assert_nil @anonymous.id + assert_nil @anonymous.email_address + assert_nil @anonymous.login + end + + test "has no messages" do + assert_equal [], @anonymous.messages + end + + test "has anonymous service level" do + assert @anonymous.effective_service_level.is_a? AnonymousServiceLevel + end + +end diff --git a/test/unit/unauthenticated_user_test.rb b/test/unit/unauthenticated_user_test.rb deleted file mode 100644 index e5fafb8..0000000 --- a/test/unit/unauthenticated_user_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class UnauthenticatedUserTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end -- cgit v1.2.3 From be81b7430e0a2046125be7c3a4b01b8725f4afe6 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 18 Apr 2014 12:51:18 +0200 Subject: adopt service_level config to platform settings cost -> rate quota -> storage --- test/functional/v1/services_controller_test.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb index b81103f..e4058c0 100644 --- a/test/functional/v1/services_controller_test.rb +++ b/test/functional/v1/services_controller_test.rb @@ -6,8 +6,7 @@ class V1::ServicesControllerTest < ActionController::TestCase get :show, format: :json assert_json_response name: 'anonymous', eip_rate_limit: false, - description: 'please login to access our services', - cost: 0 + description: 'please login to access our services' end test "anonymous user gets vpn service info" do @@ -15,8 +14,7 @@ class V1::ServicesControllerTest < ActionController::TestCase get :show, format: :json assert_json_response name: 'anonymous', eip_rate_limit: false, - description: 'anonymous access to the VPN', - cost: 0 + description: 'anonymous access to the VPN' end end @@ -26,8 +24,7 @@ class V1::ServicesControllerTest < ActionController::TestCase assert_json_response name: 'free', eip_rate_limit: true, description: 'free account, with rate limited VPN', - cost: 0, - quota: 100 + storage: 100 end end -- cgit v1.2.3 From f23ca91c01ce14d75c221ccddb7d8b1b7e2c0cef Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 24 Apr 2014 13:17:06 +0200 Subject: make test independent of button tag input or button can be used --- test/integration/browser/account_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb index a5677ad..6d5f7f9 100644 --- a/test/integration/browser/account_test.rb +++ b/test/integration/browser/account_test.rb @@ -131,9 +131,9 @@ class AccountTest < BrowserIntegrationTest end def assert_invalid_login(page) - assert page.has_selector? 'input.btn-primary.disabled' + assert page.has_selector? '.btn-primary.disabled' assert page.has_content? I18n.t(:invalid_user_pass) - assert page.has_no_selector? 'input.btn-primary.disabled' + assert page.has_no_selector? '.btn-primary.disabled' end def inject_malicious_js -- cgit v1.2.3 From a2909d781a790e47acbdbb8b4560177100ad9942 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 25 Apr 2014 16:51:03 +0200 Subject: basic password validation without client side gem The client_side_validations gem is not maintained anymore and the validations were not working lately. So instead of trying to fix it I started working on independent validations for the password as it can't be validated on the server due to SRP. So far these validations are very primitive. They require 8 characters length and a matching confirmation. --- .../browser/password_validation_test.rb | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/integration/browser/password_validation_test.rb (limited to 'test') diff --git a/test/integration/browser/password_validation_test.rb b/test/integration/browser/password_validation_test.rb new file mode 100644 index 0000000..45eb0bf --- /dev/null +++ b/test/integration/browser/password_validation_test.rb @@ -0,0 +1,31 @@ +require 'test_helper' + +class PasswordValidationTest < BrowserIntegrationTest + + test "password confirmation is validated" do + 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 + "-typo" + click_on 'Sign Up' + assert page.has_content? "does not match." + assert_equal '/users/new', current_path + assert page.has_selector? ".error #srp_password_confirmation" + end + + test "password needs to be at least 8 chars long" do + username ||= "test_#{SecureRandom.urlsafe_base64}".downcase + password ||= SecureRandom.base64[0,7] + visit '/users/new' + fill_in 'Username', with: username + fill_in 'Password', with: password + fill_in 'Password confirmation', with: password + click_on 'Sign Up' + assert page.has_content? "needs to be at least 8 characters long" + assert_equal '/users/new', current_path + assert page.has_selector? ".error #srp_password" + end +end + -- cgit v1.2.3 From 71814734c0a0f2e384f2e72e4d7c70aea04c01ab Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 30 Apr 2014 09:21:18 +0200 Subject: remove outdated os detection test --- test/integration/os_detection_test.rb | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/integration/os_detection_test.rb (limited to 'test') diff --git a/test/integration/os_detection_test.rb b/test/integration/os_detection_test.rb deleted file mode 100644 index 6d9a648..0000000 --- a/test/integration/os_detection_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'test_helper' - -class OsDetectionTest < BrowserIntegrationTest - - test "old windows shows deactivated download" do - page.driver.add_headers "User-Agent" => "Win98" - visit '/' - assert_selector "html.oldwin" - assert has_text? "not available" - end - - test "android shows android download" do - page.driver.add_headers "User-Agent" => "Android" - visit '/' - assert_selector "html.android" - assert has_no_text? "not available" - assert_selector "small", text: "Android" - end - -end -- cgit v1.2.3 From 3952a7dfcd13993bd51ac7dc07cb3a01b4658e25 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 30 Apr 2014 16:35:39 +0200 Subject: hide srp forms when no js is available Hiding them using two mechanisms in case one fails: .hidden class - bootstrap hides them then style='display:none' - so they are hidden even if css load fails --- test/integration/browser/account_test.rb | 14 ++++++++++++++ test/support/browser_integration_test.rb | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb index 6d5f7f9..4e11520 100644 --- a/test/integration/browser/account_test.rb +++ b/test/integration/browser/account_test.rb @@ -123,6 +123,20 @@ class AccountTest < BrowserIntegrationTest assert page.has_content?("server failed") end + test "does not render signup form without js" do + Capybara.current_driver = :rack_test # no js + visit '/signup' + assert page.has_no_content?("Username") + assert page.has_no_content?("Password") + end + + test "does not render login form without js" do + Capybara.current_driver = :rack_test # no js + visit '/login' + assert page.has_no_content?("Username") + assert page.has_no_content?("Password") + end + def attempt_login(username, password) click_on 'Log In' fill_in 'Username', with: username diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index 2885c3a..9cae8cb 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -60,7 +60,6 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest 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 "=========================" @@ -76,6 +75,9 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest test_log.puts "------------------------" test_log.puts `tail log/test.log -n 200` end + page.save_screenshot screenshot_path + # some drivers do not support screenshots + rescue Capybara::NotSupportedByDriverError end end -- cgit v1.2.3 From 86eb9062f1e81302647bf18ce0f5fd981202b68a Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 13 May 2014 09:51:36 +0200 Subject: allow for usernames with dots preparing for #5664 with some test improvements i ran into this issue This commit includes a fix and the test improvements. In particular it adds BrowserIntegrationTest#login - so there is no need to go through the signup procedure everytime you want a user to be logged in. --- test/integration/browser/account_test.rb | 27 +++++++++++++++------------ test/integration/browser/session_test.rb | 20 +++++--------------- test/support/browser_integration_test.rb | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 27 deletions(-) (limited to 'test') diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb index 4e11520..491a9e1 100644 --- a/test/integration/browser/account_test.rb +++ b/test/integration/browser/account_test.rb @@ -6,7 +6,7 @@ class AccountTest < BrowserIntegrationTest Identity.destroy_all_disabled end - test "normal account workflow" do + test "signup successfully" do username, password = submit_signup assert page.has_content?("Welcome #{username}") click_on 'Logout' @@ -16,6 +16,12 @@ class AccountTest < BrowserIntegrationTest user.account.destroy end + test "signup with username ending in dot json" do + username = Faker::Internet.user_name + '.json' + submit_signup username + assert page.has_content?("Welcome #{username}") + end + test "successful login" do username, password = submit_signup click_on 'Logout' @@ -51,7 +57,7 @@ class AccountTest < BrowserIntegrationTest end test "default user actions" do - username, password = submit_signup + login click_on "Account Settings" assert page.has_content? I18n.t('destroy_my_account') assert page.has_no_css? '#update_login_and_password' @@ -59,8 +65,8 @@ class AccountTest < BrowserIntegrationTest end test "default admin actions" do - username, password = submit_signup - with_config admins: [username] do + login + with_config admins: [@user.login] do click_on "Account Settings" assert page.has_content? I18n.t('destroy_my_account') assert page.has_no_css? '#update_login_and_password' @@ -70,7 +76,7 @@ class AccountTest < BrowserIntegrationTest test "change password" do with_config user_actions: ['change_password'] do - username, password = submit_signup + login click_on "Account Settings" within('#update_login_and_password') do fill_in 'Password', with: "other password" @@ -78,16 +84,15 @@ class AccountTest < BrowserIntegrationTest 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 + attempt_login(@user.login, "other password") + assert page.has_content?("Welcome #{@user.login}") 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 + login click_on "Account Settings" within('#update_pgp_key') do fill_in 'Public key', with: pgp_key @@ -97,9 +102,7 @@ class AccountTest < BrowserIntegrationTest # 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 + assert_equal pgp_key, @user.reload.public_key end end diff --git a/test/integration/browser/session_test.rb b/test/integration/browser/session_test.rb index 3a41b3a..fb20847 100644 --- a/test/integration/browser/session_test.rb +++ b/test/integration/browser/session_test.rb @@ -2,26 +2,16 @@ 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}") + login + assert page.has_content?("Logout") end test "expired session" do - assert page.has_content?("Welcome #{@username}") - pretend_now_is(Time.now + 40.minutes) do + login + pretend_now_is(Time.now + 80.minutes) do visit '/' - assert page.has_no_content?("Welcome #{@username}") + assert page.has_content?("Log In") end end end diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index 9cae8cb..836eb63 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -53,6 +53,22 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest return username, password end + # currently this only works for tests with poltergeist. + def login(user = nil) + user ||= @user ||= FactoryGirl.create(:user) + token = Token.create user_id: user.id + page.driver.add_header "Authorization", + 'Token token="' + token.to_s + '"' + visit '/' + end + + teardown do + if @user && @user.reload + Identity.destroy_all_for @user + @user.destroy + end + end + add_teardown_hook do |testcase| unless testcase.passed? testcase.save_state -- cgit v1.2.3 From 0261e82686ec4fcfc8b633664fadb1dd6d9c8070 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 13 May 2014 10:52:55 +0200 Subject: keep empty email field if user removed prefill We should respect the users choice. We can still get their email from the user id if we really need to. --- test/factories.rb | 5 +++++ test/support/browser_integration_test.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/factories.rb b/test/factories.rb index ac9333c..bebda5c 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -22,6 +22,11 @@ FactoryGirl.define do admin.stubs(:is_admin?).returns(true) end end + + factory :premium_user do + effective_service_level_code 2 + end + end factory :token do diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index 836eb63..dbd56a9 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -55,7 +55,7 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest # currently this only works for tests with poltergeist. def login(user = nil) - user ||= @user ||= FactoryGirl.create(:user) + @user ||= user ||= FactoryGirl.create(:user) token = Token.create user_id: user.id page.driver.add_header "Authorization", 'Token token="' + token.to_s + '"' -- cgit v1.2.3 From 84ce597ad0516b92d6633c1f81c03517b5d74004 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 13 May 2014 11:01:10 +0200 Subject: minor: use %Q for interpolated string with " --- test/support/browser_integration_test.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'test') diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index dbd56a9..1c872ff 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -57,8 +57,7 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest def login(user = nil) @user ||= user ||= FactoryGirl.create(:user) token = Token.create user_id: user.id - page.driver.add_header "Authorization", - 'Token token="' + token.to_s + '"' + page.driver.add_header "Authorization", %Q(Token token="#{token}") visit '/' end -- cgit v1.2.3 From bbe9de73352b5aa937173b4158267f6a37e9ca5f Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 13 May 2014 14:03:53 +0200 Subject: destinguish user.email from user.email_address use the former if you want a working email account or nil, the latter if you want the email address associated with a given user no matter if the user actually has an email account or not. --- test/unit/account_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/account_test.rb b/test/unit/account_test.rb index 4fb3c3d..b2bfe27 100644 --- a/test/unit/account_test.rb +++ b/test/unit/account_test.rb @@ -8,7 +8,7 @@ class AccountTest < ActiveSupport::TestCase test "create a new account" do user = Account.create(FactoryGirl.attributes_for(:user)) - assert user.valid? + assert user.valid?, "unexpected errors: #{user.errors.inspect}" assert user.persisted? assert id = user.identity assert_equal user.email_address, id.address -- cgit v1.2.3 From 3278e474a32ef4926b1dab0d97ca4df1c59aa2a0 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 13 May 2014 17:21:08 +0200 Subject: adjust tests to new config and method implementation Ticket.is_creator_vlidated? now actually fetches the user from the db and returns false if it does not exist. --- test/functional/v1/services_controller_test.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb index e4058c0..cde7d9f 100644 --- a/test/functional/v1/services_controller_test.rb +++ b/test/functional/v1/services_controller_test.rb @@ -21,10 +21,8 @@ class V1::ServicesControllerTest < ActionController::TestCase test "user can see their service info" do login get :show, format: :json - assert_json_response name: 'free', - eip_rate_limit: true, - description: 'free account, with rate limited VPN', - storage: 100 + default_level = APP_CONFIG[:default_service_level] + assert_json_response APP_CONFIG[:service_levels][default_level] end end -- cgit v1.2.3