add user and ssh management #12

Merged
inex merged 21 commits from def_graphql into graphql 2022-08-01 13:40:41 +03:00
23 changed files with 1855 additions and 14 deletions

View File

@ -1,2 +1,2 @@
[MASTER]
init-hook='import sys; sys.path.append("/path/to/root")'
init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))"

View File

@ -53,7 +53,6 @@ def create_app(test_config=None):
pass
elif request.path.startswith("/auth/recovery_token/use"):
pass
# TODO: REMOVE THIS
elif request.path.startswith("/graphql"):
pass
else:

View File

@ -0,0 +1,78 @@
import typing
from enum import Enum
import strawberry
from selfprivacy_api.utils import ReadUserData
from selfprivacy_api.graphql.mutations.mutation_interface import (
MutationReturnInterface,
)
inex marked this conversation as resolved Outdated

Беспорядочные импорты. Надо стремиться оформлять их так, что сначала идут системные, потом библиотечные, и потом уже локальные.

Беспорядочные импорты. Надо стремиться оформлять их так, что сначала идут системные, потом библиотечные, и потом уже локальные.
@strawberry.enum
class UserType(Enum):
NORMAL = "NORMAL"
PRIMARY = "PRIMARY"
inex marked this conversation as resolved Outdated

Возможно будет лучше сделать тип списком, который по умолчанию пустой.

sshKeys: typing.List[str] = []
Возможно будет лучше сделать тип списком, который по умолчанию пустой. ```python sshKeys: typing.List[str] = [] ```
ROOT = "ROOT"
@strawberry.type
class User:
inex marked this conversation as resolved
Review

Вводящий в заблуждение комментарий, подразумевающий что в этом классе есть функции для управления юзером.

Вводящий в заблуждение комментарий, подразумевающий что в этом классе есть функции для управления юзером.
user_type: UserType
username: str
# userHomeFolderspace: UserHomeFolderUsage
ssh_keys: typing.List[str] = strawberry.field(default_factory=list)
@strawberry.type
class UserMutationReturn(MutationReturnInterface):
"""Return type for user mutation"""
user: typing.Optional[User]
def ensure_ssh_and_users_fields_exist(data):
if "ssh" not in data:
data["ssh"] = []
data["ssh"]["rootKeys"] = []
elif data["ssh"].get("rootKeys") is None:
data["ssh"]["rootKeys"] = []
if "sshKeys" not in data:
data["sshKeys"] = []
if "users" not in data:
data["users"] = []
def get_user_by_username(username: str) -> typing.Optional[User]:
with ReadUserData() as data:
ensure_ssh_and_users_fields_exist(data)
if username == "root":
return User(
user_type=UserType.ROOT,
username="root",
ssh_keys=data["ssh"]["rootKeys"],
)
if username == data["username"]:
return User(
user_type=UserType.PRIMARY,
username=username,
ssh_keys=data["sshKeys"],
)
for user in data["users"]:
if user["username"] == username:
if "sshKeys" not in user:
user["sshKeys"] = []
return User(
user_type=UserType.NORMAL,
username=username,
ssh_keys=user["sshKeys"],
)
return None

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Users management module"""
# pylint: disable=too-few-public-methods
inex marked this conversation as resolved
Review

Ненужные пустые строки.

Ненужные пустые строки.
import strawberry
from selfprivacy_api.graphql import IsAuthenticated
from selfprivacy_api.graphql.mutations.ssh_utils import (
create_ssh_key,
remove_ssh_key,
inex marked this conversation as resolved Outdated

Неиспользованный импорт. Сверяйся с линтером перед коммитом.

Среда должна быть настроена на использование питона из nix-shell, чтобы линтер работал корректно.

Неиспользованный импорт. Сверяйся с линтером перед коммитом. Среда должна быть настроена на использование питона из nix-shell, чтобы линтер работал корректно.
)
from selfprivacy_api.graphql.common_types.user import (
UserMutationReturn,
get_user_by_username,
)
@strawberry.input
class SshMutationInput:
"""Input type for ssh mutation"""
username: str
ssh_key: str
@strawberry.type
class SshMutations:
"""Mutations ssh"""
@strawberry.mutation(permission_classes=[IsAuthenticated])
def add_ssh_key(self, ssh_input: SshMutationInput) -> UserMutationReturn:
"""Add a new ssh key"""
inex marked this conversation as resolved Outdated

Некорректный комментарий и название функции.
В референсе было указано add_ssh_key.

Некорректный комментарий и название функции. В референсе было указано `add_ssh_key`.
inex marked this conversation as resolved Outdated

settingsinput

`settings` → `input`
success, message, code = create_ssh_key(ssh_input.username, ssh_input.ssh_key)
return UserMutationReturn(
inex marked this conversation as resolved Outdated

Эта проверка была из-за legacy кода со стороны клиента.
С новой API это больше не нужно, эта функция может быть использована для добавления ключей рут пользователю.

Эта проверка была из-за legacy кода со стороны клиента. С новой API это больше не нужно, эта функция может быть использована для добавления ключей рут пользователю.
success=success,
message=message,
code=code,
user=get_user_by_username(ssh_input.username),
)
@strawberry.mutation(permission_classes=[IsAuthenticated])
def remove_ssh_key(self, ssh_input: SshMutationInput) -> UserMutationReturn:
inex marked this conversation as resolved Outdated

remove_ssh_key

`remove_ssh_key`
"""Remove ssh key from user"""
success, message, code = remove_ssh_key(ssh_input.username, ssh_input.ssh_key)
return UserMutationReturn(
inex marked this conversation as resolved Outdated

Возврат этого ненастоящего пользователя не имеет смысла. Мог быть запрошен другой пользователь.

Возврат этого ненастоящего пользователя не имеет смысла. Мог быть запрошен другой пользователь.
success=success,
message=message,
code=code,
user=get_user_by_username(ssh_input.username),
)

View File

@ -0,0 +1,74 @@
from selfprivacy_api.graphql.common_types.user import ensure_ssh_and_users_fields_exist
from selfprivacy_api.utils import (
WriteUserData,
validate_ssh_public_key,
)
def create_ssh_key(username: str, ssh_key: str) -> tuple[bool, str, int]:
inex marked this conversation as resolved Outdated

Как насчёт указывать тип аргументов и возвращаемые значения? Относится ко всем функциям в файле.

Как насчёт указывать тип аргументов и возвращаемые значения? Относится ко всем функциям в файле.
"""Create a new ssh key"""
if not validate_ssh_public_key(ssh_key):
return (
False,
"Invalid key type. Only ssh-ed25519 and ssh-rsa are supported",
400,
)
with WriteUserData() as data:
ensure_ssh_and_users_fields_exist(data)
if username == data["username"]:
if ssh_key in data["sshKeys"]:
return False, "Key already exists", 409
data["sshKeys"].append(ssh_key)
return True, "New SSH key successfully written", 201
if username == "root":
if ssh_key in data["ssh"]["rootKeys"]:
return False, "Key already exists", 409
data["ssh"]["rootKeys"].append(ssh_key)
return True, "New SSH key successfully written", 201
for user in data["users"]:
if user["username"] == username:
if ssh_key in user["sshKeys"]:
return False, "Key already exists", 409
user["sshKeys"].append(ssh_key)
return True, "New SSH key successfully written", 201
return False, "User not found", 404
def remove_ssh_key(username: str, ssh_key: str) -> tuple[bool, str, int]:
"""Delete a ssh key"""
with WriteUserData() as data:
ensure_ssh_and_users_fields_exist(data)
if username == "root":
if ssh_key in data["ssh"]["rootKeys"]:
data["ssh"]["rootKeys"].remove(ssh_key)
return True, "SSH key deleted", 200
return False, "Key not found", 404
if username == data["username"]:
if ssh_key in data["sshKeys"]:
data["sshKeys"].remove(ssh_key)
return True, "SSH key deleted", 200
return False, "Key not found", 404
for user in data["users"]:
if user["username"] == username:
if ssh_key in user["sshKeys"]:
user["sshKeys"].remove(ssh_key)
return True, "SSH key deleted", 200
return False, "Key not found", 404
return False, "User not found", 404

View File

@ -7,7 +7,7 @@ from selfprivacy_api.graphql import IsAuthenticated
from selfprivacy_api.graphql.mutations.mutation_interface import (
MutationReturnInterface,
)
from selfprivacy_api.utils import ReadUserData, WriteUserData
from selfprivacy_api.utils import WriteUserData
@strawberry.type

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Users management module"""
# pylint: disable=too-few-public-methods
import strawberry
from selfprivacy_api.graphql import IsAuthenticated
from selfprivacy_api.graphql.common_types.user import (
UserMutationReturn,
get_user_by_username,
)
from selfprivacy_api.graphql.mutations.mutation_interface import (
GenericMutationReturn,
)
from selfprivacy_api.graphql.mutations.users_utils import (
create_user,
delete_user,
update_user,
)
@strawberry.input
class UserMutationInput:
"""Input type for user mutation"""
username: str
password: str
@strawberry.type
class UserMutations:
inex marked this conversation as resolved Outdated

settingsuser

`settings` → `user`
"""Mutations change user settings"""
inex marked this conversation as resolved Outdated

Стоит вынести код для получения хэша в другое место, и вызывать эту функцию и отсюда, и из REST кода.

Папка utils может для этого подойти.

Стоит вынести код для получения хэша в другое место, и вызывать эту функцию и отсюда, и из REST кода. Папка utils может для этого подойти.
@strawberry.mutation(permission_classes=[IsAuthenticated])
def create_user(self, user: UserMutationInput) -> UserMutationReturn:
success, message, code = create_user(user.username, user.password)
return UserMutationReturn(
success=success,
message=message,
code=code,
user=get_user_by_username(user.username),
)
@strawberry.mutation(permission_classes=[IsAuthenticated])
def delete_user(self, username: str) -> GenericMutationReturn:
success, message, code = delete_user(username)
inex marked this conversation as resolved Outdated

Error: излишне, мы и так знаем что случилась ошибка.

`Error: ` излишне, мы и так знаем что случилась ошибка.
return GenericMutationReturn(
success=success,
message=message,
code=code,
)
@strawberry.mutation(permission_classes=[IsAuthenticated])
def update_user(self, user: UserMutationInput) -> UserMutationReturn:
"""Update user mutation"""
success, message, code = update_user(user.username, user.password)
return UserMutationReturn(
success=success,
message=message,
code=code,
user=get_user_by_username(user.username),
)

View File

@ -0,0 +1,111 @@
import re
from selfprivacy_api.utils import (
WriteUserData,
ReadUserData,
is_username_forbidden,
)
from selfprivacy_api.utils import hash_password
def ensure_ssh_and_users_fields_exist(data):
if "ssh" not in data:
inex marked this conversation as resolved Outdated

Суффикс util лишён смысла. Касается всех функций в файле.

Не хватает типов аргументов и возвращаемых значений

Суффикс `util` лишён смысла. Касается всех функций в файле. Не хватает типов аргументов и возвращаемых значений
data["ssh"] = []
data["ssh"]["rootKeys"] = []
elif data["ssh"].get("rootKeys") is None:
data["ssh"]["rootKeys"] = []
if "sshKeys" not in data:
data["sshKeys"] = []
if "users" not in data:
data["users"] = []
def create_user(username: str, password: str) -> tuple[bool, str, int]:
"""Create a new user"""
# Check if password is null or none
if password == "":
return False, "Password is null", 400
# Check if username is forbidden
if is_username_forbidden(username):
return False, "Username is forbidden", 409
# Check is username passes regex
if not re.match(r"^[a-z_][a-z0-9_]+$", username):
return False, "Username must be alphanumeric", 400
# Check if username less than 32 characters
if len(username) >= 32:
return False, "Username must be less than 32 characters", 400
with ReadUserData() as data:
ensure_ssh_and_users_fields_exist(data)
# Return 409 if user already exists
if data["username"] == username:
return False, "User already exists", 409
for data_user in data["users"]:
if data_user["username"] == username:
return False, "User already exists", 409
hashed_password = hash_password(password)
with WriteUserData() as data:
ensure_ssh_and_users_fields_exist(data)
data["users"].append(
{
"username": username,
"hashedPassword": hashed_password,
"sshKeys": [],
}
)
return True, "User was successfully created!", 201
def delete_user(username: str) -> tuple[bool, str, int]:
with WriteUserData() as data:
ensure_ssh_and_users_fields_exist(data)
if username == data["username"] or username == "root":
return False, "Cannot delete main or root user", 400
# Return 404 if user does not exist
for data_user in data["users"]:
if data_user["username"] == username:
data["users"].remove(data_user)
break
else:
return False, "User does not exist", 404
return True, "User was deleted", 200
def update_user(username: str, password: str) -> tuple[bool, str, int]:
# Check if password is null or none
if password == "":
return False, "Password is null", 400
hashed_password = hash_password(password)
with WriteUserData() as data:
ensure_ssh_and_users_fields_exist(data)
if username == data["username"]:
data["hashedMasterPassword"] = hashed_password
# Return 404 if user does not exist
else:
for data_user in data["users"]:
if data_user["username"] == username:
data_user["hashedPassword"] = hashed_password
break
else:
return False, "User does not exist", 404
return True, "User was successfully updated", 200

View File

@ -0,0 +1,38 @@
"""Users"""
# pylint: disable=too-few-public-methods
import typing
import strawberry
from selfprivacy_api.graphql.common_types.user import (
User,
ensure_ssh_and_users_fields_exist,
get_user_by_username,
)
from selfprivacy_api.utils import ReadUserData
inex marked this conversation as resolved Outdated

В список не попадает основной пользователь. Но ты мне напомнил что приложению нужно знать тип пользователя.

Давай определим enum UserType у которого могут быть следующие значения:

  • NORMAL для обычных пользователей
  • PRIMARY для основного пользователя, который указывается при создании сервера.
  • ROOT для рута.

Добавляем в тип User поле type с типом UserType.

Внёс эту правку также в документ в Figma.

В список не попадает основной пользователь. Но ты мне напомнил что приложению нужно знать тип пользователя. Давай определим enum `UserType` у которого могут быть следующие значения: - `NORMAL` для обычных пользователей - `PRIMARY` для основного пользователя, который указывается при создании сервера. - `ROOT` для рута. Добавляем в тип `User` поле `type` с типом `UserType`. Внёс эту правку также в документ в Figma.
from selfprivacy_api.graphql import IsAuthenticated
def get_users() -> typing.List[User]:
"""Get users"""
user_list = []
inex marked this conversation as resolved Outdated

Может случиться ошибка если поля data["sshKeys"] нет. Обрати внимание на проверки, что были в REST версии.

501c4ce43c/selfprivacy_api/resources/services/ssh.py (L204)

Может случиться ошибка если поля `data["sshKeys"]` нет. Обрати внимание на проверки, что были в REST версии. https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api/src/commit/501c4ce43c4e61b1daa0d541cb535ff7453cf29b/selfprivacy_api/resources/services/ssh.py#L204
with ReadUserData() as data:
ensure_ssh_and_users_fields_exist(data)
for user in data["users"]:
user_list.append(get_user_by_username(user["username"]))
user_list.append(get_user_by_username(data["username"]))
return user_list
@strawberry.type
class Users:
@strawberry.field(permission_classes=[IsAuthenticated])
def get_user(self, username: str) -> typing.Optional[User]:
"""Get users"""
return get_user_by_username(username)
all_users: typing.List[User] = strawberry.field(
permission_classes=[IsAuthenticated], resolver=get_users
)

View File

@ -1,9 +1,10 @@
"""GraphQL API for SelfPrivacy."""
# pylint: disable=too-few-public-methods
import typing
import strawberry
from selfprivacy_api.graphql import IsAuthenticated
from selfprivacy_api.graphql.mutations.api_mutations import ApiMutations
from selfprivacy_api.graphql.mutations.ssh_mutations import SshMutations
from selfprivacy_api.graphql.mutations.storage_mutation import StorageMutations
from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations
@ -11,6 +12,9 @@ from selfprivacy_api.graphql.queries.api_queries import Api
from selfprivacy_api.graphql.queries.storage import Storage
from selfprivacy_api.graphql.queries.system import System
from selfprivacy_api.graphql.mutations.users_mutations import UserMutations
from selfprivacy_api.graphql.queries.users import Users
@strawberry.type
class Query:
@ -26,6 +30,11 @@ class Query:
"""API access status"""
return Api()
@strawberry.field(permission_classes=[IsAuthenticated])
def users(self) -> Users:
"""Users queries"""
return Users()
@strawberry.field(permission_classes=[IsAuthenticated])
def storage(self) -> Storage:
"""Storage queries"""
@ -33,7 +42,13 @@ class Query:
@strawberry.type
class Mutation(ApiMutations, SystemMutations, StorageMutations):
class Mutation(
ApiMutations,
SystemMutations,
UserMutations,
SshMutations,
StorageMutations,
):
"""Root schema for mutations"""
pass

View File

@ -159,3 +159,17 @@ def get_dkim_key(domain):
dkim = cat_process.communicate()[0]
return str(dkim, "utf-8")
return None
def hash_password(password):
hashing_command = ["mkpasswd", "-m", "sha-512", password]
password_hash_process_descriptor = subprocess.Popen(
hashing_command,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
hashed_password = password_hash_process_descriptor.communicate()[0]
hashed_password = hashed_password.decode("ascii")
hashed_password = hashed_password.rstrip()
return hashed_password

View File

@ -20,5 +20,9 @@ def generate_system_query(query_array):
return "query TestSystem {\n system {" + "\n".join(query_array) + "}\n}"
def generate_users_query(query_array):
return "query TestUsers {\n users {" + "\n".join(query_array) + "}\n}"
def mnemonic_to_hex(mnemonic):
return Mnemonic(language="english").to_entropy(mnemonic).hex()

View File

@ -1,12 +1,10 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=missing-function-docstring
import json
import os
import pytest
import datetime
from tests.common import generate_system_query, read_json, write_json
from tests.common import generate_system_query, read_json
@pytest.fixture
@ -56,7 +54,7 @@ class ProcessMock:
self.args = args
self.kwargs = kwargs
def communicate():
def communicate(): # pylint: disable=no-method-argument
return (b"", None)
returncode = 0
@ -65,7 +63,7 @@ class ProcessMock:
class BrokenServiceMock(ProcessMock):
"""Mock subprocess.Popen for broken service"""
def communicate():
def communicate(): # pylint: disable=no-method-argument
return (b"Testing error", None)
returncode = 3

View File

@ -1,7 +1,6 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=missing-function-docstring
import pytest
from tests.common import generate_api_query
from tests.test_graphql.test_api_devices import API_DEVICES_QUERY

View File

@ -1,9 +1,6 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=missing-function-docstring
import json
from time import strftime
import pytest
import datetime
from tests.common import generate_api_query, mnemonic_to_hex, read_json, write_json

View File

@ -0,0 +1,353 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
import pytest
from tests.common import read_json
class ProcessMock:
"""Mock subprocess.Popen"""
def __init__(self, args, **kwargs):
self.args = args
self.kwargs = kwargs
def communicate(): # pylint: disable=no-method-argument
return (b"NEW_HASHED", None)
returncode = 0
@pytest.fixture
def mock_subprocess_popen(mocker):
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock)
return mock
@pytest.fixture
def some_users(mocker, datadir):
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "some_users.json")
assert read_json(datadir / "some_users.json")["users"] == [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": ["ssh-rsa KEY user1@pc"],
},
{"username": "user2", "hashedPassword": "HASHED_PASSWORD_2", "sshKeys": []},
{"username": "user3", "hashedPassword": "HASHED_PASSWORD_3"},
]
return datadir
# TESTS ########################################################
API_CREATE_SSH_KEY_MUTATION = """
mutation addSshKey($sshInput: SshMutationInput!) {
addSshKey(sshInput: $sshInput) {
success
message
inex marked this conversation as resolved Outdated

Нужно будет поправить

Нужно будет поправить
code
user {
username
sshKeys
}
}
}
"""
def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen):
response = client.post(
"/graphql",
inex marked this conversation as resolved
Review

test_graphql_add_ssh_key_unauthorized

Аналогично ко всему остальному.

test_graphql_add_ssh_**key**_unauthorized Аналогично ко всему остальному.
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_add_ssh_key(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["addSshKey"]["code"] == 201
assert response.json["data"]["addSshKey"]["message"] is not None
assert response.json["data"]["addSshKey"]["success"] is True
assert response.json["data"]["addSshKey"]["user"]["username"] == "user1"
assert response.json["data"]["addSshKey"]["user"]["sshKeys"] == [
"ssh-rsa KEY user1@pc",
"ssh-rsa KEY test_key@pc",
]
def test_graphql_add_root_ssh_key(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "root",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["addSshKey"]["code"] == 201
assert response.json["data"]["addSshKey"]["message"] is not None
assert response.json["data"]["addSshKey"]["success"] is True
assert response.json["data"]["addSshKey"]["user"]["username"] == "root"
assert response.json["data"]["addSshKey"]["user"]["sshKeys"] == [
"ssh-ed25519 KEY test@pc",
"ssh-rsa KEY test_key@pc",
]
def test_graphql_add_main_ssh_key(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "tester",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["addSshKey"]["code"] == 201
assert response.json["data"]["addSshKey"]["message"] is not None
assert response.json["data"]["addSshKey"]["success"] is True
assert response.json["data"]["addSshKey"]["user"]["username"] == "tester"
assert response.json["data"]["addSshKey"]["user"]["sshKeys"] == [
"ssh-rsa KEY test@pc",
"ssh-rsa KEY test_key@pc",
]
def test_graphql_add_bad_ssh_key(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "trust me, this is the ssh key",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["addSshKey"]["code"] == 400
assert response.json["data"]["addSshKey"]["message"] is not None
assert response.json["data"]["addSshKey"]["success"] is False
def test_graphql_add_ssh_key_nonexistent_user(
authorized_client, some_users, mock_subprocess_popen
):
inex marked this conversation as resolved Outdated

Что такое 404user?
test_graphql_add_ssh_key_nonexistent_user

Что такое `404user`? `test_graphql_add_ssh_key_nonexistent_user`
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user666",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["addSshKey"]["code"] == 404
assert response.json["data"]["addSshKey"]["message"] is not None
assert response.json["data"]["addSshKey"]["success"] is False
API_REMOVE_SSH_KEY_MUTATION = """
mutation removeSshKey($sshInput: SshMutationInput!) {
inex marked this conversation as resolved Outdated

Удаляем ключ, а не SSH

Удаляем ключ, а не SSH
removeSshKey(sshInput: $sshInput) {
success
message
code
user {
username
sshKeys
}
}
}
"""
def test_graphql_remove_ssh_key_unauthorized(client, some_users, mock_subprocess_popen):
inex marked this conversation as resolved Outdated

Причём тут Dell?

Причём тут Dell?
response = client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_remove_ssh_key(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "ssh-rsa KEY user1@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["removeSshKey"]["code"] == 200
assert response.json["data"]["removeSshKey"]["message"] is not None
assert response.json["data"]["removeSshKey"]["success"] is True
assert response.json["data"]["removeSshKey"]["user"]["username"] == "user1"
assert response.json["data"]["removeSshKey"]["user"]["sshKeys"] == []
def test_graphql_remove_root_ssh_key(
authorized_client, some_users, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "root",
"sshKey": "ssh-ed25519 KEY test@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["removeSshKey"]["code"] == 200
assert response.json["data"]["removeSshKey"]["message"] is not None
assert response.json["data"]["removeSshKey"]["success"] is True
assert response.json["data"]["removeSshKey"]["user"]["username"] == "root"
assert response.json["data"]["removeSshKey"]["user"]["sshKeys"] == []
def test_graphql_remove_main_ssh_key(
authorized_client, some_users, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "tester",
"sshKey": "ssh-rsa KEY test@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["removeSshKey"]["code"] == 200
assert response.json["data"]["removeSshKey"]["message"] is not None
assert response.json["data"]["removeSshKey"]["success"] is True
assert response.json["data"]["removeSshKey"]["user"]["username"] == "tester"
assert response.json["data"]["removeSshKey"]["user"]["sshKeys"] == []
def test_graphql_remove_nonexistent_ssh_key(
authorized_client, some_users, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user1",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["removeSshKey"]["code"] == 404
assert response.json["data"]["removeSshKey"]["message"] is not None
assert response.json["data"]["removeSshKey"]["success"] is False
def test_graphql_remove_ssh_key_nonexistent_user(
authorized_client, some_users, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_REMOVE_SSH_KEY_MUTATION,
"variables": {
"sshInput": {
"username": "user666",
"sshKey": "ssh-rsa KEY test_key@pc",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["removeSshKey"]["code"] == 404
assert response.json["data"]["removeSshKey"]["message"] is not None
assert response.json["data"]["removeSshKey"]["success"] is False

View File

@ -0,0 +1,71 @@
{
"backblaze": {
"accountId": "ID",
"accountKey": "KEY",
"bucket": "selfprivacy"
},
"api": {
"token": "TEST_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": false
},
"cloudflare": {
"apiKey": "TOKEN"
},
"databasePassword": "PASSWORD",
"domain": "test.tld",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "test-instance",
"nextcloud": {
"adminPassword": "ADMIN",
"databasePassword": "ADMIN",
"enable": true
},
"resticPassword": "PASS",
"ssh": {
"enable": true,
"passwordAuthentication": true,
"rootKeys": [
"ssh-ed25519 KEY test@pc"
]
},
"username": "tester",
"gitea": {
"enable": false
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"autoUpgrade": {
"enable": true,
"allowReboot": true
},
"timezone": "Europe/Moscow",
"sshKeys": [
"ssh-rsa KEY test@pc"
],
"users": [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": [
"ssh-rsa KEY user1@pc"
]
},
{
"username": "user2",
"hashedPassword": "HASHED_PASSWORD_2",
"sshKeys": [
]
},
{
"username": "user3",
"hashedPassword": "HASHED_PASSWORD_3"
}
]
}

View File

@ -0,0 +1,733 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
import pytest
from tests.common import (
generate_users_query,
read_json,
)
invalid_usernames = [
"messagebus",
"postfix",
"polkituser",
"dovecot2",
"dovenull",
"nginx",
"postgres",
"systemd-journal-gateway",
"prosody",
"systemd-network",
"systemd-resolve",
"systemd-timesync",
"opendkim",
"rspamd",
"sshd",
"selfprivacy-api",
"restic",
"redis",
"pleroma",
"ocserv",
"nextcloud",
"memcached",
"knot-resolver",
"gitea",
"bitwarden_rs",
"vaultwarden",
"acme",
"virtualMail",
"nixbld1",
"nixbld2",
"nixbld29",
"nobody",
]
## FIXTURES ###################################################
@pytest.fixture
def no_users(mocker, datadir):
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "no_users.json")
assert read_json(datadir / "no_users.json")["users"] == []
return datadir
@pytest.fixture
def one_user(mocker, datadir):
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "one_user.json")
assert read_json(datadir / "one_user.json")["users"] == [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": ["ssh-rsa KEY user1@pc"],
}
]
return datadir
@pytest.fixture
def some_users(mocker, datadir):
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "some_users.json")
assert read_json(datadir / "some_users.json")["users"] == [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": ["ssh-rsa KEY user1@pc"],
},
{"username": "user2", "hashedPassword": "HASHED_PASSWORD_2", "sshKeys": []},
{"username": "user3", "hashedPassword": "HASHED_PASSWORD_3"},
]
return datadir
@pytest.fixture
def undefined_settings(mocker, datadir):
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
assert "users" not in read_json(datadir / "undefined.json")
return datadir
class ProcessMock:
"""Mock subprocess.Popen"""
def __init__(self, args, **kwargs):
self.args = args
self.kwargs = kwargs
def communicate(): # pylint: disable=no-method-argument
return (b"NEW_HASHED", None)
returncode = 0
@pytest.fixture
def mock_subprocess_popen(mocker):
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock)
return mock
## TESTS ######################################################
API_USERS_INFO = """
allUsers {
username
sshKeys
}
"""
def test_graphql_get_users_unauthorized(client, some_users, mock_subprocess_popen):
"""Test wrong auth"""
response = client.get(
"/graphql",
json={
"query": generate_users_query([API_USERS_INFO]),
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_get_some_users(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": generate_users_query([API_USERS_INFO]),
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["allUsers"]) == 4
assert response.json["data"]["users"]["allUsers"][0]["username"] == "user1"
assert response.json["data"]["users"]["allUsers"][0]["sshKeys"] == [
"ssh-rsa KEY user1@pc"
]
assert response.json["data"]["users"]["allUsers"][1]["username"] == "user2"
assert response.json["data"]["users"]["allUsers"][1]["sshKeys"] == []
assert response.json["data"]["users"]["allUsers"][3]["username"] == "tester"
assert response.json["data"]["users"]["allUsers"][3]["sshKeys"] == [
"ssh-rsa KEY test@pc"
]
def test_graphql_get_no_users(authorized_client, no_users, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": generate_users_query([API_USERS_INFO]),
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["allUsers"]) == 1
assert response.json["data"]["users"]["allUsers"][0]["username"] == "tester"
assert response.json["data"]["users"]["allUsers"][0]["sshKeys"] == [
"ssh-rsa KEY test@pc"
]
API_GET_USERS = """
query TestUsers($username: String!) {
users {
getUser(username: $username) {
sshKeys
username
}
}
}
"""
def test_graphql_get_one_user_unauthorized(client, one_user, mock_subprocess_popen):
response = client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "user1",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_get_one_user(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "user1",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["getUser"]) == 2
assert response.json["data"]["users"]["getUser"]["username"] == "user1"
assert response.json["data"]["users"]["getUser"]["sshKeys"] == [
"ssh-rsa KEY user1@pc"
]
def test_graphql_get_some_user(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "user2",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["getUser"]) == 2
assert response.json["data"]["users"]["getUser"]["username"] == "user2"
assert response.json["data"]["users"]["getUser"]["sshKeys"] == []
def test_graphql_get_root_user(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "root",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["getUser"]) == 2
assert response.json["data"]["users"]["getUser"]["username"] == "root"
assert response.json["data"]["users"]["getUser"]["sshKeys"] == [
"ssh-ed25519 KEY test@pc"
]
def test_graphql_get_main_user(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "tester",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert len(response.json["data"]["users"]["getUser"]) == 2
assert response.json["data"]["users"]["getUser"]["username"] == "tester"
assert response.json["data"]["users"]["getUser"]["sshKeys"] == [
"ssh-rsa KEY test@pc"
]
def test_graphql_get_nonexistent_user(
authorized_client, one_user, mock_subprocess_popen
):
response = authorized_client.get(
"/graphql",
json={
"query": API_GET_USERS,
"variables": {
"username": "tyler_durden",
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["users"]["getUser"] is None
inex marked this conversation as resolved Outdated

Так меняем или создаём?

Так меняем или создаём?
API_CREATE_USERS_MUTATION = """
mutation createUser($user: UserMutationInput!) {
createUser(user: $user) {
success
message
code
user {
username
sshKeys
}
}
}
"""
def test_graphql_add_user_unauthorize(client, one_user, mock_subprocess_popen):
response = client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "user2",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_add_user(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "user2",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 201
assert response.json["data"]["createUser"]["success"] is True
assert response.json["data"]["createUser"]["user"]["username"] == "user2"
assert response.json["data"]["createUser"]["user"]["sshKeys"] == []
def test_graphql_add_undefined_settings(
authorized_client, undefined_settings, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "user2",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 201
assert response.json["data"]["createUser"]["success"] is True
assert response.json["data"]["createUser"]["user"]["username"] == "user2"
assert response.json["data"]["createUser"]["user"]["sshKeys"] == []
def test_graphql_add_without_password(
authorized_client, one_user, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "user2",
"password": "",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 400
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"] is None
def test_graphql_add_without_both(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "",
"password": "",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 400
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"] is None
@pytest.mark.parametrize("username", invalid_usernames)
def test_graphql_add_system_username(
authorized_client, one_user, mock_subprocess_popen, username
):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": username,
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 409
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"] is None
def test_graphql_add_existing_user(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "user1",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 409
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"]["username"] == "user1"
assert (
response.json["data"]["createUser"]["user"]["sshKeys"][0]
== "ssh-rsa KEY user1@pc"
)
def test_graphql_add_main_user(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "tester",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 409
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"]["username"] == "tester"
assert (
response.json["data"]["createUser"]["user"]["sshKeys"][0]
== "ssh-rsa KEY test@pc"
)
def test_graphql_add_long_username(authorized_client, one_user, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": "a" * 32,
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 400
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"] is None
@pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "^-^"])
def test_graphql_add_invalid_username(
authorized_client, one_user, mock_subprocess_popen, username
):
response = authorized_client.post(
"/graphql",
json={
"query": API_CREATE_USERS_MUTATION,
"variables": {
"user": {
"username": username,
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["createUser"]["message"] is not None
assert response.json["data"]["createUser"]["code"] == 400
assert response.json["data"]["createUser"]["success"] is False
assert response.json["data"]["createUser"]["user"] is None
inex marked this conversation as resolved
Review

Удаляем одного, а не нескольких.

Удаляем одного, а не нескольких.
API_DELETE_USER_MUTATION = """
mutation deleteUser($username: String!) {
deleteUser(username: $username) {
success
message
code
}
}
"""
def test_graphql_delete_user_unauthorized(client, some_users, mock_subprocess_popen):
response = client.post(
"/graphql",
json={
"query": API_DELETE_USER_MUTATION,
"variables": {"username": "user1"},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_delete_user(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_DELETE_USER_MUTATION,
"variables": {"username": "user1"},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["deleteUser"]["code"] == 200
assert response.json["data"]["deleteUser"]["message"] is not None
assert response.json["data"]["deleteUser"]["success"] is True
@pytest.mark.parametrize("username", ["", "def"])
def test_graphql_delete_nonexistent_users(
authorized_client, some_users, mock_subprocess_popen, username
):
response = authorized_client.post(
"/graphql",
json={
"query": API_DELETE_USER_MUTATION,
"variables": {"username": username},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["deleteUser"]["code"] == 404
assert response.json["data"]["deleteUser"]["message"] is not None
assert response.json["data"]["deleteUser"]["success"] is False
@pytest.mark.parametrize("username", invalid_usernames)
def test_graphql_delete_system_users(
authorized_client, some_users, mock_subprocess_popen, username
):
response = authorized_client.post(
"/graphql",
json={
"query": API_DELETE_USER_MUTATION,
"variables": {"username": username},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert (
response.json["data"]["deleteUser"]["code"] == 404
or response.json["data"]["deleteUser"]["code"] == 400
)
assert response.json["data"]["deleteUser"]["message"] is not None
assert response.json["data"]["deleteUser"]["success"] is False
def test_graphql_delete_main_user(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_DELETE_USER_MUTATION,
"variables": {"username": "tester"},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["deleteUser"]["code"] == 400
assert response.json["data"]["deleteUser"]["message"] is not None
assert response.json["data"]["deleteUser"]["success"] is False
inex marked this conversation as resolved
Review

Меняем одного, а не нескольких

Меняем одного, а не нескольких
API_UPDATE_USER_MUTATION = """
mutation updateUser($user: UserMutationInput!) {
updateUser(user: $user) {
success
message
code
user {
username
sshKeys
}
}
}
"""
def test_graphql_update_user_unauthorized(client, some_users, mock_subprocess_popen):
response = client.post(
"/graphql",
json={
"query": API_UPDATE_USER_MUTATION,
"variables": {
"user": {
"username": "user1",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is None
def test_graphql_update_user(authorized_client, some_users, mock_subprocess_popen):
response = authorized_client.post(
"/graphql",
json={
"query": API_UPDATE_USER_MUTATION,
"variables": {
"user": {
"username": "user1",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["updateUser"]["code"] == 200
assert response.json["data"]["updateUser"]["message"] is not None
assert response.json["data"]["updateUser"]["success"] is True
assert response.json["data"]["updateUser"]["user"]["username"] == "user1"
assert response.json["data"]["updateUser"]["user"]["sshKeys"] == [
"ssh-rsa KEY user1@pc"
]
assert mock_subprocess_popen.call_count == 1
def test_graphql_update_nonexistent_user(
authorized_client, some_users, mock_subprocess_popen
):
response = authorized_client.post(
"/graphql",
json={
"query": API_UPDATE_USER_MUTATION,
"variables": {
"user": {
"username": "user666",
"password": "12345678",
},
},
},
)
assert response.status_code == 200
assert response.json.get("data") is not None
assert response.json["data"]["updateUser"]["code"] == 404
assert response.json["data"]["updateUser"]["message"] is not None
assert response.json["data"]["updateUser"]["success"] is False
assert response.json["data"]["updateUser"]["user"] is None
assert mock_subprocess_popen.call_count == 1

View File

@ -0,0 +1,54 @@
{
"backblaze": {
"accountId": "ID",
"accountKey": "KEY",
"bucket": "selfprivacy"
},
"api": {
"token": "TEST_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": false
},
"cloudflare": {
"apiKey": "TOKEN"
},
"databasePassword": "PASSWORD",
"domain": "test.tld",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "test-instance",
"nextcloud": {
"adminPassword": "ADMIN",
"databasePassword": "ADMIN",
"enable": true
},
"resticPassword": "PASS",
"ssh": {
"enable": true,
"passwordAuthentication": true,
"rootKeys": [
"ssh-ed25519 KEY test@pc"
]
},
"username": "tester",
"gitea": {
"enable": false
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"autoUpgrade": {
"enable": true,
"allowReboot": true
},
"timezone": "Europe/Moscow",
"sshKeys": [
"ssh-rsa KEY test@pc"
],
"users": [
]
}

View File

@ -0,0 +1,61 @@
{
"backblaze": {
"accountId": "ID",
"accountKey": "KEY",
"bucket": "selfprivacy"
},
"api": {
"token": "TEST_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": false
},
"cloudflare": {
"apiKey": "TOKEN"
},
"databasePassword": "PASSWORD",
"domain": "test.tld",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "test-instance",
"nextcloud": {
"adminPassword": "ADMIN",
"databasePassword": "ADMIN",
"enable": true
},
"resticPassword": "PASS",
"ssh": {
"enable": true,
"passwordAuthentication": true,
"rootKeys": [
"ssh-ed25519 KEY test@pc"
]
},
"username": "tester",
"gitea": {
"enable": false
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"autoUpgrade": {
"enable": true,
"allowReboot": true
},
"timezone": "Europe/Moscow",
"sshKeys": [
"ssh-rsa KEY test@pc"
],
"users": [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": [
"ssh-rsa KEY user1@pc"
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"backblaze": {
"accountId": "ID",
"accountKey": "KEY",
"bucket": "selfprivacy"
},
"api": {
"token": "TEST_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": false
},
"cloudflare": {
"apiKey": "TOKEN"
},
"databasePassword": "PASSWORD",
"domain": "test.tld",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "test-instance",
"nextcloud": {
"adminPassword": "ADMIN",
"databasePassword": "ADMIN",
"enable": true
},
"resticPassword": "PASS",
"ssh": {
"enable": true,
"passwordAuthentication": true,
"rootKeys": [
"ssh-ed25519 KEY test@pc"
]
},
"username": "tester",
"gitea": {
"enable": false
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"autoUpgrade": {
"enable": true,
"allowReboot": true
},
"timezone": "Europe/Moscow",
"sshKeys": [
"ssh-rsa KEY test@pc"
],
"users": [
{
"username": "user1",
"hashedPassword": "HASHED_PASSWORD_1",
"sshKeys": [
"ssh-rsa KEY user1@pc"
]
},
{
"username": "user2",
"hashedPassword": "HASHED_PASSWORD_2",
"sshKeys": [
]
},
{
"username": "user3",
"hashedPassword": "HASHED_PASSWORD_3"
}
]
}

View File

@ -0,0 +1,52 @@
{
"backblaze": {
"accountId": "ID",
"accountKey": "KEY",
"bucket": "selfprivacy"
},
"api": {
"token": "TEST_TOKEN",
"enableSwagger": false
},
"bitwarden": {
"enable": false
},
"cloudflare": {
"apiKey": "TOKEN"
},
"databasePassword": "PASSWORD",
"domain": "test.tld",
"hashedMasterPassword": "HASHED_PASSWORD",
"hostname": "test-instance",
"nextcloud": {
"adminPassword": "ADMIN",
"databasePassword": "ADMIN",
"enable": true
},
"resticPassword": "PASS",
"ssh": {
"enable": true,
"passwordAuthentication": true,
"rootKeys": [
"ssh-ed25519 KEY test@pc"
]
},
"username": "tester",
"gitea": {
"enable": false
},
"ocserv": {
"enable": true
},
"pleroma": {
"enable": true
},
"autoUpgrade": {
"enable": true,
"allowReboot": true
},
"timezone": "Europe/Moscow",
"sshKeys": [
"ssh-rsa KEY test@pc"
]
}