2023-12-20 10:32:02 +00:00
|
|
|
"""
|
|
|
|
Action-level tests of ssh
|
|
|
|
(For API-independent logic incl. connection to persistent storage)
|
|
|
|
"""
|
|
|
|
|
2023-12-15 12:48:18 +00:00
|
|
|
import pytest
|
2023-12-21 13:05:06 +00:00
|
|
|
from typing import Optional
|
2023-12-15 12:48:18 +00:00
|
|
|
|
2023-12-20 10:26:54 +00:00
|
|
|
from selfprivacy_api.actions.ssh import (
|
|
|
|
set_ssh_settings,
|
|
|
|
get_ssh_settings,
|
|
|
|
create_ssh_key,
|
|
|
|
remove_ssh_key,
|
2023-12-20 11:36:58 +00:00
|
|
|
KeyNotFound,
|
2023-12-22 10:09:51 +00:00
|
|
|
UserNotFound,
|
2023-12-20 10:26:54 +00:00
|
|
|
)
|
2023-12-21 13:05:06 +00:00
|
|
|
from selfprivacy_api.actions.users import (
|
|
|
|
get_users,
|
|
|
|
get_user_by_username,
|
|
|
|
UserDataUserOrigin,
|
|
|
|
)
|
2023-12-15 12:48:18 +00:00
|
|
|
from selfprivacy_api.utils import WriteUserData, ReadUserData
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=[True, False])
|
|
|
|
def bool_value(request):
|
|
|
|
return request.param
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(
|
|
|
|
params=[
|
|
|
|
"normal_populated_json",
|
|
|
|
"deleted_enabled",
|
|
|
|
"deleted_auth",
|
|
|
|
"empty",
|
|
|
|
"ssh_not_in_json",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
def possibly_undefined_ssh_settings(generic_userdata, request, bool_value):
|
|
|
|
with WriteUserData() as data:
|
|
|
|
data["ssh"] = {"enable": bool_value, "passswordAuthentication": bool_value}
|
|
|
|
assert get_raw_json_ssh_setting("enable") == bool_value
|
|
|
|
assert get_raw_json_ssh_setting("passswordAuthentication") == bool_value
|
|
|
|
|
|
|
|
if request.param == "deleted_enabled":
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["enable"]
|
|
|
|
|
|
|
|
if request.param == "deleted_auth":
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["passswordAuthentication"]
|
|
|
|
|
|
|
|
if request.param == "empty":
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["passswordAuthentication"]
|
|
|
|
del data["ssh"]["enable"]
|
|
|
|
|
|
|
|
if request.param == "ssh_not_in_json":
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=[True, False, None])
|
|
|
|
def ssh_enable_spectrum(request):
|
|
|
|
return request.param
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=[True, False, None])
|
|
|
|
def password_auth_spectrum(request):
|
|
|
|
return request.param
|
|
|
|
|
|
|
|
|
2023-12-21 13:05:06 +00:00
|
|
|
def admin_name() -> Optional[str]:
|
|
|
|
users = get_users()
|
|
|
|
for user in users:
|
|
|
|
if user.origin == UserDataUserOrigin.PRIMARY:
|
|
|
|
return user.username
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2023-12-15 12:48:18 +00:00
|
|
|
def get_raw_json_ssh_setting(setting: str):
|
|
|
|
with ReadUserData() as data:
|
|
|
|
return (data.get("ssh") or {}).get(setting)
|
|
|
|
|
|
|
|
|
2023-12-18 11:21:21 +00:00
|
|
|
def test_read_json(possibly_undefined_ssh_settings):
|
|
|
|
with ReadUserData() as data:
|
|
|
|
if "ssh" not in data.keys():
|
|
|
|
assert get_ssh_settings().enable is not None
|
|
|
|
assert get_ssh_settings().passwordAuthentication is not None
|
|
|
|
|
|
|
|
# TODO: Is it really a good idea to have password ssh enabled by default?
|
|
|
|
assert get_ssh_settings().enable is True
|
|
|
|
assert get_ssh_settings().passwordAuthentication is True
|
|
|
|
return
|
|
|
|
|
|
|
|
if "enable" not in data["ssh"].keys():
|
|
|
|
assert get_ssh_settings().enable is True
|
|
|
|
else:
|
|
|
|
assert get_ssh_settings().enable == data["ssh"]["enable"]
|
|
|
|
|
|
|
|
if "passwordAuthentication" not in data["ssh"].keys():
|
2024-01-09 18:58:09 +00:00
|
|
|
assert get_ssh_settings().passwordAuthentication is False
|
2023-12-18 11:21:21 +00:00
|
|
|
else:
|
|
|
|
assert (
|
|
|
|
get_ssh_settings().passwordAuthentication
|
|
|
|
== data["ssh"]["passwordAuthentication"]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-15 12:48:18 +00:00
|
|
|
def test_enabling_disabling_writes_json(
|
|
|
|
possibly_undefined_ssh_settings, ssh_enable_spectrum, password_auth_spectrum
|
|
|
|
):
|
|
|
|
original_enable = get_raw_json_ssh_setting("enable")
|
|
|
|
original_password_auth = get_raw_json_ssh_setting("passwordAuthentication")
|
|
|
|
|
|
|
|
set_ssh_settings(ssh_enable_spectrum, password_auth_spectrum)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
if ssh_enable_spectrum is None:
|
|
|
|
assert get_raw_json_ssh_setting("enable") == original_enable
|
|
|
|
else:
|
|
|
|
assert get_raw_json_ssh_setting("enable") == ssh_enable_spectrum
|
|
|
|
|
|
|
|
if password_auth_spectrum is None:
|
|
|
|
assert (
|
|
|
|
get_raw_json_ssh_setting("passwordAuthentication")
|
|
|
|
== original_password_auth
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
assert (
|
|
|
|
get_raw_json_ssh_setting("passwordAuthentication")
|
|
|
|
== password_auth_spectrum
|
|
|
|
)
|
2023-12-20 10:26:54 +00:00
|
|
|
|
|
|
|
|
2023-12-21 13:05:06 +00:00
|
|
|
############### ROOTKEYS
|
|
|
|
|
|
|
|
|
2023-12-20 10:26:54 +00:00
|
|
|
def test_read_root_keys_from_json(generic_userdata):
|
|
|
|
assert get_ssh_settings().rootKeys == ["ssh-ed25519 KEY test@pc"]
|
|
|
|
new_keys = ["ssh-ed25519 KEY test@pc", "ssh-ed25519 KEY2 test@pc"]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
data["ssh"]["rootKeys"] = new_keys
|
|
|
|
|
|
|
|
assert get_ssh_settings().rootKeys == new_keys
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["rootKeys"]
|
|
|
|
|
|
|
|
assert get_ssh_settings().rootKeys == []
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]
|
|
|
|
|
|
|
|
assert get_ssh_settings().rootKeys == []
|
|
|
|
|
|
|
|
|
|
|
|
def test_removing_root_key_writes_json(generic_userdata):
|
|
|
|
# generic userdata has a a single root key
|
|
|
|
rootkeys = get_ssh_settings().rootKeys
|
|
|
|
assert len(rootkeys) == 1
|
|
|
|
key1 = rootkeys[0]
|
|
|
|
key2 = "ssh-rsa MYSUPERKEY root@pc"
|
|
|
|
|
|
|
|
create_ssh_key("root", key2)
|
|
|
|
rootkeys = get_ssh_settings().rootKeys
|
|
|
|
assert len(rootkeys) == 2
|
|
|
|
|
|
|
|
remove_ssh_key("root", key2)
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "ssh" in data
|
|
|
|
assert "rootKeys" in data["ssh"]
|
|
|
|
assert data["ssh"]["rootKeys"] == [key1]
|
|
|
|
|
|
|
|
remove_ssh_key("root", key1)
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "ssh" in data
|
|
|
|
assert "rootKeys" in data["ssh"]
|
|
|
|
assert data["ssh"]["rootKeys"] == []
|
|
|
|
|
|
|
|
|
2023-12-20 11:36:58 +00:00
|
|
|
def test_remove_root_key_on_undefined(generic_userdata):
|
|
|
|
# generic userdata has a a single root key
|
|
|
|
rootkeys = get_ssh_settings().rootKeys
|
|
|
|
assert len(rootkeys) == 1
|
|
|
|
key1 = rootkeys[0]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["rootKeys"]
|
|
|
|
|
|
|
|
with pytest.raises(KeyNotFound):
|
|
|
|
remove_ssh_key("root", key1)
|
|
|
|
rootkeys = get_ssh_settings().rootKeys
|
|
|
|
assert len(rootkeys) == 0
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]
|
|
|
|
|
|
|
|
with pytest.raises(KeyNotFound):
|
|
|
|
remove_ssh_key("root", key1)
|
|
|
|
rootkeys = get_ssh_settings().rootKeys
|
|
|
|
assert len(rootkeys) == 0
|
|
|
|
|
|
|
|
|
2023-12-20 10:26:54 +00:00
|
|
|
def test_adding_root_key_writes_json(generic_userdata):
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]
|
|
|
|
key1 = "ssh-ed25519 KEY test@pc"
|
|
|
|
key2 = "ssh-ed25519 KEY2 test@pc"
|
|
|
|
create_ssh_key("root", key1)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "ssh" in data
|
|
|
|
assert "rootKeys" in data["ssh"]
|
|
|
|
assert data["ssh"]["rootKeys"] == [key1]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["ssh"]["rootKeys"]
|
|
|
|
create_ssh_key("root", key1)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "ssh" in data
|
|
|
|
assert "rootKeys" in data["ssh"]
|
|
|
|
assert data["ssh"]["rootKeys"] == [key1]
|
|
|
|
|
|
|
|
create_ssh_key("root", key2)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "ssh" in data
|
|
|
|
assert "rootKeys" in data["ssh"]
|
|
|
|
# order is irrelevant
|
|
|
|
assert set(data["ssh"]["rootKeys"]) == set([key1, key2])
|
2023-12-21 13:05:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
############### ADMIN KEYS
|
|
|
|
|
|
|
|
|
|
|
|
def test_read_admin_keys_from_json(generic_userdata):
|
|
|
|
admin_name = "tester"
|
|
|
|
assert get_user_by_username(admin_name).ssh_keys == ["ssh-rsa KEY test@pc"]
|
|
|
|
new_keys = ["ssh-rsa KEY test@pc", "ssh-ed25519 KEY2 test@pc"]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
data["sshKeys"] = new_keys
|
|
|
|
|
2023-12-22 09:40:03 +00:00
|
|
|
assert get_user_by_username(admin_name).ssh_keys == new_keys
|
2023-12-21 13:05:06 +00:00
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["sshKeys"]
|
|
|
|
|
2023-12-22 09:40:03 +00:00
|
|
|
assert get_user_by_username(admin_name).ssh_keys == []
|
2023-12-21 13:05:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_adding_admin_key_writes_json(generic_userdata):
|
|
|
|
admin_name = "tester"
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["sshKeys"]
|
|
|
|
key1 = "ssh-ed25519 KEY test@pc"
|
|
|
|
key2 = "ssh-ed25519 KEY2 test@pc"
|
|
|
|
create_ssh_key(admin_name, key1)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "sshKeys" in data
|
|
|
|
assert data["sshKeys"] == [key1]
|
|
|
|
|
|
|
|
create_ssh_key(admin_name, key2)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "sshKeys" in data
|
|
|
|
# order is irrelevant
|
|
|
|
assert set(data["sshKeys"]) == set([key1, key2])
|
|
|
|
|
|
|
|
|
|
|
|
def test_removing_admin_key_writes_json(generic_userdata):
|
2023-12-22 10:09:51 +00:00
|
|
|
# generic userdata has a a single admin key
|
2023-12-21 13:05:06 +00:00
|
|
|
admin_name = "tester"
|
|
|
|
|
|
|
|
admin_keys = get_user_by_username(admin_name).ssh_keys
|
|
|
|
assert len(admin_keys) == 1
|
|
|
|
key1 = admin_keys[0]
|
|
|
|
key2 = "ssh-rsa MYSUPERKEY admin@pc"
|
|
|
|
|
|
|
|
create_ssh_key(admin_name, key2)
|
|
|
|
admin_keys = get_user_by_username(admin_name).ssh_keys
|
|
|
|
assert len(admin_keys) == 2
|
|
|
|
|
|
|
|
remove_ssh_key(admin_name, key2)
|
|
|
|
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "sshKeys" in data
|
|
|
|
assert data["sshKeys"] == [key1]
|
|
|
|
|
|
|
|
remove_ssh_key(admin_name, key1)
|
|
|
|
with ReadUserData() as data:
|
|
|
|
assert "sshKeys" in data
|
|
|
|
assert data["sshKeys"] == []
|
|
|
|
|
|
|
|
|
|
|
|
def test_remove_admin_key_on_undefined(generic_userdata):
|
2023-12-22 10:09:51 +00:00
|
|
|
# generic userdata has a a single admin key
|
2023-12-21 13:05:06 +00:00
|
|
|
admin_name = "tester"
|
|
|
|
|
|
|
|
admin_keys = get_user_by_username(admin_name).ssh_keys
|
|
|
|
assert len(admin_keys) == 1
|
|
|
|
key1 = admin_keys[0]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["sshKeys"]
|
|
|
|
|
|
|
|
with pytest.raises(KeyNotFound):
|
|
|
|
remove_ssh_key(admin_name, key1)
|
|
|
|
admin_keys = get_user_by_username(admin_name).ssh_keys
|
|
|
|
assert len(admin_keys) == 0
|
2023-12-22 09:40:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
############### USER KEYS
|
|
|
|
|
|
|
|
regular_users = ["user1", "user2", "user3"]
|
|
|
|
|
|
|
|
|
|
|
|
def find_user_index_in_json_users(users: list, username: str) -> Optional[int]:
|
|
|
|
for i, user in enumerate(users):
|
|
|
|
if user["username"] == username:
|
|
|
|
return i
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("username", regular_users)
|
|
|
|
def test_read_user_keys_from_json(generic_userdata, username):
|
|
|
|
old_keys = [f"ssh-rsa KEY {username}@pc"]
|
|
|
|
assert get_user_by_username(username).ssh_keys == old_keys
|
|
|
|
new_keys = ["ssh-rsa KEY test@pc", "ssh-ed25519 KEY2 test@pc"]
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
data["users"][user_index]["sshKeys"] = new_keys
|
|
|
|
|
|
|
|
assert get_user_by_username(username).ssh_keys == new_keys
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
del data["users"][user_index]["sshKeys"]
|
|
|
|
|
|
|
|
assert get_user_by_username(username).ssh_keys == []
|
|
|
|
|
|
|
|
# deeper deletions are for user getter tests, not here
|
|
|
|
|
|
|
|
|
2023-12-22 09:49:27 +00:00
|
|
|
@pytest.mark.parametrize("username", regular_users)
|
|
|
|
def test_adding_user_key_writes_json(generic_userdata, username):
|
|
|
|
with WriteUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
del data["users"][user_index]["sshKeys"]
|
|
|
|
key1 = "ssh-ed25519 KEY test@pc"
|
|
|
|
key2 = "ssh-ed25519 KEY2 test@pc"
|
|
|
|
create_ssh_key(username, key1)
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:49:27 +00:00
|
|
|
with ReadUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
assert "sshKeys" in data["users"][user_index]
|
|
|
|
assert data["users"][user_index]["sshKeys"] == [key1]
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:49:27 +00:00
|
|
|
create_ssh_key(username, key2)
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:49:27 +00:00
|
|
|
with ReadUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
assert "sshKeys" in data["users"][user_index]
|
|
|
|
# order is irrelevant
|
|
|
|
assert set(data["users"][user_index]["sshKeys"]) == set([key1, key2])
|
2023-12-22 09:40:03 +00:00
|
|
|
|
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
@pytest.mark.parametrize("username", regular_users)
|
|
|
|
def test_removing_user_key_writes_json(generic_userdata, username):
|
2023-12-22 10:09:51 +00:00
|
|
|
# generic userdata has a a single user key
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
user_keys = get_user_by_username(username).ssh_keys
|
|
|
|
assert len(user_keys) == 1
|
|
|
|
key1 = user_keys[0]
|
|
|
|
key2 = "ssh-rsa MYSUPERKEY admin@pc"
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
create_ssh_key(username, key2)
|
|
|
|
user_keys = get_user_by_username(username).ssh_keys
|
|
|
|
assert len(user_keys) == 2
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
remove_ssh_key(username, key2)
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
with ReadUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
assert "sshKeys" in data["users"][user_index]
|
|
|
|
assert data["users"][user_index]["sshKeys"] == [key1]
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 09:57:35 +00:00
|
|
|
remove_ssh_key(username, key1)
|
|
|
|
with ReadUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
assert "sshKeys" in data["users"][user_index]
|
|
|
|
assert data["users"][user_index]["sshKeys"] == []
|
2023-12-22 09:40:03 +00:00
|
|
|
|
|
|
|
|
2023-12-22 10:09:51 +00:00
|
|
|
@pytest.mark.parametrize("username", regular_users)
|
|
|
|
def test_remove_user_key_on_undefined(generic_userdata, username):
|
|
|
|
# generic userdata has a a single user key
|
|
|
|
user_keys = get_user_by_username(username).ssh_keys
|
|
|
|
assert len(user_keys) == 1
|
|
|
|
key1 = user_keys[0]
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 10:09:51 +00:00
|
|
|
with WriteUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
del data["users"][user_index]["sshKeys"]
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 10:09:51 +00:00
|
|
|
with pytest.raises(KeyNotFound):
|
|
|
|
remove_ssh_key(username, key1)
|
|
|
|
|
|
|
|
user_keys = get_user_by_username(username).ssh_keys
|
|
|
|
assert len(user_keys) == 0
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
user_index = find_user_index_in_json_users(data["users"], username)
|
|
|
|
del data["users"][user_index]
|
|
|
|
|
|
|
|
with pytest.raises(UserNotFound):
|
|
|
|
remove_ssh_key(username, key1)
|
|
|
|
|
|
|
|
with WriteUserData() as data:
|
|
|
|
del data["users"]
|
2023-12-22 09:40:03 +00:00
|
|
|
|
2023-12-22 10:09:51 +00:00
|
|
|
with pytest.raises(UserNotFound):
|
|
|
|
remove_ssh_key(username, key1)
|