mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Tests: - All users tests pass. Signed-off-by: Veiko Aasa <veiko17@disroot.org> [sunil: Update to reflect the new utility function name] [sunil: Update some more cases to use the utility] Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
401 lines
15 KiB
Python
401 lines
15 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||
"""
|
||
Functional, browser based tests for users app.
|
||
"""
|
||
|
||
# TODO Scenario: Add user to wiki group
|
||
# TODO Scenario: Remove user from wiki group
|
||
|
||
import subprocess
|
||
import urllib
|
||
|
||
import pytest
|
||
|
||
from plinth.tests import functional
|
||
|
||
_admin_password = functional.config['DEFAULT']['password']
|
||
|
||
pytestmark = [pytest.mark.system, pytest.mark.essential, pytest.mark.users]
|
||
|
||
_language_codes = {
|
||
'None': '',
|
||
'Deutsch': 'de',
|
||
'Nederlands': 'nl',
|
||
'Português': 'pt',
|
||
'Türkçe': 'tr',
|
||
'dansk': 'da',
|
||
'español': 'es',
|
||
'français': 'fr',
|
||
'norsk (bokmål)': 'nb',
|
||
'polski': 'pl',
|
||
'svenska': 'sv',
|
||
'Русский': 'ru',
|
||
'తెలుగు': 'te',
|
||
'简体中文': 'zh-hans'
|
||
}
|
||
|
||
_config_page_title_language_map = {
|
||
'': 'General Configuration',
|
||
'da': 'Generel Konfiguration',
|
||
'de': 'Allgemeine Konfiguration',
|
||
'es': 'Configuración general',
|
||
'fr': 'Configuration générale',
|
||
'nb': 'Generelt oppsett',
|
||
'nl': 'Algemene Instellingen',
|
||
'pl': 'Ustawienia główne',
|
||
'pt': 'Configuração Geral',
|
||
'ru': 'Общие настройки',
|
||
'sv': 'Allmän Konfiguration',
|
||
'te': 'సాధారణ ఆకృతీకరణ',
|
||
'tr': 'Genel Yapılandırma',
|
||
'zh-hans': '常规配置',
|
||
}
|
||
|
||
|
||
@pytest.fixture(scope='module', autouse=True)
|
||
def fixture_background(session_browser):
|
||
"""Unset language."""
|
||
yield
|
||
functional.login(session_browser)
|
||
functional.user_set_language(session_browser, _language_codes['None'])
|
||
|
||
|
||
@pytest.fixture(scope='function', autouse=True)
|
||
def fixture_login(session_browser):
|
||
"""Login."""
|
||
functional.login(session_browser)
|
||
|
||
|
||
def test_create_user(session_browser):
|
||
"""Test creating a user."""
|
||
_delete_user(session_browser, 'alice')
|
||
|
||
functional.create_user(session_browser, 'alice', email='alice@example.com')
|
||
assert functional.user_exists(session_browser, 'alice')
|
||
assert _get_email(session_browser, 'alice') == 'alice@example.com'
|
||
|
||
|
||
def test_rename_user(session_browser, host_sudo):
|
||
"""Test renaming a user."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
_delete_user(session_browser, 'bob')
|
||
|
||
_rename_user(session_browser, 'alice', 'bob')
|
||
assert not functional.user_exists(session_browser, 'alice')
|
||
assert functional.user_exists(session_browser, 'bob')
|
||
|
||
assert 'cn: bob' in host_sudo.check_output('ldapfinger bob')
|
||
assert host_sudo.user('bob').gecos == 'bob'
|
||
assert host_sudo.user('bob').home == '/home/bob'
|
||
|
||
|
||
def test_admin_users_can_change_own_ssh_keys(session_browser):
|
||
"""Test that admin users can change their own ssh keys."""
|
||
_set_ssh_keys(session_browser, 'somekey123')
|
||
assert _get_ssh_keys(session_browser) == 'somekey123'
|
||
|
||
|
||
def test_non_admin_users_can_change_own_ssh_keys(session_browser):
|
||
"""Test that non-admin users can change their own ssh keys."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
'alice')
|
||
_set_ssh_keys(session_browser, 'somekey456')
|
||
assert _get_ssh_keys(session_browser) == 'somekey456'
|
||
|
||
|
||
def test_admin_users_can_change_other_users_ssh_keys(session_browser):
|
||
"""Test that admin users can change other user's ssh keys."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
_set_ssh_keys(session_browser, 'alicesomekey123', username='alice')
|
||
assert _get_ssh_keys(session_browser,
|
||
username='alice') == 'alicesomekey123'
|
||
|
||
|
||
def test_users_can_remove_ssh_keys(session_browser):
|
||
"""Test that users can remove ssh keys."""
|
||
_set_ssh_keys(session_browser, 'somekey123')
|
||
_set_ssh_keys(session_browser, '')
|
||
assert _get_ssh_keys(session_browser) == ''
|
||
|
||
|
||
def test_users_can_connect_passwordless_over_ssh(session_browser,
|
||
tmp_path_factory):
|
||
"""Test that users can connect passwordless over ssh if the keys are
|
||
set."""
|
||
functional.app_enable(session_browser, 'ssh')
|
||
_configure_ssh_keys(session_browser, tmp_path_factory)
|
||
_should_connect_passwordless_over_ssh(session_browser, tmp_path_factory)
|
||
|
||
|
||
def test_ssh_passwordless_after_user_rename(session_browser, tmp_path_factory):
|
||
"""Test that users can connect passwordless after user is renamed."""
|
||
username_old = 'bob'
|
||
username_new = 'bob2'
|
||
functional.app_enable(session_browser, 'ssh')
|
||
_non_admin_user_exists(session_browser, username_old,
|
||
groups=['freedombox-ssh'])
|
||
_delete_user(session_browser, username_new)
|
||
_configure_ssh_keys(session_browser, tmp_path_factory,
|
||
username=username_old)
|
||
_should_connect_passwordless_over_ssh(session_browser, tmp_path_factory,
|
||
username=username_old)
|
||
|
||
_rename_user(session_browser, username_old, username_new)
|
||
|
||
assert functional.user_exists(session_browser, username_new)
|
||
_should_connect_passwordless_over_ssh(session_browser, tmp_path_factory,
|
||
username=username_new)
|
||
|
||
|
||
def test_users_cannot_connect_passwordless_over_ssh(session_browser,
|
||
tmp_path_factory):
|
||
"""Test that users cannot connect passwordless over ssh if the keys aren't
|
||
set."""
|
||
functional.app_enable(session_browser, 'ssh')
|
||
_configure_ssh_keys(session_browser, tmp_path_factory)
|
||
_set_ssh_keys(session_browser, '')
|
||
_should_not_connect_passwordless_over_ssh(session_browser,
|
||
tmp_path_factory)
|
||
|
||
|
||
def test_update_user(session_browser):
|
||
"""Test changing properties of a user."""
|
||
functional.create_user(session_browser, 'alice', email='alice@example.com')
|
||
|
||
# Update email
|
||
_set_email(session_browser, 'alice', 'alice1@example.com')
|
||
assert _get_email(session_browser, 'alice') == 'alice1@example.com'
|
||
_set_email(session_browser, 'alice', 'alice2@example.com')
|
||
assert _get_email(session_browser, 'alice') == 'alice2@example.com'
|
||
|
||
|
||
@pytest.mark.parametrize('language_code', _language_codes.values())
|
||
def test_change_language(session_browser, language_code):
|
||
"""Test changing the language."""
|
||
functional.user_set_language(session_browser, language_code)
|
||
assert _check_language(session_browser, language_code)
|
||
|
||
|
||
def test_user_states(session_browser, tmp_path_factory):
|
||
"""Test that admin users can set other users as inactive/active."""
|
||
username = 'bob2'
|
||
_non_admin_user_exists(session_browser, username,
|
||
groups=['freedombox-ssh'])
|
||
_configure_ssh_keys(session_browser, tmp_path_factory, username=username)
|
||
|
||
# Test set user inactive
|
||
_set_user_status(session_browser, username, 'inactive')
|
||
# Test Django login
|
||
_cannot_log_in(session_browser, username)
|
||
# Test PAM/nslcd authorization
|
||
_should_not_connect_passwordless_over_ssh(session_browser,
|
||
tmp_path_factory,
|
||
username=username)
|
||
|
||
# Test set user active
|
||
functional.login(session_browser)
|
||
_set_user_status(session_browser, username, 'active')
|
||
_can_log_in(session_browser, username)
|
||
_should_connect_passwordless_over_ssh(session_browser, tmp_path_factory,
|
||
username=username)
|
||
|
||
|
||
def test_admin_users_can_change_own_password(session_browser):
|
||
"""Test that admin users can change their own password."""
|
||
_admin_user_exists(session_browser, 'testadmin')
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
'testadmin')
|
||
_change_password(session_browser, 'newpassword456')
|
||
_can_log_in_with_password(session_browser, 'testadmin', 'newpassword456')
|
||
|
||
|
||
def test_admin_users_can_change_others_password(session_browser):
|
||
"""Test that admin users can change other user's password."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
_change_password(session_browser, 'secretsecret567', username='alice')
|
||
_can_log_in_with_password(session_browser, 'alice', 'secretsecret567')
|
||
|
||
|
||
def test_non_admin_users_can_change_own_password(session_browser):
|
||
"""Test that non-admin users can change their own password."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
'alice')
|
||
_change_password(session_browser, 'newpassword123')
|
||
_can_log_in_with_password(session_browser, 'alice', 'newpassword123')
|
||
|
||
|
||
def test_delete_user(session_browser):
|
||
"""Test deleting a user."""
|
||
_non_admin_user_exists(session_browser, 'alice')
|
||
functional.delete_user(session_browser, 'alice')
|
||
assert not functional.user_exists(session_browser, 'alice')
|
||
|
||
|
||
def _delete_user(session_browser, name):
|
||
"""Delete a user."""
|
||
if functional.user_exists(session_browser, name):
|
||
functional.delete_user(session_browser, name)
|
||
|
||
|
||
def _admin_user_exists(session_browser, name):
|
||
_delete_user(session_browser, name)
|
||
functional.create_user(session_browser, name, groups=['admin'])
|
||
|
||
|
||
def _non_admin_user_exists(session_browser, name, groups=[]):
|
||
_delete_user(session_browser, name)
|
||
functional.create_user(session_browser, name, groups=groups)
|
||
|
||
|
||
def _generate_ssh_keys(session_browser, key_file):
|
||
try:
|
||
key_file.unlink()
|
||
except FileNotFoundError:
|
||
pass
|
||
|
||
subprocess.check_call(
|
||
['ssh-keygen', '-t', 'ed25519', '-N', '', '-q', '-f',
|
||
str(key_file)])
|
||
|
||
|
||
def _configure_ssh_keys(session_browser, tmp_path_factory, username=None):
|
||
key_file = tmp_path_factory.getbasetemp() / 'users-ssh.key'
|
||
_generate_ssh_keys(session_browser, key_file)
|
||
public_key_file = key_file.with_suffix(key_file.suffix + '.pub')
|
||
public_key = public_key_file.read_text()
|
||
_set_ssh_keys(session_browser, public_key, username=username)
|
||
|
||
|
||
def _can_log_in(session_browser, username):
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
username)
|
||
assert len(session_browser.find_by_id('id_user_menu')) > 0
|
||
|
||
|
||
def _can_log_in_with_password(session_browser, username, password):
|
||
functional.logout(session_browser)
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
username, password)
|
||
assert len(session_browser.find_by_id('id_user_menu')) > 0
|
||
|
||
|
||
def _cannot_log_in(session_browser, username):
|
||
functional.login_with_account(session_browser, functional.base_url,
|
||
username)
|
||
assert len(session_browser.find_by_id('id_user_menu')) == 0
|
||
|
||
|
||
def _should_connect_passwordless_over_ssh(session_browser, tmp_path_factory,
|
||
username=None):
|
||
key_file = tmp_path_factory.getbasetemp() / 'users-ssh.key'
|
||
_try_login_to_ssh(key_file=key_file, username=username)
|
||
|
||
|
||
def _should_not_connect_passwordless_over_ssh(session_browser,
|
||
tmp_path_factory, username=None):
|
||
key_file = tmp_path_factory.getbasetemp() / 'users-ssh.key'
|
||
with pytest.raises(subprocess.CalledProcessError):
|
||
_try_login_to_ssh(key_file=key_file, username=username)
|
||
|
||
|
||
def _rename_user(browser, old_name, new_name):
|
||
functional.nav_to_module(browser, 'users')
|
||
functional.click_link_by_href(browser,
|
||
f'/plinth/sys/users/{old_name}/edit/')
|
||
browser.find_by_id('id_username').fill(new_name)
|
||
browser.find_by_id('id_confirm_password').fill(_admin_password)
|
||
functional.submit(browser, form_class='form-update')
|
||
|
||
|
||
def _set_email(browser, username, email):
|
||
"""Set the email field value for a user."""
|
||
functional.visit(browser, '/plinth/sys/users/{}/edit/'.format(username))
|
||
browser.find_by_id('id_email').fill(email)
|
||
browser.find_by_id('id_confirm_password').fill(_admin_password)
|
||
functional.submit(browser, form_class='form-update')
|
||
|
||
|
||
def _get_email(browser, username):
|
||
"""Return the email field value for a user."""
|
||
functional.visit(browser, '/plinth/sys/users/{}/edit/'.format(username))
|
||
return browser.find_by_id('id_email').value
|
||
|
||
|
||
def _check_language(browser, language_code):
|
||
functional.nav_to_module(browser, 'config')
|
||
return browser.find_by_css('.app-titles').first.find_by_tag(
|
||
'h2').first.value == _config_page_title_language_map[language_code]
|
||
|
||
|
||
def _get_ssh_keys(browser, username=None):
|
||
functional.visit(browser, '/plinth/')
|
||
if username is None:
|
||
browser.find_by_id('id_user_menu').click()
|
||
functional.click_and_wait(browser,
|
||
browser.find_by_id('id_user_edit_menu'))
|
||
else:
|
||
functional.visit(browser, f'/plinth/sys/users/{username}/edit/')
|
||
return browser.find_by_id('id_ssh_keys').text
|
||
|
||
|
||
def _set_ssh_keys(browser, ssh_keys, username=None):
|
||
if username is None:
|
||
browser.find_by_id('id_user_menu').click()
|
||
functional.click_and_wait(browser,
|
||
browser.find_by_id('id_user_edit_menu'))
|
||
else:
|
||
functional.visit(browser, f'/plinth/sys/users/{username}/edit/')
|
||
|
||
current_user = browser.find_by_id('id_user_menu_link').text
|
||
auth_password = functional.get_password(current_user)
|
||
|
||
browser.find_by_id('id_ssh_keys').fill(ssh_keys)
|
||
browser.find_by_id('id_confirm_password').fill(auth_password)
|
||
|
||
functional.submit(browser, form_class='form-update')
|
||
|
||
|
||
def _set_user_status(browser, username, status):
|
||
functional.visit(browser, '/plinth/sys/users/{}/edit/'.format(username))
|
||
if status == 'inactive':
|
||
browser.find_by_id('id_is_active').uncheck()
|
||
elif status == 'active':
|
||
browser.find_by_id('id_is_active').check()
|
||
|
||
browser.find_by_id('id_confirm_password').fill(_admin_password)
|
||
functional.submit(browser, form_class='form-update')
|
||
|
||
|
||
def _change_password(browser, new_password, current_password=None,
|
||
username=None):
|
||
if username is None:
|
||
browser.find_by_id('id_user_menu').click()
|
||
functional.click_and_wait(
|
||
browser, browser.find_by_id('id_change_password_menu'))
|
||
else:
|
||
functional.visit(
|
||
browser, '/plinth/sys/users/{}/change_password/'.format(username))
|
||
|
||
current_user = browser.find_by_id('id_user_menu_link').text
|
||
auth_password = current_password or functional.get_password(current_user)
|
||
|
||
browser.find_by_id('id_new_password1').fill(new_password)
|
||
browser.find_by_id('id_new_password2').fill(new_password)
|
||
browser.find_by_id('id_confirm_password').fill(auth_password)
|
||
functional.submit(browser, form_class='form-change-password')
|
||
|
||
|
||
def _try_login_to_ssh(key_file=None, username=None):
|
||
user = username if username else functional.config['DEFAULT']['username']
|
||
hostname = urllib.parse.urlparse(
|
||
functional.config['DEFAULT']['url']).hostname
|
||
port = functional.config['DEFAULT']['ssh_port']
|
||
|
||
subprocess.check_call([
|
||
'ssh', '-p', port, '-i', key_file, '-q', '-o',
|
||
'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', '-o',
|
||
'BatchMode=yes', '{0}@{1}'.format(user, hostname), '/bin/true'
|
||
])
|