Dismantle REST API #55
|
@ -2,7 +2,7 @@
|
|||
import os
|
||||
import subprocess
|
||||
import pytz
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from selfprivacy_api.utils import WriteUserData, ReadUserData
|
||||
|
@ -58,36 +58,56 @@ def set_auto_upgrade_settings(
|
|||
user_data["autoUpgrade"]["allowReboot"] = allowReboot
|
||||
|
||||
|
||||
class ShellException(Exception):
|
||||
"""Something went wrong when calling another process"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def run_blocking(cmd: List[str], new_session: bool = False) -> str:
|
||||
"""Run a process, block until done, return output, complain if failed"""
|
||||
process_handle = subprocess.Popen(
|
||||
cmd,
|
||||
shell=False,
|
||||
start_new_session=new_session,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
stdout_raw, stderr_raw = process_handle.communicate()
|
||||
stdout = stdout_raw.decode("utf-8")
|
||||
if stderr_raw is not None:
|
||||
stderr = stderr_raw.decode("utf-8")
|
||||
else:
|
||||
stderr = ""
|
||||
output = stdout + "\n" + stderr
|
||||
if process_handle.returncode != 0:
|
||||
raise ShellException(
|
||||
f"Shell command failed, command array: {cmd}, output: {output}"
|
||||
)
|
||||
return stdout
|
||||
|
||||
|
||||
def rebuild_system() -> int:
|
||||
"""Rebuild the system"""
|
||||
rebuild_result = subprocess.Popen(
|
||||
["systemctl", "start", "sp-nixos-rebuild.service"], start_new_session=True
|
||||
)
|
||||
rebuild_result.communicate()[0]
|
||||
return rebuild_result.returncode
|
||||
run_blocking(["systemctl", "start", "sp-nixos-rebuild.service"], new_session=True)
|
||||
return 0
|
||||
|
||||
|
||||
def rollback_system() -> int:
|
||||
"""Rollback the system"""
|
||||
rollback_result = subprocess.Popen(
|
||||
["systemctl", "start", "sp-nixos-rollback.service"], start_new_session=True
|
||||
)
|
||||
rollback_result.communicate()[0]
|
||||
return rollback_result.returncode
|
||||
run_blocking(["systemctl", "start", "sp-nixos-rollback.service"], new_session=True)
|
||||
return 0
|
||||
|
||||
|
||||
def upgrade_system() -> int:
|
||||
"""Upgrade the system"""
|
||||
upgrade_result = subprocess.Popen(
|
||||
["systemctl", "start", "sp-nixos-upgrade.service"], start_new_session=True
|
||||
)
|
||||
upgrade_result.communicate()[0]
|
||||
return upgrade_result.returncode
|
||||
run_blocking(["systemctl", "start", "sp-nixos-upgrade.service"], new_session=True)
|
||||
return 0
|
||||
|
||||
|
||||
def reboot_system() -> None:
|
||||
"""Reboot the system"""
|
||||
subprocess.Popen(["reboot"], start_new_session=True)
|
||||
run_blocking(["reboot"], new_session=True)
|
||||
|
||||
|
||||
def get_system_version() -> str:
|
||||
|
|
|
@ -58,7 +58,7 @@ def get_users(
|
|||
)
|
||||
for user in user_data["users"]
|
||||
]
|
||||
if not exclude_primary:
|
||||
if not exclude_primary and "username" in user_data.keys():
|
||||
users.append(
|
||||
UserDataUser(
|
||||
username=user_data["username"],
|
||||
|
@ -107,6 +107,12 @@ class PasswordIsEmpty(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class InvalidConfiguration(Exception):
|
||||
"""The userdata is broken"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def create_user(username: str, password: str):
|
||||
if password == "":
|
||||
raise PasswordIsEmpty("Password is empty")
|
||||
|
@ -124,6 +130,10 @@ def create_user(username: str, password: str):
|
|||
|
||||
with ReadUserData() as user_data:
|
||||
ensure_ssh_and_users_fields_exist(user_data)
|
||||
if "username" not in user_data.keys():
|
||||
raise InvalidConfiguration(
|
||||
"Broken config: Admin name is not defined. Consider recovery or add it manually"
|
||||
)
|
||||
if username == user_data["username"]:
|
||||
raise UserAlreadyExists("User already exists")
|
||||
if username in [user["username"] for user in user_data["users"]]:
|
||||
|
|
|
@ -10,12 +10,6 @@ from selfprivacy_api.dependencies import get_api_version
|
|||
from selfprivacy_api.graphql.schema import schema
|
||||
from selfprivacy_api.migrations import run_migrations
|
||||
|
||||
from selfprivacy_api.rest import (
|
||||
system,
|
||||
users,
|
||||
api_auth,
|
||||
services,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
@ -32,10 +26,6 @@ app.add_middleware(
|
|||
)
|
||||
|
||||
|
||||
app.include_router(system.router)
|
||||
app.include_router(users.router)
|
||||
app.include_router(api_auth.router)
|
||||
app.include_router(services.router)
|
||||
app.include_router(graphql_app, prefix="/graphql")
|
||||
|
||||
|
||||
|
|
|
@ -197,6 +197,8 @@ class ResticBackupper(AbstractBackupper):
|
|||
output,
|
||||
"parsed messages:",
|
||||
messages,
|
||||
"command: ",
|
||||
backup_command,
|
||||
) from error
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -11,7 +11,9 @@ from selfprivacy_api.graphql.common_types.backup import (
|
|||
from selfprivacy_api.models.backup.snapshot import Snapshot
|
||||
from selfprivacy_api.utils.huey import huey
|
||||
from huey import crontab
|
||||
|
||||
from selfprivacy_api.services.service import Service
|
||||
from selfprivacy_api.services import get_service_by_id
|
||||
from selfprivacy_api.backup import Backups
|
||||
from selfprivacy_api.jobs import Jobs, JobStatus, Job
|
||||
|
||||
|
@ -31,12 +33,13 @@ def validate_datetime(dt: datetime) -> bool:
|
|||
|
||||
# huey tasks need to return something
|
||||
@huey.task()
|
||||
def start_backup(
|
||||
service: Service, reason: BackupReason = BackupReason.EXPLICIT
|
||||
) -> bool:
|
||||
def start_backup(service_id: str, reason: BackupReason = BackupReason.EXPLICIT) -> bool:
|
||||
"""
|
||||
The worker task that starts the backup process.
|
||||
"""
|
||||
service = get_service_by_id(service_id)
|
||||
if service is None:
|
||||
raise ValueError(f"No such service: {service_id}")
|
||||
Backups.back_up(service, reason)
|
||||
return True
|
||||
|
||||
|
|
|
@ -27,4 +27,4 @@ async def get_token_header(
|
|||
|
||||
def get_api_version() -> str:
|
||||
"""Get API version"""
|
||||
return "2.4.3"
|
||||
return "3.0.0"
|
||||
|
|
|
@ -148,7 +148,7 @@ class BackupMutations:
|
|||
)
|
||||
|
||||
job = add_backup_job(service)
|
||||
start_backup(service)
|
||||
start_backup(service_id)
|
||||
|
||||
return GenericJobMutationReturn(
|
||||
success=True,
|
||||
|
|
|
@ -4,6 +4,7 @@ import typing
|
|||
import strawberry
|
||||
from selfprivacy_api.graphql import IsAuthenticated
|
||||
from selfprivacy_api.graphql.common_types.jobs import job_to_api_job
|
||||
from selfprivacy_api.jobs import JobStatus
|
||||
|
||||
from selfprivacy_api.graphql.common_types.service import (
|
||||
Service,
|
||||
|
@ -47,14 +48,22 @@ class ServicesMutations:
|
|||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def enable_service(self, service_id: str) -> ServiceMutationReturn:
|
||||
"""Enable service."""
|
||||
service = get_service_by_id(service_id)
|
||||
if service is None:
|
||||
try:
|
||||
service = get_service_by_id(service_id)
|
||||
if service is None:
|
||||
return ServiceMutationReturn(
|
||||
success=False,
|
||||
message="Service not found.",
|
||||
code=404,
|
||||
)
|
||||
service.enable()
|
||||
except Exception as e:
|
||||
return ServiceMutationReturn(
|
||||
success=False,
|
||||
message="Service not found.",
|
||||
code=404,
|
||||
message=format_error(e),
|
||||
code=400,
|
||||
)
|
||||
service.enable()
|
||||
|
||||
return ServiceMutationReturn(
|
||||
success=True,
|
||||
message="Service enabled.",
|
||||
|
@ -65,14 +74,21 @@ class ServicesMutations:
|
|||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def disable_service(self, service_id: str) -> ServiceMutationReturn:
|
||||
"""Disable service."""
|
||||
service = get_service_by_id(service_id)
|
||||
if service is None:
|
||||
try:
|
||||
service = get_service_by_id(service_id)
|
||||
if service is None:
|
||||
return ServiceMutationReturn(
|
||||
success=False,
|
||||
message="Service not found.",
|
||||
code=404,
|
||||
)
|
||||
service.disable()
|
||||
except Exception as e:
|
||||
return ServiceMutationReturn(
|
||||
success=False,
|
||||
message="Service not found.",
|
||||
code=404,
|
||||
message=format_error(e),
|
||||
code=400,
|
||||
)
|
||||
service.disable()
|
||||
return ServiceMutationReturn(
|
||||
success=True,
|
||||
message="Service disabled.",
|
||||
|
@ -144,6 +160,8 @@ class ServicesMutations:
|
|||
message="Service not found.",
|
||||
code=404,
|
||||
)
|
||||
# TODO: make serviceImmovable and BlockdeviceNotFound exceptions
|
||||
# in the move_to_volume() function and handle them here
|
||||
if not service.is_movable():
|
||||
return ServiceJobMutationReturn(
|
||||
success=False,
|
||||
|
@ -160,10 +178,31 @@ class ServicesMutations:
|
|||
service=service_to_graphql_service(service),
|
||||
)
|
||||
job = service.move_to_volume(volume)
|
||||
return ServiceJobMutationReturn(
|
||||
success=True,
|
||||
message="Service moved.",
|
||||
code=200,
|
||||
service=service_to_graphql_service(service),
|
||||
job=job_to_api_job(job),
|
||||
)
|
||||
if job.status in [JobStatus.CREATED, JobStatus.RUNNING]:
|
||||
return ServiceJobMutationReturn(
|
||||
success=True,
|
||||
message="Started moving the service.",
|
||||
code=200,
|
||||
service=service_to_graphql_service(service),
|
||||
job=job_to_api_job(job),
|
||||
)
|
||||
elif job.status == JobStatus.FINISHED:
|
||||
return ServiceJobMutationReturn(
|
||||
success=True,
|
||||
message="Service moved.",
|
||||
code=200,
|
||||
service=service_to_graphql_service(service),
|
||||
job=job_to_api_job(job),
|
||||
)
|
||||
else:
|
||||
return ServiceJobMutationReturn(
|
||||
success=False,
|
||||
message=f"Service move failure: {job.status_text}",
|
||||
code=400,
|
||||
service=service_to_graphql_service(service),
|
||||
job=job_to_api_job(job),
|
||||
)
|
||||
|
||||
|
||||
def format_error(e: Exception) -> str:
|
||||
return type(e).__name__ + ": " + str(e)
|
||||
|
|
|
@ -9,6 +9,7 @@ from selfprivacy_api.graphql.mutations.mutation_interface import (
|
|||
)
|
||||
|
||||
import selfprivacy_api.actions.system as system_actions
|
||||
import selfprivacy_api.actions.ssh as ssh_actions
|
||||
|
||||
|
||||
@strawberry.type
|
||||
|
@ -26,6 +27,22 @@ class AutoUpgradeSettingsMutationReturn(MutationReturnInterface):
|
|||
allowReboot: bool
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class SSHSettingsMutationReturn(MutationReturnInterface):
|
||||
"""A return type for after changing SSH settings"""
|
||||
|
||||
enable: bool
|
||||
password_authentication: bool
|
||||
|
||||
|
||||
@strawberry.input
|
||||
class SSHSettingsInput:
|
||||
"""Input type for SSH settings"""
|
||||
|
||||
enable: bool
|
||||
password_authentication: bool
|
||||
|
||||
|
||||
@strawberry.input
|
||||
class AutoUpgradeSettingsInput:
|
||||
"""Input type for auto upgrade settings"""
|
||||
|
@ -77,40 +94,88 @@ class SystemMutations:
|
|||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def run_system_rebuild(self) -> GenericMutationReturn:
|
||||
system_actions.rebuild_system()
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
def change_ssh_settings(
|
||||
self, settings: SSHSettingsInput
|
||||
) -> SSHSettingsMutationReturn:
|
||||
"""Change ssh settings of the server."""
|
||||
ssh_actions.set_ssh_settings(
|
||||
enable=settings.enable,
|
||||
password_authentication=settings.password_authentication,
|
||||
)
|
||||
|
||||
new_settings = ssh_actions.get_ssh_settings()
|
||||
|
||||
return SSHSettingsMutationReturn(
|
||||
success=True,
|
||||
message="SSH settings changed",
|
||||
code=200,
|
||||
enable=new_settings.enable,
|
||||
password_authentication=new_settings.passwordAuthentication,
|
||||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def run_system_rebuild(self) -> GenericMutationReturn:
|
||||
try:
|
||||
system_actions.rebuild_system()
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
)
|
||||
except system_actions.ShellException as e:
|
||||
return GenericMutationReturn(
|
||||
success=False,
|
||||
message=str(e),
|
||||
code=500,
|
||||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def run_system_rollback(self) -> GenericMutationReturn:
|
||||
system_actions.rollback_system()
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
)
|
||||
try:
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
)
|
||||
except system_actions.ShellException as e:
|
||||
return GenericMutationReturn(
|
||||
success=False,
|
||||
message=str(e),
|
||||
code=500,
|
||||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def run_system_upgrade(self) -> GenericMutationReturn:
|
||||
system_actions.upgrade_system()
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
)
|
||||
try:
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="Starting rebuild system",
|
||||
code=200,
|
||||
)
|
||||
except system_actions.ShellException as e:
|
||||
return GenericMutationReturn(
|
||||
success=False,
|
||||
message=str(e),
|
||||
code=500,
|
||||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def reboot_system(self) -> GenericMutationReturn:
|
||||
system_actions.reboot_system()
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="System reboot has started",
|
||||
code=200,
|
||||
)
|
||||
try:
|
||||
return GenericMutationReturn(
|
||||
success=True,
|
||||
message="System reboot has started",
|
||||
code=200,
|
||||
)
|
||||
except system_actions.ShellException as e:
|
||||
return GenericMutationReturn(
|
||||
success=False,
|
||||
message=str(e),
|
||||
code=500,
|
||||
)
|
||||
|
||||
@strawberry.mutation(permission_classes=[IsAuthenticated])
|
||||
def pull_repository_changes(self) -> GenericMutationReturn:
|
||||
|
|
|
@ -69,6 +69,12 @@ class UsersMutations:
|
|||
message=str(e),
|
||||
code=400,
|
||||
)
|
||||
except users_actions.InvalidConfiguration as e:
|
||||
return UserMutationReturn(
|
||||
success=False,
|
||||
message=str(e),
|
||||
code=400,
|
||||
)
|
||||
except users_actions.UserAlreadyExists as e:
|
||||
return UserMutationReturn(
|
||||
success=False,
|
||||
|
|
|
@ -19,6 +19,7 @@ from selfprivacy_api.migrations.migrate_to_selfprivacy_channel import (
|
|||
)
|
||||
from selfprivacy_api.migrations.mount_volume import MountVolume
|
||||
from selfprivacy_api.migrations.providers import CreateProviderFields
|
||||
from selfprivacy_api.migrations.modules_in_json import CreateModulesField
|
||||
from selfprivacy_api.migrations.prepare_for_nixos_2211 import (
|
||||
MigrateToSelfprivacyChannelFrom2205,
|
||||
)
|
||||
|
@ -37,6 +38,7 @@ migrations = [
|
|||
MigrateToSelfprivacyChannelFrom2205(),
|
||||
MigrateToSelfprivacyChannelFrom2211(),
|
||||
LoadTokensToRedis(),
|
||||
CreateModulesField(),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
from selfprivacy_api.migrations.migration import Migration
|
||||
from selfprivacy_api.utils import ReadUserData, WriteUserData
|
||||
from selfprivacy_api.services import get_all_services
|
||||
|
||||
|
||||
def migrate_services_to_modules():
|
||||
with WriteUserData() as userdata:
|
||||
if "modules" not in userdata.keys():
|
||||
userdata["modules"] = {}
|
||||
|
||||
for service in get_all_services():
|
||||
name = service.get_id()
|
||||
if name in userdata.keys():
|
||||
field_content = userdata[name]
|
||||
userdata["modules"][name] = field_content
|
||||
del userdata[name]
|
||||
|
||||
|
||||
# If you ever want to get rid of modules field you will need to get rid of this migration
|
||||
class CreateModulesField(Migration):
|
||||
"""introduce 'modules' (services) into userdata"""
|
||||
|
||||
def get_migration_name(self):
|
||||
return "modules_in_json"
|
||||
|
||||
def get_migration_description(self):
|
||||
return "Group service settings into a 'modules' field in userdata.json"
|
||||
|
||||
def is_migration_needed(self) -> bool:
|
||||
try:
|
||||
with ReadUserData() as userdata:
|
||||
for service in get_all_services():
|
||||
if service.get_id() in userdata.keys():
|
||||
return True
|
||||
|
||||
if "modules" not in userdata.keys():
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def migrate(self):
|
||||
# Write info about providers to userdata.json
|
||||
try:
|
||||
migrate_services_to_modules()
|
||||
print("Done")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Error migrating service fields")
|
|
@ -1,125 +0,0 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from selfprivacy_api.actions.api_tokens import (
|
||||
CannotDeleteCallerException,
|
||||
InvalidExpirationDate,
|
||||
InvalidUsesLeft,
|
||||
NotFoundException,
|
||||
delete_api_token,
|
||||
refresh_api_token,
|
||||
get_api_recovery_token_status,
|
||||
get_api_tokens_with_caller_flag,
|
||||
get_new_api_recovery_key,
|
||||
use_mnemonic_recovery_token,
|
||||
delete_new_device_auth_token,
|
||||
get_new_device_auth_token,
|
||||
use_new_device_auth_token,
|
||||
)
|
||||
|
||||
from selfprivacy_api.dependencies import TokenHeader, get_token_header
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/auth",
|
||||
tags=["auth"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tokens")
|
||||
async def rest_get_tokens(auth_token: TokenHeader = Depends(get_token_header)):
|
||||
"""Get the tokens info"""
|
||||
return get_api_tokens_with_caller_flag(auth_token.token)
|
||||
|
||||
|
||||
class DeleteTokenInput(BaseModel):
|
||||
"""Delete token input"""
|
||||
|
||||
token_name: str
|
||||
|
||||
|
||||
@router.delete("/tokens")
|
||||
async def rest_delete_tokens(
|
||||
token: DeleteTokenInput, auth_token: TokenHeader = Depends(get_token_header)
|
||||
):
|
||||
"""Delete the tokens"""
|
||||
try:
|
||||
delete_api_token(auth_token.token, token.token_name)
|
||||
except NotFoundException:
|
||||
raise HTTPException(status_code=404, detail="Token not found")
|
||||
except CannotDeleteCallerException:
|
||||
raise HTTPException(status_code=400, detail="Cannot delete caller's token")
|
||||
return {"message": "Token deleted"}
|
||||
|
||||
|
||||
@router.post("/tokens")
|
||||
async def rest_refresh_token(auth_token: TokenHeader = Depends(get_token_header)):
|
||||
"""Refresh the token"""
|
||||
try:
|
||||
new_token = refresh_api_token(auth_token.token)
|
||||
except NotFoundException:
|
||||
raise HTTPException(status_code=404, detail="Token not found")
|
||||
return {"token": new_token}
|
||||
|
||||
|
||||
@router.get("/recovery_token")
|
||||
async def rest_get_recovery_token_status(
|
||||
auth_token: TokenHeader = Depends(get_token_header),
|
||||
):
|
||||
return get_api_recovery_token_status()
|
||||
|
||||
|
||||
class CreateRecoveryTokenInput(BaseModel):
|
||||
expiration: Optional[datetime] = None
|
||||
uses: Optional[int] = None
|
||||
|
||||
|
||||
@router.post("/recovery_token")
|
||||
async def rest_create_recovery_token(
|
||||
limits: CreateRecoveryTokenInput = CreateRecoveryTokenInput(),
|
||||
auth_token: TokenHeader = Depends(get_token_header),
|
||||
):
|
||||
try:
|
||||
token = get_new_api_recovery_key(limits.expiration, limits.uses)
|
||||
except InvalidExpirationDate as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except InvalidUsesLeft as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return {"token": token}
|
||||
|
||||
|
||||
class UseTokenInput(BaseModel):
|
||||
token: str
|
||||
device: str
|
||||
|
||||
|
||||
@router.post("/recovery_token/use")
|
||||
async def rest_use_recovery_token(input: UseTokenInput):
|
||||
token = use_mnemonic_recovery_token(input.token, input.device)
|
||||
if token is None:
|
||||
raise HTTPException(status_code=404, detail="Token not found")
|
||||
return {"token": token}
|
||||
|
||||
|
||||
@router.post("/new_device")
|
||||
async def rest_new_device(auth_token: TokenHeader = Depends(get_token_header)):
|
||||
token = get_new_device_auth_token()
|
||||
return {"token": token}
|
||||
|
||||
|
||||
@router.delete("/new_device")
|
||||
async def rest_delete_new_device_token(
|
||||
auth_token: TokenHeader = Depends(get_token_header),
|
||||
):
|
||||
delete_new_device_auth_token()
|
||||
return {"token": None}
|
||||
|
||||
|
||||
@router.post("/new_device/authorize")
|
||||
async def rest_new_device_authorize(input: UseTokenInput):
|
||||
token = use_new_device_auth_token(input.token, input.device)
|
||||
if token is None:
|
||||
raise HTTPException(status_code=404, detail="Token not found")
|
||||
return {"message": "Device authorized", "token": token}
|
|
@ -1,336 +0,0 @@
|
|||
"""Basic services legacy api"""
|
||||
import base64
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from selfprivacy_api.actions.ssh import (
|
||||
InvalidPublicKey,
|
||||
KeyAlreadyExists,
|
||||
KeyNotFound,
|
||||
create_ssh_key,
|
||||
enable_ssh,
|
||||
get_ssh_settings,
|
||||
remove_ssh_key,
|
||||
set_ssh_settings,
|
||||
)
|
||||
from selfprivacy_api.actions.users import UserNotFound, get_user_by_username
|
||||
|
||||
from selfprivacy_api.dependencies import get_token_header
|
||||
from selfprivacy_api.services.bitwarden import Bitwarden
|
||||
from selfprivacy_api.services.gitea import Gitea
|
||||
from selfprivacy_api.services.mailserver import MailServer
|
||||
from selfprivacy_api.services.nextcloud import Nextcloud
|
||||
from selfprivacy_api.services.ocserv import Ocserv
|
||||
from selfprivacy_api.services.pleroma import Pleroma
|
||||
from selfprivacy_api.services.service import ServiceStatus
|
||||
from selfprivacy_api.utils import get_dkim_key, get_domain
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/services",
|
||||
tags=["services"],
|
||||
dependencies=[Depends(get_token_header)],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
def service_status_to_return_code(status: ServiceStatus):
|
||||
"""Converts service status object to return code for
|
||||
compatibility with legacy api"""
|
||||
if status == ServiceStatus.ACTIVE:
|
||||
return 0
|
||||
elif status == ServiceStatus.FAILED:
|
||||
return 1
|
||||
elif status == ServiceStatus.INACTIVE:
|
||||
return 3
|
||||
elif status == ServiceStatus.OFF:
|
||||
return 4
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def get_status():
|
||||
"""Get the status of the services"""
|
||||
mail_status = MailServer.get_status()
|
||||
bitwarden_status = Bitwarden.get_status()
|
||||
gitea_status = Gitea.get_status()
|
||||
nextcloud_status = Nextcloud.get_status()
|
||||
ocserv_stauts = Ocserv.get_status()
|
||||
pleroma_status = Pleroma.get_status()
|
||||
|
||||
return {
|
||||
"imap": service_status_to_return_code(mail_status),
|
||||
"smtp": service_status_to_return_code(mail_status),
|
||||
"http": 0,
|
||||
"bitwarden": service_status_to_return_code(bitwarden_status),
|
||||
"gitea": service_status_to_return_code(gitea_status),
|
||||
"nextcloud": service_status_to_return_code(nextcloud_status),
|
||||
"ocserv": service_status_to_return_code(ocserv_stauts),
|
||||
"pleroma": service_status_to_return_code(pleroma_status),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/bitwarden/enable")
|
||||
async def enable_bitwarden():
|
||||
"""Enable Bitwarden"""
|
||||
Bitwarden.enable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Bitwarden enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/bitwarden/disable")
|
||||
async def disable_bitwarden():
|
||||
"""Disable Bitwarden"""
|
||||
Bitwarden.disable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Bitwarden disabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/gitea/enable")
|
||||
async def enable_gitea():
|
||||
"""Enable Gitea"""
|
||||
Gitea.enable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Gitea enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/gitea/disable")
|
||||
async def disable_gitea():
|
||||
"""Disable Gitea"""
|
||||
Gitea.disable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Gitea disabled",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/mailserver/dkim")
|
||||
async def get_mailserver_dkim():
|
||||
"""Get the DKIM record for the mailserver"""
|
||||
domain = get_domain()
|
||||
|
||||
dkim = get_dkim_key(domain, parse=False)
|
||||
if dkim is None:
|
||||
raise HTTPException(status_code=404, detail="DKIM record not found")
|
||||
dkim = base64.b64encode(dkim.encode("utf-8")).decode("utf-8")
|
||||
return dkim
|
||||
|
||||
|
||||
@router.post("/nextcloud/enable")
|
||||
async def enable_nextcloud():
|
||||
"""Enable Nextcloud"""
|
||||
Nextcloud.enable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Nextcloud enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/nextcloud/disable")
|
||||
async def disable_nextcloud():
|
||||
"""Disable Nextcloud"""
|
||||
Nextcloud.disable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Nextcloud disabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/ocserv/enable")
|
||||
async def enable_ocserv():
|
||||
"""Enable Ocserv"""
|
||||
Ocserv.enable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Ocserv enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/ocserv/disable")
|
||||
async def disable_ocserv():
|
||||
"""Disable Ocserv"""
|
||||
Ocserv.disable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Ocserv disabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/pleroma/enable")
|
||||
async def enable_pleroma():
|
||||
"""Enable Pleroma"""
|
||||
Pleroma.enable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Pleroma enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.post("/pleroma/disable")
|
||||
async def disable_pleroma():
|
||||
"""Disable Pleroma"""
|
||||
Pleroma.disable()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "Pleroma disabled",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/restic/backup/list")
|
||||
async def get_restic_backup_list():
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
@router.put("/restic/backup/create")
|
||||
async def create_restic_backup():
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/restic/backup/status")
|
||||
async def get_restic_backup_status():
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/restic/backup/reload")
|
||||
async def reload_restic_backup():
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
class BackupRestoreInput(BaseModel):
|
||||
backupId: str
|
||||
|
||||
|
||||
@router.put("/restic/backup/restore")
|
||||
async def restore_restic_backup(backup: BackupRestoreInput):
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
class BackupConfigInput(BaseModel):
|
||||
accountId: str
|
||||
accountKey: str
|
||||
bucket: str
|
||||
|
||||
|
||||
@router.put("/restic/backblaze/config")
|
||||
async def set_backblaze_config(backup_config: BackupConfigInput):
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail="This endpoint is deprecated, please use GraphQL API",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/ssh/enable")
|
||||
async def rest_enable_ssh():
|
||||
"""Enable SSH"""
|
||||
enable_ssh()
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "SSH enabled",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/ssh")
|
||||
async def rest_get_ssh():
|
||||
"""Get the SSH configuration"""
|
||||
settings = get_ssh_settings()
|
||||
return {
|
||||
"enable": settings.enable,
|
||||
"passwordAuthentication": settings.passwordAuthentication,
|
||||
}
|
||||
|
||||
|
||||
class SshConfigInput(BaseModel):
|
||||
enable: Optional[bool] = None
|
||||
passwordAuthentication: Optional[bool] = None
|
||||
|
||||
|
||||
@router.put("/ssh")
|
||||
async def rest_set_ssh(ssh_config: SshConfigInput):
|
||||
"""Set the SSH configuration"""
|
||||
set_ssh_settings(ssh_config.enable, ssh_config.passwordAuthentication)
|
||||
|
||||
return "SSH settings changed"
|
||||
|
||||
|
||||
class SshKeyInput(BaseModel):
|
||||
public_key: str
|
||||
|
||||
|
||||
@router.put("/ssh/key/send", status_code=201)
|
||||
async def rest_send_ssh_key(input: SshKeyInput):
|
||||
"""Send the SSH key"""
|
||||
try:
|
||||
create_ssh_key("root", input.public_key)
|
||||
except KeyAlreadyExists as error:
|
||||
raise HTTPException(status_code=409, detail="Key already exists") from error
|
||||
except InvalidPublicKey as error:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid key type. Only ssh-ed25519 and ssh-rsa are supported",
|
||||
) from error
|
||||
|
||||
return {
|
||||
"status": 0,
|
||||
"message": "SSH key sent",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/ssh/keys/{username}")
|
||||
async def rest_get_ssh_keys(username: str):
|
||||
"""Get the SSH keys for a user"""
|
||||
user = get_user_by_username(username)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return user.ssh_keys
|
||||
|
||||
|
||||
@router.post("/ssh/keys/{username}", status_code=201)
|
||||
async def rest_add_ssh_key(username: str, input: SshKeyInput):
|
||||
try:
|
||||
create_ssh_key(username, input.public_key)
|
||||
except KeyAlreadyExists as error:
|
||||
raise HTTPException(status_code=409, detail="Key already exists") from error
|
||||
except InvalidPublicKey as error:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid key type. Only ssh-ed25519 and ssh-rsa are supported",
|
||||
) from error
|
||||
except UserNotFound as error:
|
||||
raise HTTPException(status_code=404, detail="User not found") from error
|
||||
|
||||
return {
|
||||
"message": "New SSH key successfully written",
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/ssh/keys/{username}")
|
||||
async def rest_delete_ssh_key(username: str, input: SshKeyInput):
|
||||
try:
|
||||
remove_ssh_key(username, input.public_key)
|
||||
except KeyNotFound as error:
|
||||
raise HTTPException(status_code=404, detail="Key not found") from error
|
||||
except UserNotFound as error:
|
||||
raise HTTPException(status_code=404, detail="User not found") from error
|
||||
return {"message": "SSH key deleted"}
|
|
@ -1,105 +0,0 @@
|
|||
from typing import Optional
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from selfprivacy_api.dependencies import get_token_header
|
||||
|
||||
import selfprivacy_api.actions.system as system_actions
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/system",
|
||||
tags=["system"],
|
||||
dependencies=[Depends(get_token_header)],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/configuration/timezone")
|
||||
async def get_timezone():
|
||||
"""Get the timezone of the server"""
|
||||
return system_actions.get_timezone()
|
||||
|
||||
|
||||
class ChangeTimezoneRequestBody(BaseModel):
|
||||
"""Change the timezone of the server"""
|
||||
|
||||
timezone: str
|
||||
|
||||
|
||||
@router.put("/configuration/timezone")
|
||||
async def change_timezone(timezone: ChangeTimezoneRequestBody):
|
||||
"""Change the timezone of the server"""
|
||||
try:
|
||||
system_actions.change_timezone(timezone.timezone)
|
||||
except system_actions.InvalidTimezone as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return {"timezone": timezone.timezone}
|
||||
|
||||
|
||||
@router.get("/configuration/autoUpgrade")
|
||||
async def get_auto_upgrade_settings():
|
||||
"""Get the auto-upgrade settings"""
|
||||
return system_actions.get_auto_upgrade_settings().dict()
|
||||
|
||||
|
||||
class AutoUpgradeSettings(BaseModel):
|
||||
"""Settings for auto-upgrading user data"""
|
||||
|
||||
enable: Optional[bool] = None
|
||||
allowReboot: Optional[bool] = None
|
||||
|
||||
|
||||
@router.put("/configuration/autoUpgrade")
|
||||
async def set_auto_upgrade_settings(settings: AutoUpgradeSettings):
|
||||
"""Set the auto-upgrade settings"""
|
||||
system_actions.set_auto_upgrade_settings(settings.enable, settings.allowReboot)
|
||||
return "Auto-upgrade settings changed"
|
||||
|
||||
|
||||
@router.get("/configuration/apply")
|
||||
async def apply_configuration():
|
||||
"""Apply the configuration"""
|
||||
return_code = system_actions.rebuild_system()
|
||||
return return_code
|
||||
|
||||
|
||||
@router.get("/configuration/rollback")
|
||||
async def rollback_configuration():
|
||||
"""Rollback the configuration"""
|
||||
return_code = system_actions.rollback_system()
|
||||
return return_code
|
||||
|
||||
|
||||
@router.get("/configuration/upgrade")
|
||||
async def upgrade_configuration():
|
||||
"""Upgrade the configuration"""
|
||||
return_code = system_actions.upgrade_system()
|
||||
return return_code
|
||||
|
||||
|
||||
@router.get("/reboot")
|
||||
async def reboot_system():
|
||||
"""Reboot the system"""
|
||||
system_actions.reboot_system()
|
||||
return "System reboot has started"
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
async def get_system_version():
|
||||
"""Get the system version"""
|
||||
return {"system_version": system_actions.get_system_version()}
|
||||
|
||||
|
||||
@router.get("/pythonVersion")
|
||||
async def get_python_version():
|
||||
"""Get the Python version"""
|
||||
return system_actions.get_python_version()
|
||||
|
||||
|
||||
@router.get("/configuration/pull")
|
||||
async def pull_configuration():
|
||||
"""Pull the configuration"""
|
||||
action_result = system_actions.pull_repository_changes()
|
||||
if action_result.status == 0:
|
||||
return action_result.dict()
|
||||
raise HTTPException(status_code=500, detail=action_result.dict())
|
|
@ -1,62 +0,0 @@
|
|||
"""Users management module"""
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
import selfprivacy_api.actions.users as users_actions
|
||||
|
||||
from selfprivacy_api.dependencies import get_token_header
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/users",
|
||||
tags=["users"],
|
||||
dependencies=[Depends(get_token_header)],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_users(withMainUser: bool = False):
|
||||
"""Get the list of users"""
|
||||
users: list[users_actions.UserDataUser] = users_actions.get_users(
|
||||
exclude_primary=not withMainUser, exclude_root=True
|
||||
)
|
||||
|
||||
return [user.username for user in users]
|
||||
|
||||
|
||||
class UserInput(BaseModel):
|
||||
"""User input"""
|
||||
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
async def create_user(user: UserInput):
|
||||
try:
|
||||
users_actions.create_user(user.username, user.password)
|
||||
except users_actions.PasswordIsEmpty as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except users_actions.UsernameForbidden as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
except users_actions.UsernameNotAlphanumeric as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except users_actions.UsernameTooLong as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except users_actions.UserAlreadyExists as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
|
||||
return {"result": 0, "username": user.username}
|
||||
|
||||
|
||||
@router.delete("/{username}")
|
||||
async def delete_user(username: str):
|
||||
try:
|
||||
users_actions.delete_user(username)
|
||||
except users_actions.UserNotFound as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except users_actions.UserIsProtected as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
return {"result": 0, "username": username}
|
|
@ -58,11 +58,6 @@ class Bitwarden(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "Password database, encryption certificate and attachments."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("bitwarden", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
"""
|
||||
|
@ -76,22 +71,6 @@ class Bitwarden(Service):
|
|||
"""
|
||||
return get_service_status("vaultwarden.service")
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
"""Enable Bitwarden service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "bitwarden" not in user_data:
|
||||
user_data["bitwarden"] = {}
|
||||
user_data["bitwarden"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""Disable Bitwarden service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "bitwarden" not in user_data:
|
||||
user_data["bitwarden"] = {}
|
||||
user_data["bitwarden"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
subprocess.run(["systemctl", "stop", "vaultwarden.service"])
|
||||
|
|
|
@ -54,11 +54,6 @@ class Gitea(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "Git repositories, database and user data."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("gitea", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
"""
|
||||
|
@ -71,22 +66,6 @@ class Gitea(Service):
|
|||
"""
|
||||
return get_service_status("gitea.service")
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
"""Enable Gitea service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "gitea" not in user_data:
|
||||
user_data["gitea"] = {}
|
||||
user_data["gitea"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""Disable Gitea service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "gitea" not in user_data:
|
||||
user_data["gitea"] = {}
|
||||
user_data["gitea"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
subprocess.run(["systemctl", "stop", "gitea.service"])
|
||||
|
|
|
@ -55,33 +55,12 @@ class Jitsi(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "Secrets that are used to encrypt the communication."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("jitsi", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
return get_service_status_from_several_units(
|
||||
["jitsi-videobridge.service", "jicofo.service"]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
"""Enable Jitsi service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "jitsi" not in user_data:
|
||||
user_data["jitsi"] = {}
|
||||
user_data["jitsi"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""Disable Gitea service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "jitsi" not in user_data:
|
||||
user_data["jitsi"] = {}
|
||||
user_data["jitsi"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
subprocess.run(
|
||||
|
|
|
@ -53,11 +53,6 @@ class Nextcloud(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "All the files and other data stored in Nextcloud."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("nextcloud", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
"""
|
||||
|
@ -71,22 +66,6 @@ class Nextcloud(Service):
|
|||
"""
|
||||
return get_service_status("phpfpm-nextcloud.service")
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
"""Enable Nextcloud service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "nextcloud" not in user_data:
|
||||
user_data["nextcloud"] = {}
|
||||
user_data["nextcloud"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
"""Disable Nextcloud service."""
|
||||
with WriteUserData() as user_data:
|
||||
if "nextcloud" not in user_data:
|
||||
user_data["nextcloud"] = {}
|
||||
user_data["nextcloud"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
"""Stop Nextcloud service."""
|
||||
|
|
|
@ -51,29 +51,10 @@ class Ocserv(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "Nothing to backup."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("ocserv", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
return get_service_status("ocserv.service")
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
with WriteUserData() as user_data:
|
||||
if "ocserv" not in user_data:
|
||||
user_data["ocserv"] = {}
|
||||
user_data["ocserv"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
with WriteUserData() as user_data:
|
||||
if "ocserv" not in user_data:
|
||||
user_data["ocserv"] = {}
|
||||
user_data["ocserv"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
subprocess.run(["systemctl", "stop", "ocserv.service"], check=False)
|
||||
|
|
|
@ -50,29 +50,10 @@ class Pleroma(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "Your Pleroma accounts, posts and media."
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("pleroma", {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
def get_status() -> ServiceStatus:
|
||||
return get_service_status("pleroma.service")
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
with WriteUserData() as user_data:
|
||||
if "pleroma" not in user_data:
|
||||
user_data["pleroma"] = {}
|
||||
user_data["pleroma"]["enable"] = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
with WriteUserData() as user_data:
|
||||
if "pleroma" not in user_data:
|
||||
user_data["pleroma"] = {}
|
||||
user_data["pleroma"]["enable"] = False
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
subprocess.run(["systemctl", "stop", "pleroma.service"])
|
||||
|
|
|
@ -12,6 +12,7 @@ from selfprivacy_api.services.generic_size_counter import get_storage_usage
|
|||
from selfprivacy_api.services.owned_path import OwnedPath
|
||||
from selfprivacy_api import utils
|
||||
from selfprivacy_api.utils.waitloop import wait_until_true
|
||||
from selfprivacy_api.utils import ReadUserData, WriteUserData, get_domain
|
||||
|
||||
DEFAULT_START_STOP_TIMEOUT = 5 * 60
|
||||
|
||||
|
@ -125,11 +126,17 @@ class Service(ABC):
|
|||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def is_enabled() -> bool:
|
||||
"""`True` if the service is enabled."""
|
||||
pass
|
||||
@classmethod
|
||||
def is_enabled(cls) -> bool:
|
||||
"""
|
||||
`True` if the service is enabled.
|
||||
`False` if it is not enabled or not defined in file
|
||||
If there is nothing in the file, this is equivalent to False
|
||||
because NixOS won't enable it then.
|
||||
"""
|
||||
name = cls.get_id()
|
||||
with ReadUserData() as user_data:
|
||||
return user_data.get("modules", {}).get(name, {}).get("enable", False)
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
|
@ -137,17 +144,25 @@ class Service(ABC):
|
|||
"""The status of the service, reported by systemd."""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def enable():
|
||||
"""Enable the service. Usually this means enabling systemd unit."""
|
||||
pass
|
||||
@classmethod
|
||||
def _set_enable(cls, enable: bool):
|
||||
name = cls.get_id()
|
||||
with WriteUserData() as user_data:
|
||||
if "modules" not in user_data:
|
||||
user_data["modules"] = {}
|
||||
if name not in user_data["modules"]:
|
||||
user_data["modules"][name] = {}
|
||||
user_data["modules"][name]["enable"] = enable
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def disable():
|
||||
@classmethod
|
||||
def enable(cls):
|
||||
"""Enable the service. Usually this means enabling systemd unit."""
|
||||
cls._set_enable(True)
|
||||
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
"""Disable the service. Usually this means disabling systemd unit."""
|
||||
pass
|
||||
cls._set_enable(False)
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
|
@ -247,6 +262,8 @@ class Service(ABC):
|
|||
|
||||
@abstractmethod
|
||||
def move_to_volume(self, volume: BlockDevice) -> Job:
|
||||
"""Cannot raise errors.
|
||||
Returns errors as an errored out Job instead."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -8,9 +8,10 @@ from os import path
|
|||
|
||||
# from enum import Enum
|
||||
|
||||
from selfprivacy_api.jobs import Job
|
||||
from selfprivacy_api.jobs import Job, Jobs, JobStatus
|
||||
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
|
||||
from selfprivacy_api.utils.block_devices import BlockDevice
|
||||
from selfprivacy_api.services.generic_service_mover import move_service, FolderMoveNames
|
||||
import selfprivacy_api.utils.network as network_utils
|
||||
|
||||
from selfprivacy_api.services.test_service.icon import BITWARDEN_ICON
|
||||
|
@ -22,16 +23,19 @@ class DummyService(Service):
|
|||
"""A test service"""
|
||||
|
||||
folders: List[str] = []
|
||||
startstop_delay = 0
|
||||
startstop_delay = 0.0
|
||||
backuppable = True
|
||||
movable = True
|
||||
# if False, we try to actually move
|
||||
simulate_moving = True
|
||||
drive = "sda1"
|
||||
|
||||
def __init_subclass__(cls, folders: List[str]):
|
||||
cls.folders = folders
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
status_file = self.status_file()
|
||||
with open(status_file, "w") as file:
|
||||
with open(self.status_file(), "w") as file:
|
||||
file.write(ServiceStatus.ACTIVE.value)
|
||||
|
||||
@staticmethod
|
||||
|
@ -61,9 +65,9 @@ class DummyService(Service):
|
|||
domain = "test.com"
|
||||
return f"https://password.{domain}"
|
||||
|
||||
@staticmethod
|
||||
def is_movable() -> bool:
|
||||
return True
|
||||
@classmethod
|
||||
def is_movable(cls) -> bool:
|
||||
return cls.movable
|
||||
|
||||
@staticmethod
|
||||
def is_required() -> bool:
|
||||
|
@ -73,10 +77,6 @@ class DummyService(Service):
|
|||
def get_backup_description() -> str:
|
||||
return "How did we get here?"
|
||||
|
||||
@staticmethod
|
||||
def is_enabled() -> bool:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def status_file(cls) -> str:
|
||||
dir = cls.folders[0]
|
||||
|
@ -116,22 +116,30 @@ class DummyService(Service):
|
|||
we can only set it up dynamically for tests via a classmethod"""
|
||||
cls.backuppable = new_value
|
||||
|
||||
@classmethod
|
||||
def set_movable(cls, new_value: bool) -> None:
|
||||
"""For tests: because is_movale is static,
|
||||
we can only set it up dynamically for tests via a classmethod"""
|
||||
cls.movable = new_value
|
||||
|
||||
@classmethod
|
||||
def can_be_backed_up(cls) -> bool:
|
||||
"""`True` if the service can be backed up."""
|
||||
return cls.backuppable
|
||||
|
||||
@classmethod
|
||||
def enable(cls):
|
||||
pass
|
||||
def set_delay(cls, new_delay_sec: float) -> None:
|
||||
cls.startstop_delay = new_delay_sec
|
||||
|
||||
@classmethod
|
||||
def disable(cls, delay):
|
||||
pass
|
||||
def set_drive(cls, new_drive: str) -> None:
|
||||
cls.drive = new_drive
|
||||
|
||||
@classmethod
|
||||
def set_delay(cls, new_delay):
|
||||
cls.startstop_delay = new_delay
|
||||
def set_simulated_moves(cls, enabled: bool) -> None:
|
||||
"""If True, this service will not actually call moving code
|
||||
when moved"""
|
||||
cls.simulate_moving = enabled
|
||||
|
||||
@classmethod
|
||||
def stop(cls):
|
||||
|
@ -169,9 +177,9 @@ class DummyService(Service):
|
|||
storage_usage = 0
|
||||
return storage_usage
|
||||
|
||||
@staticmethod
|
||||
def get_drive() -> str:
|
||||
return "sda1"
|
||||
@classmethod
|
||||
def get_drive(cls) -> str:
|
||||
return cls.drive
|
||||
|
||||
@classmethod
|
||||
def get_folders(cls) -> List[str]:
|
||||
|
@ -198,4 +206,22 @@ class DummyService(Service):
|
|||
]
|
||||
|
||||
def move_to_volume(self, volume: BlockDevice) -> Job:
|
||||
pass
|
||||
job = Jobs.add(
|
||||
type_id=f"services.{self.get_id()}.move",
|
||||
name=f"Move {self.get_display_name()}",
|
||||
description=f"Moving {self.get_display_name()} data to {volume.name}",
|
||||
)
|
||||
if self.simulate_moving is False:
|
||||
# completely generic code, TODO: make it the default impl.
|
||||
move_service(
|
||||
self,
|
||||
volume,
|
||||
job,
|
||||
FolderMoveNames.default_foldermoves(self),
|
||||
self.get_id(),
|
||||
)
|
||||
else:
|
||||
Jobs.update(job, status=JobStatus.FINISHED)
|
||||
|
||||
self.set_drive(volume.name)
|
||||
return job
|
||||
|
|
|
@ -6,12 +6,14 @@ import json
|
|||
import os
|
||||
import subprocess
|
||||
import portalocker
|
||||
import typing
|
||||
|
||||
|
||||
USERDATA_FILE = "/etc/nixos/userdata/userdata.json"
|
||||
TOKENS_FILE = "/etc/nixos/userdata/tokens.json"
|
||||
JOBS_FILE = "/etc/nixos/userdata/jobs.json"
|
||||
DOMAIN_FILE = "/var/domain"
|
||||
DKIM_DIR = "/var/dkim/"
|
||||
|
||||
|
||||
class UserDataFiles(Enum):
|
||||
|
@ -166,26 +168,31 @@ def parse_date(date_str: str) -> datetime.datetime:
|
|||
raise ValueError("Invalid date string")
|
||||
|
||||
|
||||
def get_dkim_key(domain, parse=True):
|
||||
def parse_dkim(dkim: str) -> str:
|
||||
# extract key from file
|
||||
dkim = dkim.split("(")[1]
|
||||
dkim = dkim.split(")")[0]
|
||||
# replace all quotes with nothing
|
||||
dkim = dkim.replace('"', "")
|
||||
# trim whitespace, remove newlines and tabs
|
||||
dkim = dkim.strip()
|
||||
dkim = dkim.replace("\n", "")
|
||||
dkim = dkim.replace("\t", "")
|
||||
# remove all redundant spaces
|
||||
dkim = " ".join(dkim.split())
|
||||
return dkim
|
||||
|
||||
|
||||
def get_dkim_key(domain: str, parse: bool = True) -> typing.Optional[str]:
|
||||
"""Get DKIM key from /var/dkim/<domain>.selector.txt"""
|
||||
if os.path.exists("/var/dkim/" + domain + ".selector.txt"):
|
||||
cat_process = subprocess.Popen(
|
||||
["cat", "/var/dkim/" + domain + ".selector.txt"], stdout=subprocess.PIPE
|
||||
)
|
||||
dkim = cat_process.communicate()[0]
|
||||
if parse:
|
||||
# Extract key from file
|
||||
dkim = dkim.split(b"(")[1]
|
||||
dkim = dkim.split(b")")[0]
|
||||
# Replace all quotes with nothing
|
||||
dkim = dkim.replace(b'"', b"")
|
||||
# Trim whitespace, remove newlines and tabs
|
||||
dkim = dkim.strip()
|
||||
dkim = dkim.replace(b"\n", b"")
|
||||
dkim = dkim.replace(b"\t", b"")
|
||||
# Remove all redundant spaces
|
||||
dkim = b" ".join(dkim.split())
|
||||
return str(dkim, "utf-8")
|
||||
|
||||
dkim_path = os.path.join(DKIM_DIR, domain + ".selector.txt")
|
||||
if os.path.exists(dkim_path):
|
||||
with open(dkim_path, encoding="utf-8") as dkim_file:
|
||||
dkim = dkim_file.read()
|
||||
if parse:
|
||||
dkim = parse_dkim(dkim)
|
||||
return dkim
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Wrapper for block device functions."""
|
||||
"""A block device API wrapping lsblk"""
|
||||
from __future__ import annotations
|
||||
import subprocess
|
||||
import json
|
||||
import typing
|
||||
|
@ -11,6 +12,7 @@ def get_block_device(device_name):
|
|||
"""
|
||||
Return a block device by name.
|
||||
"""
|
||||
# TODO: remove the function and related tests: dublicated by singleton
|
||||
lsblk_output = subprocess.check_output(
|
||||
[
|
||||
"lsblk",
|
||||
|
@ -43,22 +45,37 @@ class BlockDevice:
|
|||
A block device.
|
||||
"""
|
||||
|
||||
def __init__(self, block_device):
|
||||
self.name = block_device["name"]
|
||||
self.path = block_device["path"]
|
||||
self.fsavail = str(block_device["fsavail"])
|
||||
self.fssize = str(block_device["fssize"])
|
||||
self.fstype = block_device["fstype"]
|
||||
self.fsused = str(block_device["fsused"])
|
||||
self.mountpoints = block_device["mountpoints"]
|
||||
self.label = block_device["label"]
|
||||
self.uuid = block_device["uuid"]
|
||||
self.size = str(block_device["size"])
|
||||
self.model = block_device["model"]
|
||||
self.serial = block_device["serial"]
|
||||
self.type = block_device["type"]
|
||||
def __init__(self, device_dict: dict):
|
||||
self.update_from_dict(device_dict)
|
||||
|
||||
def update_from_dict(self, device_dict: dict):
|
||||
self.name = device_dict["name"]
|
||||
self.path = device_dict["path"]
|
||||
self.fsavail = str(device_dict["fsavail"])
|
||||
self.fssize = str(device_dict["fssize"])
|
||||
self.fstype = device_dict["fstype"]
|
||||
self.fsused = str(device_dict["fsused"])
|
||||
self.mountpoints = device_dict["mountpoints"]
|
||||
self.label = device_dict["label"]
|
||||
self.uuid = device_dict["uuid"]
|
||||
self.size = str(device_dict["size"])
|
||||
self.model = device_dict["model"]
|
||||
self.serial = device_dict["serial"]
|
||||
self.type = device_dict["type"]
|
||||
self.locked = False
|
||||
|
||||
self.children: typing.List[BlockDevice] = []
|
||||
if "children" in device_dict.keys():
|
||||
for child in device_dict["children"]:
|
||||
self.children.append(BlockDevice(child))
|
||||
|
||||
def all_children(self) -> typing.List[BlockDevice]:
|
||||
result = []
|
||||
for child in self.children:
|
||||
result.extend(child.all_children())
|
||||
result.append(child)
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -82,17 +99,7 @@ class BlockDevice:
|
|||
Update current data and return a dictionary of stats.
|
||||
"""
|
||||
device = get_block_device(self.name)
|
||||
self.fsavail = str(device["fsavail"])
|
||||
self.fssize = str(device["fssize"])
|
||||
self.fstype = device["fstype"]
|
||||
self.fsused = str(device["fsused"])
|
||||
self.mountpoints = device["mountpoints"]
|
||||
self.label = device["label"]
|
||||
self.uuid = device["uuid"]
|
||||
self.size = str(device["size"])
|
||||
self.model = device["model"]
|
||||
self.serial = device["serial"]
|
||||
self.type = device["type"]
|
||||
self.update_from_dict(device)
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
|
@ -110,6 +117,14 @@ class BlockDevice:
|
|||
"type": self.type,
|
||||
}
|
||||
|
||||
def is_usable_partition(self):
|
||||
# Ignore devices with type "rom"
|
||||
if self.type == "rom":
|
||||
return False
|
||||
if self.fstype == "ext4":
|
||||
return True
|
||||
return False
|
||||
|
||||
def resize(self):
|
||||
"""
|
||||
Resize the block device.
|
||||
|
@ -165,41 +180,16 @@ class BlockDevices(metaclass=SingletonMetaclass):
|
|||
"""
|
||||
Update the list of block devices.
|
||||
"""
|
||||
devices = []
|
||||
lsblk_output = subprocess.check_output(
|
||||
[
|
||||
"lsblk",
|
||||
"-J",
|
||||
"-b",
|
||||
"-o",
|
||||
"NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE",
|
||||
]
|
||||
)
|
||||
lsblk_output = lsblk_output.decode("utf-8")
|
||||
lsblk_output = json.loads(lsblk_output)
|
||||
for device in lsblk_output["blockdevices"]:
|
||||
# Ignore devices with type "rom"
|
||||
if device["type"] == "rom":
|
||||
continue
|
||||
# Ignore iso9660 devices
|
||||
if device["fstype"] == "iso9660":
|
||||
continue
|
||||
if device["fstype"] is None:
|
||||
if "children" in device:
|
||||
for child in device["children"]:
|
||||
if child["fstype"] == "ext4":
|
||||
device = child
|
||||
break
|
||||
devices.append(device)
|
||||
# Add new devices and delete non-existent devices
|
||||
devices = BlockDevices.lsblk_devices()
|
||||
|
||||
children = []
|
||||
for device in devices:
|
||||
if device["name"] not in [
|
||||
block_device.name for block_device in self.block_devices
|
||||
]:
|
||||
self.block_devices.append(BlockDevice(device))
|
||||
for block_device in self.block_devices:
|
||||
if block_device.name not in [device["name"] for device in devices]:
|
||||
self.block_devices.remove(block_device)
|
||||
children.extend(device.all_children())
|
||||
devices.extend(children)
|
||||
|
||||
valid_devices = [device for device in devices if device.is_usable_partition()]
|
||||
|
||||
self.block_devices = valid_devices
|
||||
|
||||
def get_block_device(self, name: str) -> typing.Optional[BlockDevice]:
|
||||
"""
|
||||
|
@ -236,3 +226,25 @@ class BlockDevices(metaclass=SingletonMetaclass):
|
|||
if "/" in block_device.mountpoints:
|
||||
return block_device
|
||||
raise RuntimeError("No root block device found")
|
||||
|
||||
@staticmethod
|
||||
def lsblk_device_dicts() -> typing.List[dict]:
|
||||
lsblk_output_bytes = subprocess.check_output(
|
||||
[
|
||||
"lsblk",
|
||||
"-J",
|
||||
"-b",
|
||||
"-o",
|
||||
"NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE",
|
||||
]
|
||||
)
|
||||
lsblk_output = lsblk_output_bytes.decode("utf-8")
|
||||
return json.loads(lsblk_output)["blockdevices"]
|
||||
|
||||
@staticmethod
|
||||
def lsblk_devices() -> typing.List[BlockDevice]:
|
||||
devices = []
|
||||
for device in BlockDevices.lsblk_device_dicts():
|
||||
devices.append(device)
|
||||
|
||||
return [BlockDevice(device) for device in devices]
|
||||
|
|
2
setup.py
2
setup.py
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||
|
||||
setup(
|
||||
name="selfprivacy_api",
|
||||
version="2.4.3",
|
||||
version="3.0.0",
|
||||
packages=find_packages(),
|
||||
scripts=[
|
||||
"selfprivacy_api/app.py",
|
||||
|
|
|
@ -67,6 +67,10 @@ def generate_backup_query(query_array):
|
|||
return "query TestBackup {\n backup {" + "\n".join(query_array) + "}\n}"
|
||||
|
||||
|
||||
def generate_service_query(query_array):
|
||||
return "query TestService {\n services {" + "\n".join(query_array) + "}\n}"
|
||||
|
||||
|
||||
def mnemonic_to_hex(mnemonic):
|
||||
return Mnemonic(language="english").to_entropy(mnemonic).hex()
|
||||
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
# pylint: disable=unused-argument
|
||||
import os
|
||||
import pytest
|
||||
from os import path
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
import os.path as path
|
||||
import datetime
|
||||
|
||||
from os import path
|
||||
from os import makedirs
|
||||
from typing import Generator
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from selfprivacy_api.utils.huey import huey
|
||||
|
||||
import selfprivacy_api.services as services
|
||||
from selfprivacy_api.services import get_service_by_id, Service
|
||||
from selfprivacy_api.services.test_service import DummyService
|
||||
|
||||
from selfprivacy_api.models.tokens.token import Token
|
||||
from selfprivacy_api.repositories.tokens.json_tokens_repository import (
|
||||
JsonTokensRepository,
|
||||
|
@ -19,6 +26,9 @@ from selfprivacy_api.repositories.tokens.redis_tokens_repository import (
|
|||
|
||||
from tests.common import read_json
|
||||
|
||||
TESTFILE_BODY = "testytest!"
|
||||
TESTFILE_2_BODY = "testissimo!"
|
||||
|
||||
EMPTY_TOKENS_JSON = ' {"tokens": []}'
|
||||
|
||||
|
||||
|
@ -147,3 +157,49 @@ def wrong_auth_client(tokens_file, huey_database, jobs_file):
|
|||
client = TestClient(app)
|
||||
client.headers.update({"Authorization": "Bearer WRONG_TOKEN"})
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def raw_dummy_service(tmpdir):
|
||||
dirnames = ["test_service", "also_test_service"]
|
||||
service_dirs = []
|
||||
for d in dirnames:
|
||||
service_dir = path.join(tmpdir, d)
|
||||
makedirs(service_dir)
|
||||
service_dirs.append(service_dir)
|
||||
|
||||
testfile_path_1 = path.join(service_dirs[0], "testfile.txt")
|
||||
with open(testfile_path_1, "w") as file:
|
||||
file.write(TESTFILE_BODY)
|
||||
|
||||
testfile_path_2 = path.join(service_dirs[1], "testfile2.txt")
|
||||
with open(testfile_path_2, "w") as file:
|
||||
file.write(TESTFILE_2_BODY)
|
||||
|
||||
# we need this to not change get_folders() much
|
||||
class TestDummyService(DummyService, folders=service_dirs):
|
||||
pass
|
||||
|
||||
service = TestDummyService()
|
||||
# assert pickle.dumps(service) is not None
|
||||
return service
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def dummy_service(
|
||||
tmpdir, raw_dummy_service, generic_userdata
|
||||
) -> Generator[Service, None, None]:
|
||||
service = raw_dummy_service
|
||||
|
||||
# register our service
|
||||
services.services.append(service)
|
||||
|
||||
huey.immediate = True
|
||||
assert huey.immediate is True
|
||||
|
||||
assert get_service_by_id(service.get_id()) is not None
|
||||
service.enable()
|
||||
yield service
|
||||
|
||||
# cleanup because apparently it matters wrt tasks
|
||||
services.services.remove(service)
|
||||
|
|
|
@ -1,60 +1,55 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": true
|
||||
},
|
||||
"api": {"token": "TEST_TOKEN", "enableSwagger": false},
|
||||
"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"
|
||||
]
|
||||
"rootKeys": ["ssh-ed25519 KEY test@pc"]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": true
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"jitsi": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"autoUpgrade": {"enable": true, "allowReboot": true},
|
||||
"useBinds": true,
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
"sshKeys": ["ssh-rsa KEY test@pc"],
|
||||
"dns": {"provider": "CLOUDFLARE", "apiKey": "TOKEN"},
|
||||
"server": {"provider": "HETZNER"},
|
||||
"modules": {
|
||||
"bitwarden": {"enable": true},
|
||||
"gitea": {"enable": true},
|
||||
"ocserv": {"enable": true},
|
||||
"pleroma": {"enable": true},
|
||||
"jitsi": {"enable": true},
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"username": "user1",
|
||||
"hashedPassword": "HASHED_PASSWORD_1",
|
||||
"sshKeys": ["ssh-rsa KEY user1@pc"]
|
||||
},
|
||||
{
|
||||
"username": "user2",
|
||||
"hashedPassword": "HASHED_PASSWORD_2",
|
||||
"sshKeys": ["ssh-rsa KEY user2@pc"]
|
||||
},
|
||||
{
|
||||
"username": "user3",
|
||||
"hashedPassword": "HASHED_PASSWORD_3",
|
||||
"sshKeys": ["ssh-rsa KEY user3@pc"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import pytest
|
|||
|
||||
import os
|
||||
import os.path as path
|
||||
from os import makedirs
|
||||
from os import remove
|
||||
from os import listdir
|
||||
from os import urandom
|
||||
|
@ -13,7 +12,10 @@ import tempfile
|
|||
|
||||
from selfprivacy_api.utils.huey import huey
|
||||
|
||||
import selfprivacy_api.services as services
|
||||
import tempfile
|
||||
|
||||
from selfprivacy_api.utils.huey import huey
|
||||
|
||||
from selfprivacy_api.services import Service, get_all_services
|
||||
from selfprivacy_api.services import get_service_by_id
|
||||
from selfprivacy_api.services.service import ServiceStatus
|
||||
|
@ -46,13 +48,13 @@ from selfprivacy_api.backup.tasks import (
|
|||
from selfprivacy_api.backup.storage import Storage
|
||||
|
||||
|
||||
TESTFILE_BODY = "testytest!"
|
||||
TESTFILE_2_BODY = "testissimo!"
|
||||
REPO_NAME = "test_backup"
|
||||
|
||||
REPOFILE_NAME = "totallyunrelated"
|
||||
|
||||
|
||||
def prepare_localfile_backups(temp_dir):
|
||||
test_repo_path = path.join(temp_dir, "totallyunrelated")
|
||||
test_repo_path = path.join(temp_dir, REPOFILE_NAME)
|
||||
assert not path.exists(test_repo_path)
|
||||
Backups.set_localfile_repo(test_repo_path)
|
||||
|
||||
|
@ -67,16 +69,24 @@ def backups_local(tmpdir):
|
|||
|
||||
@pytest.fixture(scope="function")
|
||||
def backups(tmpdir):
|
||||
# for those tests that are supposed to pass with any repo
|
||||
"""
|
||||
For those tests that are supposed to pass with
|
||||
both local and cloud repos
|
||||
"""
|
||||
|
||||
# Sometimes this is false. Idk why.
|
||||
huey.immediate = True
|
||||
assert huey.immediate is True
|
||||
|
||||
Backups.reset()
|
||||
if BACKUP_PROVIDER_ENVS["kind"] in os.environ.keys():
|
||||
Backups.set_provider_from_envs()
|
||||
else:
|
||||
prepare_localfile_backups(tmpdir)
|
||||
Jobs.reset()
|
||||
# assert not repo_path
|
||||
|
||||
Backups.init_repo()
|
||||
assert Backups.provider().location == str(tmpdir) + "/" + REPOFILE_NAME
|
||||
yield
|
||||
Backups.erase_repo()
|
||||
|
||||
|
@ -86,49 +96,6 @@ def backups_backblaze(generic_userdata):
|
|||
Backups.reset(reset_json=False)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def raw_dummy_service(tmpdir):
|
||||
dirnames = ["test_service", "also_test_service"]
|
||||
service_dirs = []
|
||||
for d in dirnames:
|
||||
service_dir = path.join(tmpdir, d)
|
||||
makedirs(service_dir)
|
||||
service_dirs.append(service_dir)
|
||||
|
||||
testfile_path_1 = path.join(service_dirs[0], "testfile.txt")
|
||||
with open(testfile_path_1, "w") as file:
|
||||
file.write(TESTFILE_BODY)
|
||||
|
||||
testfile_path_2 = path.join(service_dirs[1], "testfile2.txt")
|
||||
with open(testfile_path_2, "w") as file:
|
||||
file.write(TESTFILE_2_BODY)
|
||||
|
||||
# we need this to not change get_folders() much
|
||||
class TestDummyService(DummyService, folders=service_dirs):
|
||||
pass
|
||||
|
||||
service = TestDummyService()
|
||||
return service
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def dummy_service(tmpdir, backups, raw_dummy_service) -> Service:
|
||||
service = raw_dummy_service
|
||||
|
||||
# register our service
|
||||
services.services.append(service)
|
||||
|
||||
# make sure we are in immediate mode because this thing is non pickleable to store on queue.
|
||||
huey.immediate = True
|
||||
assert huey.immediate is True
|
||||
|
||||
assert get_service_by_id(service.get_id()) is not None
|
||||
yield service
|
||||
|
||||
# cleanup because apparently it matters wrt tasks
|
||||
services.services.remove(service)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def memory_backup() -> AbstractBackupProvider:
|
||||
ProviderClass = providers.get_provider(BackupProvider.MEMORY)
|
||||
|
@ -777,7 +744,7 @@ def simulated_service_stopping_delay(request) -> float:
|
|||
def test_backup_service_task(backups, dummy_service, simulated_service_stopping_delay):
|
||||
dummy_service.set_delay(simulated_service_stopping_delay)
|
||||
|
||||
handle = start_backup(dummy_service)
|
||||
handle = start_backup(dummy_service.get_id())
|
||||
handle(blocking=True)
|
||||
|
||||
snaps = Backups.get_snapshots(dummy_service)
|
||||
|
@ -822,7 +789,7 @@ def test_backup_larger_file(backups, dummy_service):
|
|||
mega = 2**20
|
||||
make_large_file(dir, 100 * mega)
|
||||
|
||||
handle = start_backup(dummy_service)
|
||||
handle = start_backup(dummy_service.get_id())
|
||||
handle(blocking=True)
|
||||
|
||||
# results will be slightly different on different machines. if someone has troubles with it on their machine, consider dropping this test.
|
|
@ -416,32 +416,37 @@ def lsblk_full_mock(mocker):
|
|||
def test_get_block_devices(lsblk_full_mock, authorized_client):
|
||||
block_devices = BlockDevices().get_block_devices()
|
||||
assert len(block_devices) == 2
|
||||
assert block_devices[0].name == "sda1"
|
||||
assert block_devices[0].path == "/dev/sda1"
|
||||
assert block_devices[0].fsavail == "4605702144"
|
||||
assert block_devices[0].fssize == "19814920192"
|
||||
assert block_devices[0].fstype == "ext4"
|
||||
assert block_devices[0].fsused == "14353719296"
|
||||
assert block_devices[0].mountpoints == ["/nix/store", "/"]
|
||||
assert block_devices[0].label is None
|
||||
assert block_devices[0].uuid == "ec80c004-baec-4a2c-851d-0e1807135511"
|
||||
assert block_devices[0].size == "20210236928"
|
||||
assert block_devices[0].model is None
|
||||
assert block_devices[0].serial is None
|
||||
assert block_devices[0].type == "part"
|
||||
assert block_devices[1].name == "sdb"
|
||||
assert block_devices[1].path == "/dev/sdb"
|
||||
assert block_devices[1].fsavail == "11888545792"
|
||||
assert block_devices[1].fssize == "12573614080"
|
||||
assert block_devices[1].fstype == "ext4"
|
||||
assert block_devices[1].fsused == "24047616"
|
||||
assert block_devices[1].mountpoints == ["/volumes/sdb"]
|
||||
assert block_devices[1].label is None
|
||||
assert block_devices[1].uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751"
|
||||
assert block_devices[1].size == "12884901888"
|
||||
assert block_devices[1].model == "Volume"
|
||||
assert block_devices[1].serial == "21378102"
|
||||
assert block_devices[1].type == "disk"
|
||||
devices_by_name = {device.name: device for device in block_devices}
|
||||
sda1 = devices_by_name["sda1"]
|
||||
sdb = devices_by_name["sdb"]
|
||||
|
||||
assert sda1.name == "sda1"
|
||||
assert sda1.path == "/dev/sda1"
|
||||
assert sda1.fsavail == "4605702144"
|
||||
assert sda1.fssize == "19814920192"
|
||||
assert sda1.fstype == "ext4"
|
||||
assert sda1.fsused == "14353719296"
|
||||
assert sda1.mountpoints == ["/nix/store", "/"]
|
||||
assert sda1.label is None
|
||||
assert sda1.uuid == "ec80c004-baec-4a2c-851d-0e1807135511"
|
||||
assert sda1.size == "20210236928"
|
||||
assert sda1.model is None
|
||||
assert sda1.serial is None
|
||||
assert sda1.type == "part"
|
||||
|
||||
assert sdb.name == "sdb"
|
||||
assert sdb.path == "/dev/sdb"
|
||||
assert sdb.fsavail == "11888545792"
|
||||
assert sdb.fssize == "12573614080"
|
||||
assert sdb.fstype == "ext4"
|
||||
assert sdb.fsused == "24047616"
|
||||
assert sdb.mountpoints == ["/volumes/sdb"]
|
||||
assert sdb.label is None
|
||||
assert sdb.uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751"
|
||||
assert sdb.size == "12884901888"
|
||||
assert sdb.model == "Volume"
|
||||
assert sdb.serial == "21378102"
|
||||
assert sdb.type == "disk"
|
||||
|
||||
|
||||
def test_get_block_device(lsblk_full_mock, authorized_client):
|
||||
|
@ -506,3 +511,30 @@ def test_get_root_block_device(lsblk_full_mock, authorized_client):
|
|||
assert block_device.model is None
|
||||
assert block_device.serial is None
|
||||
assert block_device.type == "part"
|
||||
|
||||
|
||||
# Unassuming sanity check, yes this did fail
|
||||
def test_get_real_devices():
|
||||
block_devices = BlockDevices().get_block_devices()
|
||||
|
||||
assert block_devices is not None
|
||||
assert len(block_devices) > 0
|
||||
|
||||
|
||||
# Unassuming sanity check
|
||||
def test_get_real_root_device():
|
||||
devices = BlockDevices().get_block_devices()
|
||||
try:
|
||||
block_device = BlockDevices().get_root_block_device()
|
||||
except Exception as e:
|
||||
raise Exception("cannot get root device:", e, "devices found:", devices)
|
||||
assert block_device is not None
|
||||
assert block_device.name is not None
|
||||
assert block_device.name != ""
|
||||
|
||||
|
||||
def test_get_real_root_device_raw(authorized_client):
|
||||
block_device = BlockDevices().get_root_block_device()
|
||||
assert block_device is not None
|
||||
assert block_device.name is not None
|
||||
assert block_device.name != ""
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import pytest
|
||||
|
||||
import os
|
||||
from os import path
|
||||
from tests.conftest import global_data_dir
|
||||
|
||||
from selfprivacy_api.utils import get_dkim_key, get_domain
|
||||
|
||||
###############################################################################
|
||||
|
||||
DKIM_FILE_CONTENT = b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for test-domain.tld\n'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dkim_file(mocker, domain_file, tmpdir):
|
||||
domain = get_domain()
|
||||
assert domain is not None
|
||||
assert domain != ""
|
||||
|
||||
filename = domain + ".selector.txt"
|
||||
dkim_path = path.join(tmpdir, filename)
|
||||
|
||||
with open(dkim_path, "wb") as file:
|
||||
file.write(DKIM_FILE_CONTENT)
|
||||
|
||||
mocker.patch("selfprivacy_api.utils.DKIM_DIR", tmpdir)
|
||||
return dkim_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def domain_file(mocker):
|
||||
# TODO: move to conftest. Challenge: it does not behave with "/" like pytest datadir does
|
||||
domain_path = path.join(global_data_dir(), "domain")
|
||||
mocker.patch("selfprivacy_api.utils.DOMAIN_FILE", domain_path)
|
||||
return domain_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_dkim_file(dkim_file):
|
||||
os.remove(dkim_file)
|
||||
assert path.exists(dkim_file) is False
|
||||
return dkim_file
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def test_get_dkim_key(domain_file, dkim_file):
|
||||
"""Test DKIM key"""
|
||||
dkim_key = get_dkim_key("test-domain.tld")
|
||||
assert (
|
||||
dkim_key
|
||||
== "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB"
|
||||
)
|
||||
|
||||
|
||||
def test_no_dkim_key(domain_file, no_dkim_file):
|
||||
"""Test no DKIM key"""
|
||||
dkim_key = get_dkim_key("test-domain.tld")
|
||||
assert dkim_key is None
|
|
@ -1,89 +0,0 @@
|
|||
from tests.common import generate_api_query
|
||||
from tests.conftest import TOKENS_FILE_CONTENTS, DEVICE_WE_AUTH_TESTS_WITH
|
||||
|
||||
ORIGINAL_DEVICES = TOKENS_FILE_CONTENTS["tokens"]
|
||||
|
||||
|
||||
def assert_ok(response, request):
|
||||
data = assert_data(response)
|
||||
assert data[request]["success"] is True
|
||||
assert data[request]["message"] is not None
|
||||
assert data[request]["code"] == 200
|
||||
|
||||
|
||||
def assert_errorcode(response, request, code):
|
||||
data = assert_data(response)
|
||||
assert data[request]["success"] is False
|
||||
assert data[request]["message"] is not None
|
||||
assert data[request]["code"] == code
|
||||
|
||||
|
||||
def assert_empty(response):
|
||||
assert response.status_code == 200
|
||||
assert response.json().get("data") is None
|
||||
|
||||
|
||||
def assert_data(response):
|
||||
assert response.status_code == 200
|
||||
data = response.json().get("data")
|
||||
assert data is not None
|
||||
assert "api" in data.keys()
|
||||
return data["api"]
|
||||
|
||||
|
||||
API_DEVICES_QUERY = """
|
||||
devices {
|
||||
creationDate
|
||||
isCaller
|
||||
name
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def request_devices(client):
|
||||
return client.post(
|
||||
"/graphql",
|
||||
json={"query": generate_api_query([API_DEVICES_QUERY])},
|
||||
)
|
||||
|
||||
|
||||
def graphql_get_devices(client):
|
||||
response = request_devices(client)
|
||||
data = assert_data(response)
|
||||
devices = data["devices"]
|
||||
assert devices is not None
|
||||
return devices
|
||||
|
||||
|
||||
def set_client_token(client, token):
|
||||
client.headers.update({"Authorization": "Bearer " + token})
|
||||
|
||||
|
||||
def assert_token_valid(client, token):
|
||||
set_client_token(client, token)
|
||||
assert graphql_get_devices(client) is not None
|
||||
|
||||
|
||||
def assert_same(graphql_devices, abstract_devices):
|
||||
"""Orderless comparison"""
|
||||
assert len(graphql_devices) == len(abstract_devices)
|
||||
for original_device in abstract_devices:
|
||||
assert original_device["name"] in [device["name"] for device in graphql_devices]
|
||||
for device in graphql_devices:
|
||||
if device["name"] == original_device["name"]:
|
||||
assert device["creationDate"] == original_device["date"].isoformat()
|
||||
|
||||
|
||||
def assert_original(client):
|
||||
devices = graphql_get_devices(client)
|
||||
assert_original_devices(devices)
|
||||
|
||||
|
||||
def assert_original_devices(devices):
|
||||
assert_same(devices, ORIGINAL_DEVICES)
|
||||
|
||||
for device in devices:
|
||||
if device["name"] == DEVICE_WE_AUTH_TESTS_WITH["name"]:
|
||||
assert device["isCaller"] is True
|
||||
else:
|
||||
assert device["isCaller"] is False
|
|
@ -4,18 +4,20 @@ from tests.conftest import TOKENS_FILE_CONTENTS, DEVICE_WE_AUTH_TESTS_WITH
|
|||
ORIGINAL_DEVICES = TOKENS_FILE_CONTENTS["tokens"]
|
||||
|
||||
|
||||
def assert_ok(response, request):
|
||||
data = assert_data(response)
|
||||
data[request]["success"] is True
|
||||
data[request]["message"] is not None
|
||||
data[request]["code"] == 200
|
||||
def assert_ok(output: dict, code=200) -> None:
|
||||
if output["success"] is False:
|
||||
# convenience for debugging, this should display error
|
||||
# if message is empty, consider adding helpful messages
|
||||
raise ValueError(output["code"], output["message"])
|
||||
assert output["success"] is True
|
||||
assert output["message"] is not None
|
||||
assert output["code"] == code
|
||||
|
||||
|
||||
def assert_errorcode(response, request, code):
|
||||
data = assert_data(response)
|
||||
data[request]["success"] is False
|
||||
data[request]["message"] is not None
|
||||
data[request]["code"] == code
|
||||
def assert_errorcode(output: dict, code) -> None:
|
||||
assert output["success"] is False
|
||||
assert output["message"] is not None
|
||||
assert output["code"] == code
|
||||
|
||||
|
||||
def assert_empty(response):
|
||||
|
@ -23,9 +25,15 @@ def assert_empty(response):
|
|||
assert response.json().get("data") is None
|
||||
|
||||
|
||||
def assert_data(response):
|
||||
def get_data(response):
|
||||
assert response.status_code == 200
|
||||
data = response.json().get("data")
|
||||
response = response.json()
|
||||
|
||||
if (
|
||||
"errors" in response.keys()
|
||||
): # convenience for debugging, this will display error
|
||||
raise ValueError(response["errors"])
|
||||
data = response.get("data")
|
||||
assert data is not None
|
||||
return data
|
||||
|
||||
|
@ -48,7 +56,7 @@ def request_devices(client):
|
|||
|
||||
def graphql_get_devices(client):
|
||||
response = request_devices(client)
|
||||
data = assert_data(response)
|
||||
data = get_data(response)
|
||||
devices = data["api"]["devices"]
|
||||
assert devices is not None
|
||||
return devices
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from os import path
|
||||
from tests.test_graphql.test_backup import dummy_service, backups, raw_dummy_service
|
||||
from tests.test_backup import backups
|
||||
from tests.common import generate_backup_query
|
||||
|
||||
|
||||
|
@ -279,7 +279,7 @@ def get_data(response):
|
|||
if (
|
||||
"errors" in response.keys()
|
||||
): # convenience for debugging, this will display error
|
||||
assert response["errors"] == []
|
||||
raise ValueError(response["errors"])
|
||||
assert response["data"] is not None
|
||||
data = response["data"]
|
||||
return data
|
||||
|
@ -301,7 +301,7 @@ def test_dummy_service_convertible_to_gql(dummy_service):
|
|||
assert gql_service is not None
|
||||
|
||||
|
||||
def test_snapshots_empty(authorized_client, dummy_service):
|
||||
def test_snapshots_empty(authorized_client, dummy_service, backups):
|
||||
snaps = api_snapshots(authorized_client)
|
||||
assert snaps == []
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ from tests.common import (
|
|||
generate_api_query,
|
||||
)
|
||||
from tests.conftest import DEVICE_WE_AUTH_TESTS_WITH, TOKENS_FILE_CONTENTS
|
||||
from tests.test_graphql.api_common import (
|
||||
assert_data,
|
||||
from tests.test_graphql.common import (
|
||||
get_data,
|
||||
assert_empty,
|
||||
assert_ok,
|
||||
assert_errorcode,
|
||||
|
@ -36,7 +36,7 @@ def graphql_get_new_device_key(authorized_client) -> str:
|
|||
"/graphql",
|
||||
json={"query": NEW_DEVICE_KEY_MUTATION},
|
||||
)
|
||||
assert_ok(response, "getNewDeviceApiKey")
|
||||
assert_ok(get_data(response)["api"]["getNewDeviceApiKey"])
|
||||
|
||||
key = response.json()["data"]["api"]["getNewDeviceApiKey"]["key"]
|
||||
assert key.split(" ").__len__() == 12
|
||||
|
@ -60,9 +60,10 @@ def graphql_try_auth_new_device(client, mnemonic_key, device_name):
|
|||
|
||||
def graphql_authorize_new_device(client, mnemonic_key, device_name) -> str:
|
||||
response = graphql_try_auth_new_device(client, mnemonic_key, "new_device")
|
||||
assert_ok(response, "authorizeWithNewDeviceApiKey")
|
||||
assert_ok(get_data(response)["api"]["authorizeWithNewDeviceApiKey"])
|
||||
token = response.json()["data"]["api"]["authorizeWithNewDeviceApiKey"]["token"]
|
||||
assert_token_valid(client, token)
|
||||
return token
|
||||
|
||||
|
||||
def test_graphql_tokens_info(authorized_client, tokens_file):
|
||||
|
@ -114,7 +115,7 @@ def test_graphql_delete_token(authorized_client, tokens_file):
|
|||
},
|
||||
},
|
||||
)
|
||||
assert_ok(response, "deleteDeviceApiToken")
|
||||
assert_ok(get_data(response)["api"]["deleteDeviceApiToken"])
|
||||
|
||||
devices = graphql_get_devices(authorized_client)
|
||||
assert_same(devices, test_devices)
|
||||
|
@ -130,7 +131,7 @@ def test_graphql_delete_self_token(authorized_client, tokens_file):
|
|||
},
|
||||
},
|
||||
)
|
||||
assert_errorcode(response, "deleteDeviceApiToken", 400)
|
||||
assert_errorcode(get_data(response)["api"]["deleteDeviceApiToken"], 400)
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
|
@ -147,7 +148,7 @@ def test_graphql_delete_nonexistent_token(
|
|||
},
|
||||
},
|
||||
)
|
||||
assert_errorcode(response, "deleteDeviceApiToken", 404)
|
||||
assert_errorcode(get_data(response)["api"]["deleteDeviceApiToken"], 404)
|
||||
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
@ -180,7 +181,7 @@ def test_graphql_refresh_token(authorized_client, client, tokens_file):
|
|||
"/graphql",
|
||||
json={"query": REFRESH_TOKEN_MUTATION},
|
||||
)
|
||||
assert_ok(response, "refreshDeviceApiToken")
|
||||
assert_ok(get_data(response)["api"]["refreshDeviceApiToken"])
|
||||
|
||||
new_token = response.json()["data"]["api"]["refreshDeviceApiToken"]["token"]
|
||||
assert_token_valid(client, new_token)
|
||||
|
@ -250,10 +251,10 @@ def test_graphql_get_and_delete_new_device_key(client, authorized_client, tokens
|
|||
"/graphql",
|
||||
json={"query": INVALIDATE_NEW_DEVICE_KEY_MUTATION},
|
||||
)
|
||||
assert_ok(response, "invalidateNewDeviceApiKey")
|
||||
assert_ok(get_data(response)["api"]["invalidateNewDeviceApiKey"])
|
||||
|
||||
response = graphql_try_auth_new_device(client, mnemonic_key, "new_device")
|
||||
assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404)
|
||||
assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404)
|
||||
|
||||
|
||||
AUTHORIZE_WITH_NEW_DEVICE_KEY_MUTATION = """
|
||||
|
@ -285,7 +286,7 @@ def test_graphql_authorize_new_device_with_invalid_key(
|
|||
client, authorized_client, tokens_file
|
||||
):
|
||||
response = graphql_try_auth_new_device(client, "invalid_token", "new_device")
|
||||
assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404)
|
||||
assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404)
|
||||
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
@ -297,7 +298,7 @@ def test_graphql_get_and_authorize_used_key(client, authorized_client, tokens_fi
|
|||
devices = graphql_get_devices(authorized_client)
|
||||
|
||||
response = graphql_try_auth_new_device(client, mnemonic_key, "new_device2")
|
||||
assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404)
|
||||
assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404)
|
||||
|
||||
assert graphql_get_devices(authorized_client) == devices
|
||||
|
||||
|
@ -309,7 +310,7 @@ def test_graphql_get_and_authorize_key_after_12_minutes(
|
|||
mock = mocker.patch(DEVICE_KEY_VALIDATION_DATETIME, NearFuture)
|
||||
|
||||
response = graphql_try_auth_new_device(client, mnemonic_key, "new_device")
|
||||
assert_errorcode(response, "authorizeWithNewDeviceApiKey", 404)
|
||||
assert_errorcode(get_data(response)["api"]["authorizeWithNewDeviceApiKey"], 404)
|
||||
|
||||
|
||||
def test_graphql_authorize_without_token(
|
||||
|
|
|
@ -18,9 +18,9 @@ from tests.common import five_minutes_into_future_naive_utc as five_minutes_into
|
|||
from tests.common import five_minutes_into_future as five_minutes_into_future_tz
|
||||
from tests.common import five_minutes_into_past_naive_utc as five_minutes_into_past
|
||||
|
||||
from tests.test_graphql.api_common import (
|
||||
from tests.test_graphql.common import (
|
||||
assert_empty,
|
||||
assert_data,
|
||||
get_data,
|
||||
assert_ok,
|
||||
assert_errorcode,
|
||||
assert_token_valid,
|
||||
|
@ -49,9 +49,9 @@ def request_recovery_status(client):
|
|||
|
||||
def graphql_recovery_status(client):
|
||||
response = request_recovery_status(client)
|
||||
data = assert_data(response)
|
||||
data = get_data(response)
|
||||
|
||||
status = data["recoveryKey"]
|
||||
status = data["api"]["recoveryKey"]
|
||||
assert status is not None
|
||||
return status
|
||||
|
||||
|
@ -74,8 +74,10 @@ def request_make_new_recovery_key(client, expires_at=None, uses=None):
|
|||
|
||||
def graphql_make_new_recovery_key(client, expires_at=None, uses=None):
|
||||
response = request_make_new_recovery_key(client, expires_at, uses)
|
||||
assert_ok(response, "getNewRecoveryApiKey")
|
||||
key = response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"]
|
||||
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
||||
assert_ok(output)
|
||||
|
||||
key = output["key"]
|
||||
assert key is not None
|
||||
assert key.split(" ").__len__() == 18
|
||||
return key
|
||||
|
@ -98,8 +100,10 @@ def request_recovery_auth(client, key, device_name):
|
|||
|
||||
def graphql_use_recovery_key(client, key, device_name):
|
||||
response = request_recovery_auth(client, key, device_name)
|
||||
assert_ok(response, "useRecoveryApiKey")
|
||||
token = response.json()["data"]["api"]["useRecoveryApiKey"]["token"]
|
||||
output = get_data(response)["api"]["useRecoveryApiKey"]
|
||||
assert_ok(output)
|
||||
|
||||
token = output["token"]
|
||||
assert token is not None
|
||||
assert_token_valid(client, token)
|
||||
set_client_token(client, token)
|
||||
|
@ -198,8 +202,10 @@ def test_graphql_use_recovery_key_after_expiration(
|
|||
mock = mocker.patch(RECOVERY_KEY_VALIDATION_DATETIME, NearFuture)
|
||||
|
||||
response = request_recovery_auth(client, key, "new_test_token3")
|
||||
assert_errorcode(response, "useRecoveryApiKey", 404)
|
||||
assert response.json()["data"]["api"]["useRecoveryApiKey"]["token"] is None
|
||||
output = get_data(response)["api"]["useRecoveryApiKey"]
|
||||
assert_errorcode(output, 404)
|
||||
|
||||
assert output["token"] is None
|
||||
assert_original(authorized_client)
|
||||
|
||||
status = graphql_recovery_status(authorized_client)
|
||||
|
@ -222,8 +228,10 @@ def test_graphql_generate_recovery_key_with_expiration_in_the_past(
|
|||
authorized_client, expires_at=expiration_date
|
||||
)
|
||||
|
||||
assert_errorcode(response, "getNewRecoveryApiKey", 400)
|
||||
assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None
|
||||
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
||||
assert_errorcode(output, 400)
|
||||
|
||||
assert output["key"] is None
|
||||
assert graphql_recovery_status(authorized_client)["exists"] is False
|
||||
|
||||
|
||||
|
@ -280,7 +288,8 @@ def test_graphql_generate_recovery_key_with_limited_uses(
|
|||
assert status["usesLeft"] == 0
|
||||
|
||||
response = request_recovery_auth(client, mnemonic_key, "new_test_token3")
|
||||
assert_errorcode(response, "useRecoveryApiKey", 404)
|
||||
output = get_data(response)["api"]["useRecoveryApiKey"]
|
||||
assert_errorcode(output, 404)
|
||||
|
||||
|
||||
def test_graphql_generate_recovery_key_with_negative_uses(
|
||||
|
@ -288,13 +297,16 @@ def test_graphql_generate_recovery_key_with_negative_uses(
|
|||
):
|
||||
response = request_make_new_recovery_key(authorized_client, uses=-1)
|
||||
|
||||
assert_errorcode(response, "getNewRecoveryApiKey", 400)
|
||||
assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None
|
||||
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
||||
assert_errorcode(output, 400)
|
||||
assert output["key"] is None
|
||||
assert graphql_recovery_status(authorized_client)["exists"] is False
|
||||
|
||||
|
||||
def test_graphql_generate_recovery_key_with_zero_uses(authorized_client, tokens_file):
|
||||
response = request_make_new_recovery_key(authorized_client, uses=0)
|
||||
|
||||
assert_errorcode(response, "getNewRecoveryApiKey", 400)
|
||||
assert response.json()["data"]["api"]["getNewRecoveryApiKey"]["key"] is None
|
||||
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
||||
assert_errorcode(output, 400)
|
||||
assert output["key"] is None
|
||||
assert graphql_recovery_status(authorized_client)["exists"] is False
|
||||
|
|
|
@ -0,0 +1,517 @@
|
|||
import pytest
|
||||
from typing import Generator
|
||||
|
||||
from selfprivacy_api.utils.block_devices import BlockDevices
|
||||
|
||||
import selfprivacy_api.services as service_module
|
||||
from selfprivacy_api.services import get_service_by_id
|
||||
from selfprivacy_api.services.service import Service, ServiceStatus
|
||||
from selfprivacy_api.services.test_service import DummyService
|
||||
|
||||
from tests.common import generate_service_query
|
||||
from tests.test_graphql.common import assert_empty, assert_ok, get_data
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def only_dummy_service(dummy_service) -> Generator[DummyService, None, None]:
|
||||
# because queries to services that are not really there error out
|
||||
back_copy = service_module.services.copy()
|
||||
service_module.services.clear()
|
||||
service_module.services.append(dummy_service)
|
||||
yield dummy_service
|
||||
service_module.services.clear()
|
||||
service_module.services.extend(back_copy)
|
||||
|
||||
|
||||
API_START_MUTATION = """
|
||||
mutation TestStartService($service_id: String!) {
|
||||
services {
|
||||
startService(serviceId: $service_id) {
|
||||
success
|
||||
message
|
||||
code
|
||||
service {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
API_RESTART_MUTATION = """
|
||||
mutation TestRestartService($service_id: String!) {
|
||||
services {
|
||||
restartService(serviceId: $service_id) {
|
||||
success
|
||||
message
|
||||
code
|
||||
service {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
API_ENABLE_MUTATION = """
|
||||
mutation TestStartService($service_id: String!) {
|
||||
services {
|
||||
enableService(serviceId: $service_id) {
|
||||
success
|
||||
message
|
||||
code
|
||||
service {
|
||||
id
|
||||
isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
API_DISABLE_MUTATION = """
|
||||
mutation TestStartService($service_id: String!) {
|
||||
services {
|
||||
disableService(serviceId: $service_id) {
|
||||
success
|
||||
message
|
||||
code
|
||||
service {
|
||||
id
|
||||
isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
API_STOP_MUTATION = """
|
||||
mutation TestStopService($service_id: String!) {
|
||||
services {
|
||||
stopService(serviceId: $service_id) {
|
||||
success
|
||||
message
|
||||
code
|
||||
service {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
API_SERVICES_QUERY = """
|
||||
allServices {
|
||||
id
|
||||
status
|
||||
isEnabled
|
||||
}
|
||||
"""
|
||||
|
||||
API_MOVE_MUTATION = """
|
||||
mutation TestMoveService($input: MoveServiceInput!) {
|
||||
services {
|
||||
moveService(input: $input) {
|
||||
success
|
||||
message
|
||||
code
|
||||
job {
|
||||
uid
|
||||
status
|
||||
}
|
||||
service {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def assert_notfound(data):
|
||||
assert_errorcode(data, 404)
|
||||
|
||||
|
||||
def assert_errorcode(data, errorcode):
|
||||
assert data["code"] == errorcode
|
||||
assert data["success"] is False
|
||||
assert data["message"] is not None
|
||||
|
||||
|
||||
def api_enable(client, service: Service) -> dict:
|
||||
return api_enable_by_name(client, service.get_id())
|
||||
|
||||
|
||||
def api_enable_by_name(client, service_id: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_ENABLE_MUTATION,
|
||||
"variables": {"service_id": service_id},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_disable(client, service: Service) -> dict:
|
||||
return api_disable_by_name(client, service.get_id())
|
||||
|
||||
|
||||
def api_disable_by_name(client, service_id: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_DISABLE_MUTATION,
|
||||
"variables": {"service_id": service_id},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_start(client, service: Service) -> dict:
|
||||
return api_start_by_name(client, service.get_id())
|
||||
|
||||
|
||||
def api_start_by_name(client, service_id: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_START_MUTATION,
|
||||
"variables": {"service_id": service_id},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_move(client, service: Service, location: str) -> dict:
|
||||
return api_move_by_name(client, service.get_id(), location)
|
||||
|
||||
|
||||
def api_move_by_name(client, service_id: str, location: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_MOVE_MUTATION,
|
||||
"variables": {
|
||||
"input": {
|
||||
"serviceId": service_id,
|
||||
"location": location,
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_restart(client, service: Service) -> dict:
|
||||
return api_restart_by_name(client, service.get_id())
|
||||
|
||||
|
||||
def api_restart_by_name(client, service_id: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_RESTART_MUTATION,
|
||||
"variables": {"service_id": service_id},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_stop(client, service: Service) -> dict:
|
||||
return api_stop_by_name(client, service.get_id())
|
||||
|
||||
|
||||
def api_stop_by_name(client, service_id: str) -> dict:
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_STOP_MUTATION,
|
||||
"variables": {"service_id": service_id},
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def api_all_services(authorized_client):
|
||||
response = api_all_services_raw(authorized_client)
|
||||
data = get_data(response)
|
||||
result = data["services"]["allServices"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_all_services_raw(client):
|
||||
return client.post(
|
||||
"/graphql",
|
||||
json={"query": generate_service_query([API_SERVICES_QUERY])},
|
||||
)
|
||||
|
||||
|
||||
def api_service(authorized_client, service: Service):
|
||||
id = service.get_id()
|
||||
for _service in api_all_services(authorized_client):
|
||||
if _service["id"] == id:
|
||||
return _service
|
||||
|
||||
|
||||
def test_get_services(authorized_client, only_dummy_service):
|
||||
services = api_all_services(authorized_client)
|
||||
assert len(services) == 1
|
||||
|
||||
api_dummy_service = services[0]
|
||||
assert api_dummy_service["id"] == "testservice"
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
assert api_dummy_service["isEnabled"] is True
|
||||
|
||||
|
||||
def test_enable_return_value(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_enable(authorized_client, dummy_service)
|
||||
data = get_data(mutation_response)["services"]["enableService"]
|
||||
assert_ok(data)
|
||||
service = data["service"]
|
||||
assert service["id"] == dummy_service.get_id()
|
||||
assert service["isEnabled"] == True
|
||||
|
||||
|
||||
def test_disable_return_value(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_disable(authorized_client, dummy_service)
|
||||
data = get_data(mutation_response)["services"]["disableService"]
|
||||
assert_ok(data)
|
||||
service = data["service"]
|
||||
assert service["id"] == dummy_service.get_id()
|
||||
assert service["isEnabled"] == False
|
||||
|
||||
|
||||
def test_start_return_value(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_start(authorized_client, dummy_service)
|
||||
data = get_data(mutation_response)["services"]["startService"]
|
||||
assert_ok(data)
|
||||
service = data["service"]
|
||||
assert service["id"] == dummy_service.get_id()
|
||||
assert service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
|
||||
def test_restart(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
dummy_service.set_delay(0.3)
|
||||
mutation_response = api_restart(authorized_client, dummy_service)
|
||||
data = get_data(mutation_response)["services"]["restartService"]
|
||||
assert_ok(data)
|
||||
service = data["service"]
|
||||
assert service["id"] == dummy_service.get_id()
|
||||
assert service["status"] == ServiceStatus.RELOADING.value
|
||||
|
||||
|
||||
def test_stop_return_value(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_stop(authorized_client, dummy_service)
|
||||
data = get_data(mutation_response)["services"]["stopService"]
|
||||
assert_ok(data)
|
||||
service = data["service"]
|
||||
assert service["id"] == dummy_service.get_id()
|
||||
assert service["status"] == ServiceStatus.INACTIVE.value
|
||||
|
||||
|
||||
def test_allservices_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_all_services_raw(client)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json().get("data") is None
|
||||
|
||||
|
||||
def test_start_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_start(client, dummy_service)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_restart_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_restart(client, dummy_service)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_stop_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_stop(client, dummy_service)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_enable_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_enable(client, dummy_service)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_disable_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_disable(client, dummy_service)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_move_unauthorized(client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
response = api_move(client, dummy_service, "sda1")
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_start_nonexistent(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_start_by_name(authorized_client, "bogus_service")
|
||||
data = get_data(mutation_response)["services"]["startService"]
|
||||
assert_notfound(data)
|
||||
|
||||
assert data["service"] is None
|
||||
|
||||
|
||||
def test_restart_nonexistent(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_restart_by_name(authorized_client, "bogus_service")
|
||||
data = get_data(mutation_response)["services"]["restartService"]
|
||||
assert_notfound(data)
|
||||
|
||||
assert data["service"] is None
|
||||
|
||||
|
||||
def test_stop_nonexistent(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_stop_by_name(authorized_client, "bogus_service")
|
||||
data = get_data(mutation_response)["services"]["stopService"]
|
||||
assert_notfound(data)
|
||||
|
||||
assert data["service"] is None
|
||||
|
||||
|
||||
def test_enable_nonexistent(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_enable_by_name(authorized_client, "bogus_service")
|
||||
data = get_data(mutation_response)["services"]["enableService"]
|
||||
assert_notfound(data)
|
||||
|
||||
assert data["service"] is None
|
||||
|
||||
|
||||
def test_disable_nonexistent(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_disable_by_name(authorized_client, "bogus_service")
|
||||
data = get_data(mutation_response)["services"]["disableService"]
|
||||
assert_notfound(data)
|
||||
|
||||
assert data["service"] is None
|
||||
|
||||
|
||||
def test_stop_start(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
# attempting to start an already started service
|
||||
api_start(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
api_stop(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["status"] == ServiceStatus.INACTIVE.value
|
||||
|
||||
# attempting to stop an already stopped service
|
||||
api_stop(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["status"] == ServiceStatus.INACTIVE.value
|
||||
|
||||
api_start(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
|
||||
def test_disable_enable(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["isEnabled"] is True
|
||||
|
||||
# attempting to enable an already enableed service
|
||||
api_enable(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["isEnabled"] is True
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
api_disable(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["isEnabled"] is False
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
# attempting to disable an already disableped service
|
||||
api_disable(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["isEnabled"] is False
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
api_enable(authorized_client, dummy_service)
|
||||
api_dummy_service = api_all_services(authorized_client)[0]
|
||||
assert api_dummy_service["isEnabled"] is True
|
||||
assert api_dummy_service["status"] == ServiceStatus.ACTIVE.value
|
||||
|
||||
|
||||
def test_move_immovable(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
dummy_service.set_movable(False)
|
||||
mutation_response = api_move(authorized_client, dummy_service, "sda1")
|
||||
data = get_data(mutation_response)["services"]["moveService"]
|
||||
assert_errorcode(data, 400)
|
||||
|
||||
# is there a meaning in returning the service in this?
|
||||
assert data["service"] is not None
|
||||
assert data["job"] is None
|
||||
|
||||
|
||||
def test_move_no_such_volume(authorized_client, only_dummy_service):
|
||||
dummy_service = only_dummy_service
|
||||
mutation_response = api_move(authorized_client, dummy_service, "bogus_volume")
|
||||
data = get_data(mutation_response)["services"]["moveService"]
|
||||
assert_notfound(data)
|
||||
|
||||
# is there a meaning in returning the service in this?
|
||||
assert data["service"] is not None
|
||||
assert data["job"] is None
|
||||
|
||||
|
||||
def test_move_same_volume(authorized_client, dummy_service):
|
||||
# dummy_service = only_dummy_service
|
||||
|
||||
# we need a drive that actually exists
|
||||
root_volume = BlockDevices().get_root_block_device()
|
||||
dummy_service.set_simulated_moves(False)
|
||||
dummy_service.set_drive(root_volume.name)
|
||||
|
||||
mutation_response = api_move(authorized_client, dummy_service, root_volume.name)
|
||||
data = get_data(mutation_response)["services"]["moveService"]
|
||||
assert_errorcode(data, 400)
|
||||
|
||||
# is there a meaning in returning the service in this?
|
||||
assert data["service"] is not None
|
||||
assert data["job"] is not None
|
||||
|
||||
|
||||
def test_mailservice_cannot_enable_disable(authorized_client):
|
||||
mailservice = get_service_by_id("email")
|
||||
|
||||
mutation_response = api_enable(authorized_client, mailservice)
|
||||
data = get_data(mutation_response)["services"]["enableService"]
|
||||
assert_errorcode(data, 400)
|
||||
# TODO?: we cannot convert mailservice to graphql Service without /var/domain yet
|
||||
# assert data["service"] is not None
|
||||
|
||||
mutation_response = api_disable(authorized_client, mailservice)
|
||||
data = get_data(mutation_response)["services"]["disableService"]
|
||||
assert_errorcode(data, 400)
|
||||
# assert data["service"] is not None
|
|
@ -1,9 +1,25 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=unused-argument
|
||||
import pytest
|
||||
from typing import Optional
|
||||
|
||||
from tests.common import read_json
|
||||
from tests.test_graphql.common import assert_empty
|
||||
from selfprivacy_api.graphql.mutations.system_mutations import SystemMutations
|
||||
from selfprivacy_api.graphql.queries.system import System
|
||||
|
||||
# only allowed in fixtures and utils
|
||||
from selfprivacy_api.actions.ssh import remove_ssh_key, get_ssh_settings
|
||||
from selfprivacy_api.actions.users import get_users, UserDataUserOrigin
|
||||
|
||||
from tests.common import read_json, generate_system_query, generate_users_query
|
||||
from tests.test_graphql.common import (
|
||||
assert_empty,
|
||||
assert_ok,
|
||||
get_data,
|
||||
assert_errorcode,
|
||||
)
|
||||
from tests.test_graphql.test_users import API_USERS_INFO
|
||||
|
||||
key_users = ["root", "tester", "user1", "user2", "user3"]
|
||||
|
||||
|
||||
class ProcessMock:
|
||||
|
@ -13,7 +29,7 @@ class ProcessMock:
|
|||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def communicate(): # pylint: disable=no-method-argument
|
||||
def communicate(self): # pylint: disable=no-method-argument
|
||||
return (b"NEW_HASHED", None)
|
||||
|
||||
returncode = 0
|
||||
|
@ -40,7 +56,56 @@ def some_users(mocker, datadir):
|
|||
return datadir
|
||||
|
||||
|
||||
# TESTS ########################################################
|
||||
@pytest.fixture
|
||||
def no_rootkeys(generic_userdata):
|
||||
for rootkey in get_ssh_settings().rootKeys:
|
||||
remove_ssh_key("root", rootkey)
|
||||
assert get_ssh_settings().rootKeys == []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_keys(generic_userdata):
|
||||
# this removes root and admin keys too
|
||||
|
||||
users = get_users()
|
||||
for user in users:
|
||||
for key in user.ssh_keys:
|
||||
remove_ssh_key(user.username, key)
|
||||
users = get_users()
|
||||
for user in users:
|
||||
assert user.ssh_keys == []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_admin_key(generic_userdata, authorized_client):
|
||||
admin_keys = api_get_user_keys(authorized_client, admin_name())
|
||||
|
||||
for admin_key in admin_keys:
|
||||
remove_ssh_key(admin_name(), admin_key)
|
||||
|
||||
assert api_get_user_keys(authorized_client, admin_name()) == []
|
||||
|
||||
|
||||
def admin_name() -> Optional[str]:
|
||||
users = get_users()
|
||||
for user in users:
|
||||
if user.origin == UserDataUserOrigin.PRIMARY:
|
||||
return user.username
|
||||
return None
|
||||
|
||||
|
||||
def api_get_user_keys(authorized_client, user: str):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": generate_users_query([API_USERS_INFO]),
|
||||
},
|
||||
)
|
||||
data = get_data(response)["users"]["allUsers"]
|
||||
for _user in data:
|
||||
if _user["username"] == user:
|
||||
return _user["sshKeys"]
|
||||
return None
|
||||
|
||||
|
||||
API_CREATE_SSH_KEY_MUTATION = """
|
||||
|
@ -59,6 +124,250 @@ mutation addSshKey($sshInput: SshMutationInput!) {
|
|||
}
|
||||
"""
|
||||
|
||||
API_SET_SSH_SETTINGS = """
|
||||
mutation enableSsh($settings: SSHSettingsInput!) {
|
||||
system {
|
||||
changeSshSettings(settings: $settings) {
|
||||
success
|
||||
message
|
||||
code
|
||||
enable
|
||||
passwordAuthentication
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
API_SSH_SETTINGS_QUERY = """
|
||||
settings {
|
||||
ssh {
|
||||
enable
|
||||
passwordAuthentication
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
API_ROOTKEYS_QUERY = """
|
||||
settings {
|
||||
ssh {
|
||||
rootSshKeys
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def api_ssh_settings_raw(client):
|
||||
return client.post(
|
||||
"/graphql",
|
||||
json={"query": generate_system_query([API_SSH_SETTINGS_QUERY])},
|
||||
)
|
||||
|
||||
|
||||
def api_rootkeys_raw(client):
|
||||
return client.post(
|
||||
"/graphql",
|
||||
json={"query": generate_system_query([API_ROOTKEYS_QUERY])},
|
||||
)
|
||||
|
||||
|
||||
def api_add_ssh_key(authorized_client, user: str, key: str):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_CREATE_SSH_KEY_MUTATION,
|
||||
"variables": {
|
||||
"sshInput": {
|
||||
"username": user,
|
||||
"sshKey": key,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
data = get_data(response)
|
||||
result = data["users"]["addSshKey"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_remove_ssh_key(authorized_client, user: str, key: str):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_REMOVE_SSH_KEY_MUTATION,
|
||||
"variables": {
|
||||
"sshInput": {
|
||||
"username": user,
|
||||
"sshKey": key,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
data = get_data(response)
|
||||
result = data["users"]["removeSshKey"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_rootkeys(authorized_client):
|
||||
response = api_rootkeys_raw(authorized_client)
|
||||
data = get_data(response)
|
||||
result = data["system"]["settings"]["ssh"]["rootSshKeys"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_ssh_settings(authorized_client):
|
||||
response = api_ssh_settings_raw(authorized_client)
|
||||
data = get_data(response)
|
||||
result = data["system"]["settings"]["ssh"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_set_ssh_settings_dict(authorized_client, dict):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_SET_SSH_SETTINGS,
|
||||
"variables": {
|
||||
"settings": dict,
|
||||
},
|
||||
},
|
||||
)
|
||||
data = get_data(response)
|
||||
result = data["system"]["changeSshSettings"]
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
|
||||
def api_set_ssh_settings(authorized_client, enable: bool, password_auth: bool):
|
||||
return api_set_ssh_settings_dict(
|
||||
authorized_client,
|
||||
{
|
||||
"enable": enable,
|
||||
"passwordAuthentication": password_auth,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# TESTS ########################################################
|
||||
|
||||
|
||||
def test_graphql_ssh_query(authorized_client, some_users):
|
||||
settings = api_ssh_settings(authorized_client)
|
||||
assert settings["enable"] is True
|
||||
assert settings["passwordAuthentication"] is True
|
||||
|
||||
|
||||
def test_graphql_get_ssh_settings_unauthorized(client, some_users):
|
||||
response = api_ssh_settings_raw(client)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def test_graphql_change_ssh_settings_unauthorized(client, some_users):
|
||||
response = client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_SET_SSH_SETTINGS,
|
||||
"variables": {
|
||||
"sshInput": {
|
||||
"enable": True,
|
||||
"passwordAuthentication": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
def assert_includes(smaller_dict: dict, bigger_dict: dict):
|
||||
for item in smaller_dict.items():
|
||||
assert item in bigger_dict.items()
|
||||
|
||||
|
||||
available_settings = [
|
||||
{"enable": True, "passwordAuthentication": True},
|
||||
{"enable": True, "passwordAuthentication": False},
|
||||
{"enable": False, "passwordAuthentication": True},
|
||||
{"enable": False, "passwordAuthentication": False},
|
||||
]
|
||||
|
||||
|
||||
original_settings = [
|
||||
{"enable": True, "passwordAuthentication": True},
|
||||
{"enable": True, "passwordAuthentication": False},
|
||||
{"enable": False, "passwordAuthentication": True},
|
||||
{"enable": False, "passwordAuthentication": False},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("original_settings", original_settings)
|
||||
@pytest.mark.parametrize("settings", available_settings)
|
||||
def test_graphql_readwrite_ssh_settings(
|
||||
authorized_client, some_users, settings, original_settings
|
||||
):
|
||||
|
||||
# Userdata-related tests like undefined fields are in actions-level tests.
|
||||
output = api_set_ssh_settings_dict(authorized_client, original_settings)
|
||||
assert_includes(api_ssh_settings(authorized_client), output)
|
||||
|
||||
output = api_set_ssh_settings_dict(authorized_client, settings)
|
||||
assert_ok(output)
|
||||
assert_includes(settings, output)
|
||||
if "enable" not in settings.keys():
|
||||
assert output["enable"] == original_settings["enable"]
|
||||
assert_includes(api_ssh_settings(authorized_client), output)
|
||||
|
||||
|
||||
forbidden_settings = [
|
||||
# we include this here so that if the next version makes the fields
|
||||
# optional, the tests will remind the person that tests are to be extended accordingly
|
||||
{"enable": True},
|
||||
{"passwordAuthentication": True},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("original_settings", original_settings)
|
||||
@pytest.mark.parametrize("settings", forbidden_settings)
|
||||
def test_graphql_readwrite_ssh_settings_partial(
|
||||
authorized_client, some_users, settings, original_settings
|
||||
):
|
||||
|
||||
output = api_set_ssh_settings_dict(authorized_client, original_settings)
|
||||
with pytest.raises(Exception):
|
||||
output = api_set_ssh_settings_dict(authorized_client, settings)
|
||||
|
||||
|
||||
def test_graphql_disable_twice(authorized_client, some_users):
|
||||
output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False)
|
||||
assert_ok(output)
|
||||
assert output["enable"] is False
|
||||
assert output["passwordAuthentication"] is False
|
||||
|
||||
output = api_set_ssh_settings(authorized_client, enable=False, password_auth=False)
|
||||
assert_ok(output)
|
||||
assert output["enable"] is False
|
||||
assert output["passwordAuthentication"] is False
|
||||
|
||||
|
||||
def test_graphql_enable_twice(authorized_client, some_users):
|
||||
output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True)
|
||||
assert_ok(output)
|
||||
assert output["enable"] is True
|
||||
assert output["passwordAuthentication"] is True
|
||||
assert_includes(api_ssh_settings(authorized_client), output)
|
||||
|
||||
output = api_set_ssh_settings(authorized_client, enable=True, password_auth=True)
|
||||
assert_ok(output)
|
||||
assert output["enable"] is True
|
||||
assert output["passwordAuthentication"] is True
|
||||
assert_includes(api_ssh_settings(authorized_client), output)
|
||||
|
||||
|
||||
############## KEYS
|
||||
|
||||
|
||||
def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_popen):
|
||||
response = client.post(
|
||||
|
@ -76,106 +385,81 @@ def test_graphql_add_ssh_key_unauthorized(client, some_users, mock_subprocess_po
|
|||
assert_empty(response)
|
||||
|
||||
|
||||
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
|
||||
# Unauth getting of keys is tested in test_users.py because it is a part of users interface
|
||||
|
||||
assert response.json()["data"]["users"]["addSshKey"]["code"] == 201
|
||||
assert response.json()["data"]["users"]["addSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["addSshKey"]["success"] is True
|
||||
|
||||
assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "user1"
|
||||
assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [
|
||||
"ssh-rsa KEY user1@pc",
|
||||
def test_graphql_get_root_key(authorized_client, some_users):
|
||||
assert api_rootkeys(authorized_client) == ["ssh-ed25519 KEY test@pc"]
|
||||
|
||||
|
||||
def test_graphql_get_root_key_when_none(authorized_client, no_rootkeys):
|
||||
assert api_rootkeys(authorized_client) == []
|
||||
|
||||
|
||||
# Getting admin keys when they are present is tested in test_users.py
|
||||
|
||||
|
||||
def test_get_admin_key_when_none(authorized_client, no_admin_key):
|
||||
assert api_get_user_keys(authorized_client, admin_name()) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_add_ssh_key_when_none(authorized_client, no_keys, user):
|
||||
key1 = "ssh-rsa KEY test_key@pc"
|
||||
if user == "root":
|
||||
assert api_rootkeys(authorized_client) == []
|
||||
else:
|
||||
assert api_get_user_keys(authorized_client, user) == []
|
||||
|
||||
output = api_add_ssh_key(authorized_client, user, key1)
|
||||
|
||||
assert_ok(output, code=201)
|
||||
|
||||
assert output["user"]["username"] == user
|
||||
assert output["user"]["sshKeys"] == [key1]
|
||||
|
||||
if user == "root":
|
||||
assert api_rootkeys(authorized_client) == [key1]
|
||||
else:
|
||||
assert api_get_user_keys(authorized_client, user) == [key1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_add_ssh_key_one_more(authorized_client, no_keys, user):
|
||||
keys = [
|
||||
"ssh-rsa KEY test_key@pc",
|
||||
"ssh-rsa KEY2 test_key@pc",
|
||||
]
|
||||
output = api_add_ssh_key(authorized_client, user, keys[0])
|
||||
assert output["user"]["sshKeys"] == [keys[0]]
|
||||
|
||||
output = api_add_ssh_key(authorized_client, user, keys[1])
|
||||
|
||||
assert_ok(output, code=201)
|
||||
|
||||
assert output["user"]["username"] == user
|
||||
assert output["user"]["sshKeys"] == keys
|
||||
|
||||
if user == "root":
|
||||
assert api_rootkeys(authorized_client) == keys
|
||||
else:
|
||||
assert api_get_user_keys(authorized_client, user) == keys
|
||||
|
||||
|
||||
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
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_add_ssh_key_same(authorized_client, no_keys, user):
|
||||
key = "ssh-rsa KEY test_key@pc"
|
||||
output = api_add_ssh_key(authorized_client, user, key)
|
||||
assert output["user"]["sshKeys"] == [key]
|
||||
|
||||
assert response.json()["data"]["users"]["addSshKey"]["code"] == 201
|
||||
assert response.json()["data"]["users"]["addSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["addSshKey"]["success"] is True
|
||||
|
||||
assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "root"
|
||||
assert response.json()["data"]["users"]["addSshKey"]["user"]["sshKeys"] == [
|
||||
"ssh-ed25519 KEY test@pc",
|
||||
"ssh-rsa KEY test_key@pc",
|
||||
]
|
||||
output = api_add_ssh_key(authorized_client, user, key)
|
||||
assert_errorcode(output, 409)
|
||||
|
||||
|
||||
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"]["users"]["addSshKey"]["code"] == 201
|
||||
assert response.json()["data"]["users"]["addSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["addSshKey"]["success"] is True
|
||||
|
||||
assert response.json()["data"]["users"]["addSshKey"]["user"]["username"] == "tester"
|
||||
assert response.json()["data"]["users"]["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"]["users"]["addSshKey"]["code"] == 400
|
||||
assert response.json()["data"]["users"]["addSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["addSshKey"]["success"] is False
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_add_bad_ssh_key(authorized_client, some_users, user):
|
||||
output = api_add_ssh_key(authorized_client, user, "trust me, this is the ssh key")
|
||||
assert_errorcode(output, 400)
|
||||
|
||||
|
||||
def test_graphql_add_ssh_key_nonexistent_user(
|
||||
|
@ -234,129 +518,35 @@ def test_graphql_remove_ssh_key_unauthorized(client, some_users, mock_subprocess
|
|||
assert_empty(response)
|
||||
|
||||
|
||||
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
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_remove_ssh_key(authorized_client, no_keys, user):
|
||||
keys = [
|
||||
"ssh-rsa KEY test_key@pc",
|
||||
"ssh-rsa KEY2 test_key@pc",
|
||||
]
|
||||
output = api_add_ssh_key(authorized_client, user, keys[0])
|
||||
output = api_add_ssh_key(authorized_client, user, keys[1])
|
||||
assert output["user"]["sshKeys"] == keys
|
||||
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["code"] == 200
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["success"] is True
|
||||
output = api_remove_ssh_key(authorized_client, user, keys[1])
|
||||
assert_ok(output)
|
||||
assert output["user"]["username"] == user
|
||||
assert output["user"]["sshKeys"] == [keys[0]]
|
||||
|
||||
assert (
|
||||
response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "user1"
|
||||
)
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["user"]["sshKeys"] == []
|
||||
if user == "root":
|
||||
assert api_rootkeys(authorized_client) == [keys[0]]
|
||||
else:
|
||||
assert api_get_user_keys(authorized_client, user) == [keys[0]]
|
||||
|
||||
|
||||
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"]["users"]["removeSshKey"]["code"] == 200
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["success"] is True
|
||||
|
||||
assert (
|
||||
response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "root"
|
||||
)
|
||||
assert response.json()["data"]["users"]["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"]["users"]["removeSshKey"]["code"] == 200
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["success"] is True
|
||||
|
||||
assert (
|
||||
response.json()["data"]["users"]["removeSshKey"]["user"]["username"] == "tester"
|
||||
)
|
||||
assert response.json()["data"]["users"]["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"]["users"]["removeSshKey"]["code"] == 404
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["success"] is False
|
||||
@pytest.mark.parametrize("user", key_users)
|
||||
def test_graphql_remove_nonexistent_ssh_key(authorized_client, some_users, user):
|
||||
output = api_remove_ssh_key(authorized_client, user, "ssh-rsa nonexistent")
|
||||
assert_errorcode(output, 404)
|
||||
|
||||
|
||||
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"]["users"]["removeSshKey"]["code"] == 404
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["removeSshKey"]["success"] is False
|
||||
output = api_remove_ssh_key(authorized_client, "user666", "ssh-rsa KEY test_key@pc")
|
||||
assert_errorcode(output, 404)
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
|
||||
from tests.common import generate_system_query, read_json
|
||||
from tests.test_graphql.common import assert_empty
|
||||
from tests.test_dkim import no_dkim_file, dkim_file
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -332,6 +333,29 @@ def test_graphql_get_domain(
|
|||
)
|
||||
|
||||
|
||||
def test_graphql_get_domain_no_dkim(
|
||||
authorized_client,
|
||||
domain_file,
|
||||
mock_get_ip4,
|
||||
mock_get_ip6,
|
||||
no_dkim_file,
|
||||
turned_on,
|
||||
):
|
||||
"""Test no DKIM file situation gets properly handled"""
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": generate_system_query([API_GET_DOMAIN_INFO]),
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json().get("data") is not None
|
||||
dns_records = response.json()["data"]["system"]["domainInfo"]["requiredDnsRecords"]
|
||||
for record in dns_records:
|
||||
if record["name"] == "selector._domainkey":
|
||||
raise ValueError("unexpected record found:", record)
|
||||
|
||||
|
||||
API_GET_TIMEZONE = """
|
||||
settings {
|
||||
timezone
|
||||
|
|
|
@ -3,18 +3,10 @@
|
|||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": true
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
|
@ -24,17 +16,27 @@
|
|||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": true
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"jitsi": {
|
||||
"enable": true
|
||||
"modules": {
|
||||
"gitea": {
|
||||
"enable": true
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"jitsi": {
|
||||
"enable": true
|
||||
},
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
|
|
|
@ -23,15 +23,6 @@ class ProcessMock:
|
|||
returncode = 0
|
||||
|
||||
|
||||
class BrokenServiceMock(ProcessMock):
|
||||
"""Mock subprocess.Popen for broken service"""
|
||||
|
||||
def communicate(): # pylint: disable=no-method-argument
|
||||
return (b"Testing error", None)
|
||||
|
||||
returncode = 3
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subprocess_popen(mocker):
|
||||
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock)
|
||||
|
|
|
@ -6,7 +6,13 @@ from tests.common import (
|
|||
generate_users_query,
|
||||
read_json,
|
||||
)
|
||||
from tests.test_graphql.common import assert_empty
|
||||
from selfprivacy_api.utils import WriteUserData
|
||||
from tests.test_graphql.common import (
|
||||
assert_empty,
|
||||
assert_errorcode,
|
||||
assert_ok,
|
||||
get_data,
|
||||
)
|
||||
|
||||
invalid_usernames = [
|
||||
"messagebus",
|
||||
|
@ -89,6 +95,15 @@ def undefined_settings(mocker, datadir):
|
|||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_users_no_admin_nobody(undefined_settings):
|
||||
datadir = undefined_settings
|
||||
with WriteUserData() as data:
|
||||
del data["username"]
|
||||
del data["sshKeys"]
|
||||
return datadir
|
||||
|
||||
|
||||
class ProcessMock:
|
||||
"""Mock subprocess.Popen"""
|
||||
|
||||
|
@ -118,6 +133,17 @@ allUsers {
|
|||
"""
|
||||
|
||||
|
||||
def api_all_users(authorized_client):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": generate_users_query([API_USERS_INFO]),
|
||||
},
|
||||
)
|
||||
output = get_data(response)["users"]["allUsers"]
|
||||
return output
|
||||
|
||||
|
||||
def test_graphql_get_users_unauthorized(client, some_users, mock_subprocess_popen):
|
||||
"""Test wrong auth"""
|
||||
response = client.post(
|
||||
|
@ -170,6 +196,38 @@ def test_graphql_get_no_users(authorized_client, no_users, mock_subprocess_popen
|
|||
]
|
||||
|
||||
|
||||
def test_graphql_get_users_undefined_but_admin(authorized_client, undefined_settings):
|
||||
response = authorized_client.post(
|
||||
"/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"
|
||||
]
|
||||
|
||||
|
||||
def test_graphql_get_users_undefined_no_admin(
|
||||
authorized_client, no_users_no_admin_nobody
|
||||
):
|
||||
response = authorized_client.post(
|
||||
"/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"]) == 0
|
||||
|
||||
|
||||
API_GET_USERS = """
|
||||
query TestUsers($username: String!) {
|
||||
users {
|
||||
|
@ -216,6 +274,23 @@ def test_graphql_get_one_user(authorized_client, one_user, mock_subprocess_popen
|
|||
]
|
||||
|
||||
|
||||
def test_graphql_get_some_user_undefined(authorized_client, undefined_settings):
|
||||
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_GET_USERS,
|
||||
"variables": {
|
||||
"username": "user1",
|
||||
},
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json().get("data") is not None
|
||||
|
||||
assert response.json()["data"]["users"]["getUser"] is None
|
||||
|
||||
|
||||
def test_graphql_get_some_user(authorized_client, some_users, mock_subprocess_popen):
|
||||
response = authorized_client.post(
|
||||
"/graphql",
|
||||
|
@ -309,248 +384,135 @@ mutation createUser($user: UserMutationInput!) {
|
|||
"""
|
||||
|
||||
|
||||
def test_graphql_add_user_unauthorize(client, one_user, mock_subprocess_popen):
|
||||
response = client.post(
|
||||
def api_add_user_json(authorized_client, user_json: dict):
|
||||
# lowlevel for deeper testing of edgecases
|
||||
return authorized_client.post(
|
||||
"/graphql",
|
||||
json={
|
||||
"query": API_CREATE_USERS_MUTATION,
|
||||
"variables": {
|
||||
"user": {
|
||||
"username": "user2",
|
||||
"password": "12345678",
|
||||
},
|
||||
"user": user_json,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def api_add_user(authorized_client, username, password):
|
||||
response = api_add_user_json(
|
||||
authorized_client, {"username": username, "password": password}
|
||||
)
|
||||
output = get_data(response)["users"]["createUser"]
|
||||
return output
|
||||
|
||||
|
||||
def test_graphql_add_user_unauthorized(client, one_user, mock_subprocess_popen):
|
||||
response = api_add_user_json(client, {"username": "user2", "password": "12345678"})
|
||||
assert_empty(response)
|
||||
|
||||
|
||||
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
|
||||
output = api_add_user(authorized_client, "user2", password="12345678")
|
||||
assert_ok(output, code=201)
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 201
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is True
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user2"
|
||||
assert response.json()["data"]["users"]["createUser"]["user"]["sshKeys"] == []
|
||||
assert output["user"]["username"] == "user2"
|
||||
assert output["user"]["sshKeys"] == []
|
||||
|
||||
|
||||
def test_graphql_add_undefined_settings(
|
||||
def test_graphql_add_user_when_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
|
||||
output = api_add_user(authorized_client, "user2", password="12345678")
|
||||
assert_ok(output, code=201)
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 201
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is True
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user2"
|
||||
assert response.json()["data"]["users"]["createUser"]["user"]["sshKeys"] == []
|
||||
assert output["user"]["username"] == "user2"
|
||||
assert output["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"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 400
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"] is None
|
||||
users_witn_empty_fields = [
|
||||
{"username": "user2", "password": ""},
|
||||
{"username": "", "password": "12345678"},
|
||||
{"username": "", "password": ""},
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
@pytest.mark.parametrize("user_json", users_witn_empty_fields)
|
||||
def test_graphql_add_with_empty_fields(authorized_client, one_user, user_json):
|
||||
response = api_add_user_json(authorized_client, user_json)
|
||||
output = get_data(response)["users"]["createUser"]
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 400
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
assert_errorcode(output, 400)
|
||||
assert output["user"] is None
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"] is None
|
||||
|
||||
users_witn_undefined_fields = [
|
||||
{"username": "user2"},
|
||||
{"password": "12345678"},
|
||||
{},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user_json", users_witn_undefined_fields)
|
||||
def test_graphql_add_with_undefined_fields(authorized_client, one_user, user_json):
|
||||
# checking that all fields are mandatory
|
||||
response = api_add_user_json(authorized_client, user_json)
|
||||
|
||||
assert response.json()["errors"] is not None
|
||||
assert response.json()["errors"] != []
|
||||
|
||||
|
||||
@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
|
||||
output = api_add_user(authorized_client, username, password="12345678")
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 409
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"] is None
|
||||
assert_errorcode(output, code=409)
|
||||
assert output["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
|
||||
def test_graphql_add_existing_user(authorized_client, one_user):
|
||||
output = api_add_user(authorized_client, "user1", password="12345678")
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 409
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"]["username"] == "user1"
|
||||
assert (
|
||||
response.json()["data"]["users"]["createUser"]["user"]["sshKeys"][0]
|
||||
== "ssh-rsa KEY user1@pc"
|
||||
)
|
||||
assert_errorcode(output, code=409)
|
||||
assert output["user"]["username"] == "user1"
|
||||
assert output["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
|
||||
def test_graphql_add_main_user(authorized_client, one_user):
|
||||
output = api_add_user(authorized_client, "tester", password="12345678")
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 409
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
assert_errorcode(output, code=409)
|
||||
assert output["user"]["username"] == "tester"
|
||||
assert output["user"]["sshKeys"][0] == "ssh-rsa KEY test@pc"
|
||||
|
||||
assert (
|
||||
response.json()["data"]["users"]["createUser"]["user"]["username"] == "tester"
|
||||
)
|
||||
assert (
|
||||
response.json()["data"]["users"]["createUser"]["user"]["sshKeys"][0]
|
||||
== "ssh-rsa KEY test@pc"
|
||||
)
|
||||
|
||||
def test_graphql_add_user_when_no_admin_defined(
|
||||
authorized_client, no_users_no_admin_nobody
|
||||
):
|
||||
output = api_add_user(authorized_client, "tester", password="12345678")
|
||||
|
||||
assert_errorcode(output, code=400)
|
||||
assert output["user"] is None
|
||||
|
||||
|
||||
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.json().get("data") is not None
|
||||
output = api_add_user(authorized_client, "a" * 32, password="12345678")
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 400
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"] is None
|
||||
assert_errorcode(output, code=400)
|
||||
assert output["user"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "^-^"])
|
||||
# TODO: maybe make a username generating function to make a more comprehensive invalid username test
|
||||
@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
|
||||
output = api_add_user(authorized_client, username, password="12345678")
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["createUser"]["code"] == 400
|
||||
assert response.json()["data"]["users"]["createUser"]["success"] is False
|
||||
|
||||
assert response.json()["data"]["users"]["createUser"]["user"] is None
|
||||
assert_errorcode(output, code=400)
|
||||
assert output["user"] is None
|
||||
|
||||
|
||||
API_DELETE_USER_MUTATION = """
|
||||
|
@ -592,6 +554,11 @@ def test_graphql_delete_user(authorized_client, some_users, mock_subprocess_pope
|
|||
assert response.json()["data"]["users"]["deleteUser"]["message"] is not None
|
||||
assert response.json()["data"]["users"]["deleteUser"]["success"] is True
|
||||
|
||||
new_users = api_all_users(authorized_client)
|
||||
assert len(new_users) == 3
|
||||
usernames = [user["username"] for user in new_users]
|
||||
assert set(usernames) == set(["user2", "user3", "tester"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("username", ["", "def"])
|
||||
def test_graphql_delete_nonexistent_users(
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import pytest
|
||||
|
||||
from selfprivacy_api.migrations.modules_in_json import CreateModulesField
|
||||
from selfprivacy_api.utils import ReadUserData, WriteUserData
|
||||
from selfprivacy_api.services import get_all_services
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def stray_services(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "strays.json")
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def empty_json(generic_userdata):
|
||||
with WriteUserData() as data:
|
||||
data.clear()
|
||||
|
||||
with ReadUserData() as data:
|
||||
assert len(data.keys()) == 0
|
||||
|
||||
return
|
||||
|
||||
|
||||
def test_modules_empty_json(empty_json):
|
||||
with ReadUserData() as data:
|
||||
assert "modules" not in data.keys()
|
||||
|
||||
assert CreateModulesField().is_migration_needed()
|
||||
|
||||
CreateModulesField().migrate()
|
||||
assert not CreateModulesField().is_migration_needed()
|
||||
|
||||
with ReadUserData() as data:
|
||||
assert "modules" in data.keys()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("modules_field", [True, False])
|
||||
def test_modules_stray_services(modules_field, stray_services):
|
||||
if not modules_field:
|
||||
with WriteUserData() as data:
|
||||
del data["modules"]
|
||||
assert CreateModulesField().is_migration_needed()
|
||||
|
||||
CreateModulesField().migrate()
|
||||
|
||||
for service in get_all_services():
|
||||
# assumes we do not tolerate previous format
|
||||
assert service.is_enabled()
|
||||
if service.get_id() == "email":
|
||||
continue
|
||||
with ReadUserData() as data:
|
||||
assert service.get_id() in data["modules"].keys()
|
||||
assert service.get_id() not in data.keys()
|
||||
|
||||
assert not CreateModulesField().is_migration_needed()
|
||||
|
||||
|
||||
def test_modules_no_migration_on_generic_data(generic_userdata):
|
||||
assert not CreateModulesField().is_migration_needed()
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"bitwarden": {
|
||||
"enable": true
|
||||
},
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"gitea": {
|
||||
"enable": true
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"jitsi": {
|
||||
"enable": true
|
||||
},
|
||||
"modules": {}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "KG9ni-B-CMPk327Zv1qC7YBQaUGaBUcgdkvMvQ2atFI",
|
||||
"name": "primary_token",
|
||||
"date": "2022-07-15 17:41:31.675698"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"tokens": [
|
||||
{
|
||||
"token": "KG9ni-B-CMPk327Zv1qC7YBQaUGaBUcgdkvMvQ2atFI",
|
||||
"name": "primary_token",
|
||||
"date": "2022-07-15 17:41:31.675698"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{}
|
|
@ -1,125 +0,0 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bitwarden_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["bitwarden"]["enable"] == False
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bitwarden_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["bitwarden"]["enable"] == True
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bitwarden_enable_undefined(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json"
|
||||
)
|
||||
assert "enable" not in read_json(datadir / "enable_undefined.json")["bitwarden"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bitwarden_undefined(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "bitwarden" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_unauthorized(client, bitwarden_off, endpoint):
|
||||
response = client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_illegal_methods(authorized_client, bitwarden_off, endpoint):
|
||||
response = authorized_client.get(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_off(authorized_client, bitwarden_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(bitwarden_off / "turned_off.json") == read_json(
|
||||
bitwarden_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_on(authorized_client, bitwarden_on, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(bitwarden_on / "turned_on.json") == read_json(
|
||||
bitwarden_on / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_twice(authorized_client, bitwarden_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(bitwarden_off / "turned_off.json") == read_json(
|
||||
bitwarden_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_attribute_deleted(
|
||||
authorized_client, bitwarden_enable_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(bitwarden_enable_undefined / "enable_undefined.json") == read_json(
|
||||
bitwarden_enable_undefined / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_bitwarden_undefined(
|
||||
authorized_client, bitwarden_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/bitwarden/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(bitwarden_undefined / "undefined.json") == read_json(
|
||||
bitwarden_undefined / target_file
|
||||
)
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": true
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gitea_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["gitea"]["enable"] == False
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gitea_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["gitea"]["enable"] == True
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gitea_enable_undefined(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json"
|
||||
)
|
||||
assert "enable" not in read_json(datadir / "enable_undefined.json")["gitea"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gitea_undefined(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "gitea" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_unauthorized(client, gitea_off, endpoint):
|
||||
response = client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_illegal_methods(authorized_client, gitea_off, endpoint):
|
||||
response = authorized_client.get(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_off(authorized_client, gitea_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(gitea_off / "turned_off.json") == read_json(
|
||||
gitea_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_on(authorized_client, gitea_on, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(gitea_on / "turned_on.json") == read_json(gitea_on / target_file)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_twice(authorized_client, gitea_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(gitea_off / "turned_off.json") == read_json(
|
||||
gitea_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_attribute_deleted(
|
||||
authorized_client, gitea_enable_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(gitea_enable_undefined / "enable_undefined.json") == read_json(
|
||||
gitea_enable_undefined / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_gitea_undefined(authorized_client, gitea_undefined, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/gitea/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(gitea_undefined / "undefined.json") == read_json(
|
||||
gitea_undefined / target_file
|
||||
)
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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": {
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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": true
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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",
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
import base64
|
||||
import json
|
||||
import pytest
|
||||
|
||||
from selfprivacy_api.utils import get_dkim_key
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class ProcessMock:
|
||||
"""Mock subprocess.Popen"""
|
||||
|
||||
def __init__(self, args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def communicate():
|
||||
return (
|
||||
b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for example.com\n',
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
class NoFileMock(ProcessMock):
|
||||
def communicate():
|
||||
return (b"", None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subproccess_popen(mocker):
|
||||
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock)
|
||||
mocker.patch(
|
||||
"selfprivacy_api.rest.services.get_domain",
|
||||
autospec=True,
|
||||
return_value="example.com",
|
||||
)
|
||||
mocker.patch("os.path.exists", autospec=True, return_value=True)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_no_file(mocker):
|
||||
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=NoFileMock)
|
||||
mocker.patch(
|
||||
"selfprivacy_api.rest.services.get_domain",
|
||||
autospec=True,
|
||||
return_value="example.com",
|
||||
)
|
||||
mocker.patch("os.path.exists", autospec=True, return_value=False)
|
||||
return mock
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def test_unauthorized(client, mock_subproccess_popen):
|
||||
"""Test unauthorized"""
|
||||
response = client.get("/services/mailserver/dkim")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_illegal_methods(authorized_client, mock_subproccess_popen):
|
||||
response = authorized_client.post("/services/mailserver/dkim")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put("/services/mailserver/dkim")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete("/services/mailserver/dkim")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
def test_get_dkim_key(mock_subproccess_popen):
|
||||
"""Test DKIM key"""
|
||||
dkim_key = get_dkim_key("example.com")
|
||||
assert (
|
||||
dkim_key
|
||||
== "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB"
|
||||
)
|
||||
assert mock_subproccess_popen.call_args[0][0] == [
|
||||
"cat",
|
||||
"/var/dkim/example.com.selector.txt",
|
||||
]
|
||||
|
||||
|
||||
def test_dkim_key(authorized_client, mock_subproccess_popen):
|
||||
"""Test old REST DKIM key endpoint"""
|
||||
response = authorized_client.get("/services/mailserver/dkim")
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
base64.b64decode(response.text)
|
||||
== b'selector._domainkey\tIN\tTXT\t( "v=DKIM1; k=rsa; "\n\t "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNn/IhEz1SxgHxxxI8vlPYC2dNueiLe1GC4SYz8uHimC8SDkMvAwm7rqi2SimbFgGB5nccCNOqCkrIqJTCB9vufqBnVKAjshHqpOr5hk4JJ1T/AGQKWinstmDbfTLPYTbU8ijZrwwGeqQLlnXR5nSN0GB9GazheA9zaPsT6PV+aQIDAQAB" ) ; ----- DKIM key selector for example.com\n'
|
||||
)
|
||||
assert mock_subproccess_popen.call_args[0][0] == [
|
||||
"cat",
|
||||
"/var/dkim/example.com.selector.txt",
|
||||
]
|
||||
|
||||
|
||||
def test_no_dkim_key(authorized_client, mock_no_file):
|
||||
"""Test no DKIM key"""
|
||||
response = authorized_client.get("/services/mailserver/dkim")
|
||||
assert response.status_code == 404
|
||||
assert mock_no_file.called == False
|
|
@ -1,123 +0,0 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nextcloud_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["nextcloud"]["enable"] == False
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nextcloud_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["nextcloud"]["enable"] == True
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nextcloud_enable_undefined(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json"
|
||||
)
|
||||
assert "enable" not in read_json(datadir / "enable_undefined.json")["nextcloud"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nextcloud_undefined(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "nextcloud" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_unauthorized(client, nextcloud_off, endpoint):
|
||||
response = client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_illegal_methods(authorized_client, nextcloud_off, endpoint):
|
||||
response = authorized_client.get(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_off(authorized_client, nextcloud_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(nextcloud_off / "turned_off.json") == read_json(
|
||||
nextcloud_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_on(authorized_client, nextcloud_on, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(nextcloud_on / "turned_on.json") == read_json(
|
||||
nextcloud_on / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_twice(authorized_client, nextcloud_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(nextcloud_off / "turned_off.json") == read_json(
|
||||
nextcloud_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_attribute_deleted(
|
||||
authorized_client, nextcloud_enable_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(nextcloud_enable_undefined / "enable_undefined.json") == read_json(
|
||||
nextcloud_enable_undefined / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint,target", [("enable", True), ("disable", False)])
|
||||
def test_on_nextcloud_undefined(
|
||||
authorized_client, nextcloud_undefined, endpoint, target
|
||||
):
|
||||
response = authorized_client.post(f"/services/nextcloud/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
read_json(nextcloud_undefined / "undefined.json")["nextcloud"]["enable"]
|
||||
== target
|
||||
)
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN"
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ocserv_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["ocserv"]["enable"] == False
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ocserv_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["ocserv"]["enable"] == True
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ocserv_enable_undefined(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json"
|
||||
)
|
||||
assert "enable" not in read_json(datadir / "enable_undefined.json")["ocserv"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ocserv_undefined(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "ocserv" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_unauthorized(client, ocserv_off, endpoint):
|
||||
response = client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_illegal_methods(authorized_client, ocserv_off, endpoint):
|
||||
response = authorized_client.get(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_off(authorized_client, ocserv_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ocserv_off / "turned_off.json") == read_json(
|
||||
ocserv_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_on(authorized_client, ocserv_on, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ocserv_on / "turned_on.json") == read_json(ocserv_on / target_file)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_twice(authorized_client, ocserv_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ocserv_off / "turned_off.json") == read_json(
|
||||
ocserv_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_attribute_deleted(
|
||||
authorized_client, ocserv_enable_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ocserv_enable_undefined / "enable_undefined.json") == read_json(
|
||||
ocserv_enable_undefined / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_ocserv_undefined(
|
||||
authorized_client, ocserv_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/ocserv/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ocserv_undefined / "undefined.json") == read_json(
|
||||
ocserv_undefined / target_file
|
||||
)
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": false
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pleroma_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["pleroma"]["enable"] == False
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pleroma_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["pleroma"]["enable"] == True
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pleroma_enable_undefined(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "enable_undefined.json"
|
||||
)
|
||||
assert "enable" not in read_json(datadir / "enable_undefined.json")["pleroma"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pleroma_undefined(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "pleroma" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_unauthorized(client, pleroma_off, endpoint):
|
||||
response = client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["enable", "disable"])
|
||||
def test_illegal_methods(authorized_client, pleroma_off, endpoint):
|
||||
response = authorized_client.get(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_off(authorized_client, pleroma_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(pleroma_off / "turned_off.json") == read_json(
|
||||
pleroma_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_from_on(authorized_client, pleroma_on, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(pleroma_on / "turned_on.json") == read_json(
|
||||
pleroma_on / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_switch_twice(authorized_client, pleroma_off, endpoint, target_file):
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(pleroma_off / "turned_off.json") == read_json(
|
||||
pleroma_off / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_attribute_deleted(
|
||||
authorized_client, pleroma_enable_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(pleroma_enable_undefined / "enable_undefined.json") == read_json(
|
||||
pleroma_enable_undefined / target_file
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint,target_file",
|
||||
[("enable", "turned_on.json"), ("disable", "turned_off.json")],
|
||||
)
|
||||
def test_on_pleroma_undefined(
|
||||
authorized_client, pleroma_undefined, endpoint, target_file
|
||||
):
|
||||
response = authorized_client.post(f"/services/pleroma/{endpoint}")
|
||||
assert response.status_code == 200
|
||||
assert read_json(pleroma_undefined / "undefined.json") == read_json(
|
||||
pleroma_undefined / target_file
|
||||
)
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": false
|
||||
},
|
||||
"pleroma": {
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": false
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": false
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": false
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": false
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": true,
|
||||
"passwordAuthentication": true,
|
||||
"rootKeys": [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": false
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"sshKeys": [
|
||||
"ssh-rsa KEY test@pc"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "BUCKET"
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
import base64
|
||||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
def call_args_asserts(mocked_object):
|
||||
assert mocked_object.call_count == 7
|
||||
assert mocked_object.call_args_list[0][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"dovecot2.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[1][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"postfix.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[2][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"vaultwarden.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[3][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"gitea.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[4][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"phpfpm-nextcloud.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[5][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"ocserv.service",
|
||||
]
|
||||
assert mocked_object.call_args_list[6][0][0] == [
|
||||
"systemctl",
|
||||
"show",
|
||||
"pleroma.service",
|
||||
]
|
||||
|
||||
|
||||
SUCCESSFUL_STATUS = b"""
|
||||
Type=oneshot
|
||||
ExitType=main
|
||||
Restart=no
|
||||
NotifyAccess=none
|
||||
RestartUSec=100ms
|
||||
LoadState=loaded
|
||||
ActiveState=active
|
||||
FreezerState=running
|
||||
SubState=exited
|
||||
"""
|
||||
|
||||
FAILED_STATUS = b"""
|
||||
Type=oneshot
|
||||
ExitType=main
|
||||
Restart=no
|
||||
NotifyAccess=none
|
||||
RestartUSec=100ms
|
||||
LoadState=loaded
|
||||
ActiveState=failed
|
||||
FreezerState=running
|
||||
SubState=exited
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subproccess_popen(mocker):
|
||||
mock = mocker.patch(
|
||||
"subprocess.check_output", autospec=True, return_value=SUCCESSFUL_STATUS
|
||||
)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_broken_service(mocker):
|
||||
mock = mocker.patch(
|
||||
"subprocess.check_output", autospec=True, return_value=FAILED_STATUS
|
||||
)
|
||||
return mock
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
def test_unauthorized(client, mock_subproccess_popen):
|
||||
"""Test unauthorized"""
|
||||
response = client.get("/services/status")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_illegal_methods(authorized_client, mock_subproccess_popen):
|
||||
response = authorized_client.post("/services/status")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.put("/services/status")
|
||||
assert response.status_code == 405
|
||||
response = authorized_client.delete("/services/status")
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
def test_dkim_key(authorized_client, mock_subproccess_popen):
|
||||
response = authorized_client.get("/services/status")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"imap": 0,
|
||||
"smtp": 0,
|
||||
"http": 0,
|
||||
"bitwarden": 0,
|
||||
"gitea": 0,
|
||||
"nextcloud": 0,
|
||||
"ocserv": 0,
|
||||
"pleroma": 0,
|
||||
}
|
||||
call_args_asserts(mock_subproccess_popen)
|
||||
|
||||
|
||||
def test_no_dkim_key(authorized_client, mock_broken_service):
|
||||
response = authorized_client.get("/services/status")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"imap": 1,
|
||||
"smtp": 1,
|
||||
"http": 0,
|
||||
"bitwarden": 1,
|
||||
"gitea": 1,
|
||||
"nextcloud": 1,
|
||||
"ocserv": 1,
|
||||
"pleroma": 1,
|
||||
}
|
||||
call_args_asserts(mock_broken_service)
|
|
@ -1,521 +0,0 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
import pytest
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
## FIXTURES ###################################################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ssh_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert not read_json(datadir / "turned_off.json")["ssh"]["enable"]
|
||||
assert read_json(datadir / "turned_off.json")["ssh"]["passwordAuthentication"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ssh_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_off.json")["ssh"]["passwordAuthentication"]
|
||||
assert read_json(datadir / "turned_on.json")["ssh"]["enable"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def all_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "all_off.json")
|
||||
assert not read_json(datadir / "all_off.json")["ssh"]["passwordAuthentication"]
|
||||
assert not read_json(datadir / "all_off.json")["ssh"]["enable"]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def undefined_settings(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "ssh" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def undefined_values(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined_values.json"
|
||||
)
|
||||
assert "ssh" in read_json(datadir / "undefined_values.json")
|
||||
assert "enable" not in read_json(datadir / "undefined_values.json")["ssh"]
|
||||
assert (
|
||||
"passwordAuthentication"
|
||||
not in read_json(datadir / "undefined_values.json")["ssh"]
|
||||
)
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root_and_admin_have_keys(mocker, datadir):
|
||||
mocker.patch(
|
||||
"selfprivacy_api.utils.USERDATA_FILE",
|
||||
new=datadir / "root_and_admin_have_keys.json",
|
||||
)
|
||||
assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"]["enable"]
|
||||
assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"][
|
||||
"passwordAuthentication"
|
||||
]
|
||||
assert read_json(datadir / "root_and_admin_have_keys.json")["ssh"]["rootKeys"] == [
|
||||
"ssh-ed25519 KEY test@pc"
|
||||
]
|
||||
assert read_json(datadir / "root_and_admin_have_keys.json")["sshKeys"] == [
|
||||
"ssh-rsa KEY test@pc"
|
||||
]
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def some_users(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "some_users.json")
|
||||
assert "users" in read_json(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
|
||||
|
||||
|
||||
## TEST 401 ######################################################
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["ssh/enable", "ssh/keys/user"])
|
||||
def test_unauthorized(client, ssh_off, endpoint):
|
||||
response = client.post(f"/services/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["ssh", "ssh/key/send"])
|
||||
def test_unauthorized_put(client, ssh_off, endpoint):
|
||||
response = client.put(f"/services/{endpoint}")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
## TEST ENABLE ######################################################
|
||||
|
||||
|
||||
def test_legacy_enable(authorized_client, ssh_off):
|
||||
response = authorized_client.post("/services/ssh/enable")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ssh_off / "turned_off.json") == read_json(
|
||||
ssh_off / "turned_on.json"
|
||||
)
|
||||
|
||||
|
||||
def test_legacy_on_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.post("/services/ssh/enable")
|
||||
assert response.status_code == 200
|
||||
data = read_json(undefined_settings / "undefined.json")
|
||||
assert data["ssh"]["enable"] == True
|
||||
|
||||
|
||||
def test_legacy_enable_when_enabled(authorized_client, ssh_on):
|
||||
response = authorized_client.post("/services/ssh/enable")
|
||||
assert response.status_code == 200
|
||||
assert read_json(ssh_on / "turned_on.json") == read_json(ssh_on / "turned_on.json")
|
||||
|
||||
|
||||
## GET ON /ssh ######################################################
|
||||
|
||||
|
||||
def test_get_current_settings_ssh_off(authorized_client, ssh_off):
|
||||
response = authorized_client.get("/services/ssh")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"enable": False, "passwordAuthentication": True}
|
||||
|
||||
|
||||
def test_get_current_settings_ssh_on(authorized_client, ssh_on):
|
||||
response = authorized_client.get("/services/ssh")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"enable": True, "passwordAuthentication": True}
|
||||
|
||||
|
||||
def test_get_current_settings_all_off(authorized_client, all_off):
|
||||
response = authorized_client.get("/services/ssh")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"enable": False, "passwordAuthentication": False}
|
||||
|
||||
|
||||
def test_get_current_settings_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.get("/services/ssh")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"enable": True, "passwordAuthentication": True}
|
||||
|
||||
|
||||
def test_get_current_settings_mostly_undefined(authorized_client, undefined_values):
|
||||
response = authorized_client.get("/services/ssh")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"enable": True, "passwordAuthentication": True}
|
||||
|
||||
|
||||
## PUT ON /ssh ######################################################
|
||||
|
||||
available_settings = [
|
||||
{"enable": True, "passwordAuthentication": True},
|
||||
{"enable": True, "passwordAuthentication": False},
|
||||
{"enable": False, "passwordAuthentication": True},
|
||||
{"enable": False, "passwordAuthentication": False},
|
||||
{"enable": True},
|
||||
{"enable": False},
|
||||
{"passwordAuthentication": True},
|
||||
{"passwordAuthentication": False},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("settings", available_settings)
|
||||
def test_set_settings_ssh_off(authorized_client, ssh_off, settings):
|
||||
response = authorized_client.put("/services/ssh", json=settings)
|
||||
assert response.status_code == 200
|
||||
data = read_json(ssh_off / "turned_off.json")["ssh"]
|
||||
if "enable" in settings:
|
||||
assert data["enable"] == settings["enable"]
|
||||
if "passwordAuthentication" in settings:
|
||||
assert data["passwordAuthentication"] == settings["passwordAuthentication"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("settings", available_settings)
|
||||
def test_set_settings_ssh_on(authorized_client, ssh_on, settings):
|
||||
response = authorized_client.put("/services/ssh", json=settings)
|
||||
assert response.status_code == 200
|
||||
data = read_json(ssh_on / "turned_on.json")["ssh"]
|
||||
if "enable" in settings:
|
||||
assert data["enable"] == settings["enable"]
|
||||
if "passwordAuthentication" in settings:
|
||||
assert data["passwordAuthentication"] == settings["passwordAuthentication"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("settings", available_settings)
|
||||
def test_set_settings_all_off(authorized_client, all_off, settings):
|
||||
response = authorized_client.put("/services/ssh", json=settings)
|
||||
assert response.status_code == 200
|
||||
data = read_json(all_off / "all_off.json")["ssh"]
|
||||
if "enable" in settings:
|
||||
assert data["enable"] == settings["enable"]
|
||||
if "passwordAuthentication" in settings:
|
||||
assert data["passwordAuthentication"] == settings["passwordAuthentication"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("settings", available_settings)
|
||||
def test_set_settings_undefined(authorized_client, undefined_settings, settings):
|
||||
response = authorized_client.put("/services/ssh", json=settings)
|
||||
assert response.status_code == 200
|
||||
data = read_json(undefined_settings / "undefined.json")["ssh"]
|
||||
if "enable" in settings:
|
||||
assert data["enable"] == settings["enable"]
|
||||
if "passwordAuthentication" in settings:
|
||||
assert data["passwordAuthentication"] == settings["passwordAuthentication"]
|
||||
|
||||
|
||||
## PUT ON /ssh/key/send ######################################################
|
||||
|
||||
|
||||
def test_add_root_key(authorized_client, ssh_on):
|
||||
response = authorized_client.put(
|
||||
"/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert read_json(ssh_on / "turned_on.json")["ssh"]["rootKeys"] == [
|
||||
"ssh-rsa KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_root_key_on_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.put(
|
||||
"/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = read_json(undefined_settings / "undefined.json")
|
||||
assert data["ssh"]["rootKeys"] == ["ssh-rsa KEY test@pc"]
|
||||
|
||||
|
||||
def test_add_root_key_one_more(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.put(
|
||||
"/services/ssh/key/send", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][
|
||||
"rootKeys"
|
||||
] == [
|
||||
"ssh-ed25519 KEY test@pc",
|
||||
"ssh-rsa KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_existing_root_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.put(
|
||||
"/services/ssh/key/send", json={"public_key": "ssh-ed25519 KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][
|
||||
"rootKeys"
|
||||
] == [
|
||||
"ssh-ed25519 KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_invalid_root_key(authorized_client, ssh_on):
|
||||
response = authorized_client.put(
|
||||
"/services/ssh/key/send", json={"public_key": "INVALID KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
## /ssh/keys/{user} ######################################################
|
||||
|
||||
|
||||
def test_get_root_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.get("/services/ssh/keys/root")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == ["ssh-ed25519 KEY test@pc"]
|
||||
|
||||
|
||||
def test_get_root_key_when_none(authorized_client, ssh_on):
|
||||
response = authorized_client.get("/services/ssh/keys/root")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
|
||||
def test_get_root_key_on_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.get("/services/ssh/keys/root")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
|
||||
def test_delete_root_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
"rootKeys"
|
||||
not in read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[
|
||||
"ssh"
|
||||
]
|
||||
or read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][
|
||||
"rootKeys"
|
||||
]
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_delete_root_nonexistent_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/root", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["ssh"][
|
||||
"rootKeys"
|
||||
] == [
|
||||
"ssh-ed25519 KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_delete_root_key_on_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/root", json={"public_key": "ssh-ed25519 KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert "ssh" not in read_json(undefined_settings / "undefined.json")
|
||||
|
||||
|
||||
def test_get_admin_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.get("/services/ssh/keys/tester")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == ["ssh-rsa KEY test@pc"]
|
||||
|
||||
|
||||
def test_get_admin_key_when_none(authorized_client, ssh_on):
|
||||
response = authorized_client.get("/services/ssh/keys/tester")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
|
||||
def test_delete_admin_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")["sshKeys"]
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_delete_nonexistent_admin_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa NO KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[
|
||||
"sshKeys"
|
||||
] == ["ssh-rsa KEY test@pc"]
|
||||
|
||||
|
||||
def test_delete_admin_key_on_undefined(authorized_client, undefined_settings):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert "sshKeys" not in read_json(undefined_settings / "undefined.json")
|
||||
|
||||
|
||||
def test_add_admin_key(authorized_client, ssh_on):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert read_json(ssh_on / "turned_on.json")["sshKeys"] == [
|
||||
"ssh-rsa KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_admin_key_one_more(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY_2 test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[
|
||||
"sshKeys"
|
||||
] == ["ssh-rsa KEY test@pc", "ssh-rsa KEY_2 test@pc"]
|
||||
|
||||
|
||||
def test_add_existing_admin_key(authorized_client, root_and_admin_have_keys):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/tester", json={"public_key": "ssh-rsa KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert read_json(root_and_admin_have_keys / "root_and_admin_have_keys.json")[
|
||||
"sshKeys"
|
||||
] == [
|
||||
"ssh-rsa KEY test@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_invalid_admin_key(authorized_client, ssh_on):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/tester", json={"public_key": "INVALID KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", [1, 2, 3])
|
||||
def test_get_user_key(authorized_client, some_users, user):
|
||||
response = authorized_client.get(f"/services/ssh/keys/user{user}")
|
||||
assert response.status_code == 200
|
||||
if user == 1:
|
||||
assert response.json() == ["ssh-rsa KEY user1@pc"]
|
||||
else:
|
||||
assert response.json() == []
|
||||
|
||||
|
||||
def test_get_keys_of_nonexistent_user(authorized_client, some_users):
|
||||
response = authorized_client.get("/services/ssh/keys/user4")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_get_keys_of_undefined_users(authorized_client, undefined_settings):
|
||||
response = authorized_client.get("/services/ssh/keys/user1")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", [1, 2, 3])
|
||||
def test_add_user_key(authorized_client, some_users, user):
|
||||
response = authorized_client.post(
|
||||
f"/services/ssh/keys/user{user}", json={"public_key": "ssh-ed25519 KEY test@pc"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
if user == 1:
|
||||
assert read_json(some_users / "some_users.json")["users"][user - 1][
|
||||
"sshKeys"
|
||||
] == [
|
||||
"ssh-rsa KEY user1@pc",
|
||||
"ssh-ed25519 KEY test@pc",
|
||||
]
|
||||
else:
|
||||
assert read_json(some_users / "some_users.json")["users"][user - 1][
|
||||
"sshKeys"
|
||||
] == ["ssh-ed25519 KEY test@pc"]
|
||||
|
||||
|
||||
def test_add_existing_user_key(authorized_client, some_users):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"}
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert read_json(some_users / "some_users.json")["users"][0]["sshKeys"] == [
|
||||
"ssh-rsa KEY user1@pc",
|
||||
]
|
||||
|
||||
|
||||
def test_add_invalid_user_key(authorized_client, some_users):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/user1", json={"public_key": "INVALID KEY user1@pc"}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_delete_user_key(authorized_client, some_users):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(some_users / "some_users.json")["users"][0]["sshKeys"] == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", [2, 3])
|
||||
def test_delete_nonexistent_user_key(authorized_client, some_users, user):
|
||||
response = authorized_client.delete(
|
||||
f"/services/ssh/keys/user{user}", json={"public_key": "ssh-rsa KEY user1@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
if user == 2:
|
||||
assert (
|
||||
read_json(some_users / "some_users.json")["users"][user - 1]["sshKeys"]
|
||||
== []
|
||||
)
|
||||
if user == 3:
|
||||
"sshKeys" not in read_json(some_users / "some_users.json")["users"][user - 1]
|
||||
|
||||
|
||||
def test_add_keys_of_nonexistent_user(authorized_client, some_users):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/user4", json={"public_key": "ssh-rsa KEY user4@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_add_key_on_undefined_users(authorized_client, undefined_settings):
|
||||
response = authorized_client.post(
|
||||
"/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user4@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_delete_keys_of_nonexistent_user(authorized_client, some_users):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/user4", json={"public_key": "ssh-rsa KEY user4@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_delete_key_when_undefined_users(authorized_client, undefined_settings):
|
||||
response = authorized_client.delete(
|
||||
"/services/ssh/keys/user1", json={"public_key": "ssh-rsa KEY user1@pc"}
|
||||
)
|
||||
assert response.status_code == 404
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": false,
|
||||
"passwordAuthentication": false,
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {
|
||||
"enable": false,
|
||||
"passwordAuthentication": true
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"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
|
||||
},
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"username": "tester",
|
||||
"gitea": {
|
||||
"enable": false
|
||||
},
|
||||
"ocserv": {
|
||||
"enable": true
|
||||
},
|
||||
"pleroma": {
|
||||
"enable": true
|
||||
},
|
||||
"autoUpgrade": {
|
||||
"enable": true,
|
||||
"allowReboot": true
|
||||
},
|
||||
"timezone": "Europe/Moscow",
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"api": {
|
||||
"token": "TEST_TOKEN",
|
||||
"enableSwagger": false
|
||||
},
|
||||
"bitwarden": {
|
||||
"enable": false
|
||||
},
|
||||
"databasePassword": "PASSWORD",
|
||||
"domain": "test.tld",
|
||||
"hashedMasterPassword": "HASHED_PASSWORD",
|
||||
"hostname": "test-instance",
|
||||
"nextcloud": {
|
||||
"adminPassword": "ADMIN",
|
||||
"databasePassword": "ADMIN",
|
||||
"enable": true
|
||||
},
|
||||
"resticPassword": "PASS",
|
||||
"ssh": {},
|
||||
"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"
|
||||
],
|
||||
"dns": {
|
||||
"provider": "CLOUDFLARE",
|
||||
"apiKey": "TOKEN"
|
||||
},
|
||||
"server": {
|
||||
"provider": "HETZNER"
|
||||
},
|
||||
"backup": {
|
||||
"provider": "BACKBLAZE",
|
||||
"accountId": "ID",
|
||||
"accountKey": "KEY",
|
||||
"bucket": "selfprivacy"
|
||||
}
|
||||
}
|
|
@ -1,457 +0,0 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=missing-function-docstring
|
||||
import datetime
|
||||
from datetime import timezone
|
||||
import pytest
|
||||
|
||||
from tests.conftest import TOKENS_FILE_CONTENTS
|
||||
from tests.common import (
|
||||
RECOVERY_KEY_VALIDATION_DATETIME,
|
||||
DEVICE_KEY_VALIDATION_DATETIME,
|
||||
NearFuture,
|
||||
assert_recovery_recent,
|
||||
)
|
||||
from tests.common import five_minutes_into_future_naive_utc as five_minutes_into_future
|
||||
from tests.common import five_minutes_into_past_naive_utc as five_minutes_into_past
|
||||
|
||||
DATE_FORMATS = [
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S.%fZ",
|
||||
"%Y-%m-%d %H:%M:%S.%f",
|
||||
]
|
||||
|
||||
|
||||
def assert_original(client):
|
||||
new_tokens = rest_get_tokens_info(client)
|
||||
|
||||
for token in TOKENS_FILE_CONTENTS["tokens"]:
|
||||
assert_token_valid(client, token["token"])
|
||||
for new_token in new_tokens:
|
||||
if new_token["name"] == token["name"]:
|
||||
assert (
|
||||
datetime.datetime.fromisoformat(new_token["date"]) == token["date"]
|
||||
)
|
||||
assert_no_recovery(client)
|
||||
|
||||
|
||||
def assert_token_valid(client, token):
|
||||
client.headers.update({"Authorization": "Bearer " + token})
|
||||
assert rest_get_tokens_info(client) is not None
|
||||
|
||||
|
||||
def rest_get_tokens_info(client):
|
||||
response = client.get("/auth/tokens")
|
||||
assert response.status_code == 200
|
||||
return response.json()
|
||||
|
||||
|
||||
def rest_try_authorize_new_device(client, token, device_name):
|
||||
response = client.post(
|
||||
"/auth/new_device/authorize",
|
||||
json={
|
||||
"token": token,
|
||||
"device": device_name,
|
||||
},
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def rest_make_recovery_token(client, expires_at=None, timeformat=None, uses=None):
|
||||
json = {}
|
||||
|
||||
if expires_at is not None:
|
||||
assert timeformat is not None
|
||||
expires_at_str = expires_at.strftime(timeformat)
|
||||
json["expiration"] = expires_at_str
|
||||
|
||||
if uses is not None:
|
||||
json["uses"] = uses
|
||||
|
||||
if json == {}:
|
||||
response = client.post("/auth/recovery_token")
|
||||
else:
|
||||
response = client.post(
|
||||
"/auth/recovery_token",
|
||||
json=json,
|
||||
)
|
||||
|
||||
if not response.status_code == 200:
|
||||
raise ValueError(response.reason, response.text, response.json()["detail"])
|
||||
assert response.status_code == 200
|
||||
assert "token" in response.json()
|
||||
return response.json()["token"]
|
||||
|
||||
|
||||
def rest_get_recovery_status(client):
|
||||
response = client.get("/auth/recovery_token")
|
||||
assert response.status_code == 200
|
||||
return response.json()
|
||||
|
||||
|
||||
def rest_get_recovery_date(client):
|
||||
status = rest_get_recovery_status(client)
|
||||
assert "date" in status
|
||||
return status["date"]
|
||||
|
||||
|
||||
def assert_no_recovery(client):
|
||||
assert not rest_get_recovery_status(client)["exists"]
|
||||
|
||||
|
||||
def rest_recover_with_mnemonic(client, mnemonic_token, device_name):
|
||||
recovery_response = client.post(
|
||||
"/auth/recovery_token/use",
|
||||
json={"token": mnemonic_token, "device": device_name},
|
||||
)
|
||||
assert recovery_response.status_code == 200
|
||||
new_token = recovery_response.json()["token"]
|
||||
assert_token_valid(client, new_token)
|
||||
return new_token
|
||||
|
||||
|
||||
# Tokens
|
||||
|
||||
|
||||
def test_get_tokens_info(authorized_client, tokens_file):
|
||||
assert sorted(rest_get_tokens_info(authorized_client), key=lambda x: x["name"]) == [
|
||||
{"name": "test_token", "date": "2022-01-14T08:31:10.789314", "is_caller": True},
|
||||
{
|
||||
"name": "test_token2",
|
||||
"date": "2022-01-14T08:31:10.789314",
|
||||
"is_caller": False,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_get_tokens_unauthorized(client, tokens_file):
|
||||
response = client.get("/auth/tokens")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_delete_token_unauthorized(client, authorized_client, tokens_file):
|
||||
response = client.delete("/auth/tokens")
|
||||
assert response.status_code == 401
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_delete_token(authorized_client, tokens_file):
|
||||
response = authorized_client.delete(
|
||||
"/auth/tokens", json={"token_name": "test_token2"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert rest_get_tokens_info(authorized_client) == [
|
||||
{"name": "test_token", "date": "2022-01-14T08:31:10.789314", "is_caller": True}
|
||||
]
|
||||
|
||||
|
||||
def test_delete_self_token(authorized_client, tokens_file):
|
||||
response = authorized_client.delete(
|
||||
"/auth/tokens", json={"token_name": "test_token"}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_delete_nonexistent_token(authorized_client, tokens_file):
|
||||
response = authorized_client.delete(
|
||||
"/auth/tokens", json={"token_name": "test_token3"}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_refresh_token_unauthorized(client, authorized_client, tokens_file):
|
||||
response = client.post("/auth/tokens")
|
||||
assert response.status_code == 401
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_refresh_token(authorized_client, tokens_file):
|
||||
response = authorized_client.post("/auth/tokens")
|
||||
assert response.status_code == 200
|
||||
new_token = response.json()["token"]
|
||||
assert_token_valid(authorized_client, new_token)
|
||||
|
||||
|
||||
# New device
|
||||
|
||||
|
||||
def test_get_new_device_auth_token_unauthorized(client, authorized_client, tokens_file):
|
||||
response = client.post("/auth/new_device")
|
||||
assert response.status_code == 401
|
||||
assert "token" not in response.json()
|
||||
assert "detail" in response.json()
|
||||
# We only can check existence of a token we know.
|
||||
|
||||
|
||||
def test_get_and_delete_new_device_token(client, authorized_client, tokens_file):
|
||||
token = rest_get_new_device_token(authorized_client)
|
||||
response = authorized_client.delete("/auth/new_device", json={"token": token})
|
||||
assert response.status_code == 200
|
||||
assert rest_try_authorize_new_device(client, token, "new_device").status_code == 404
|
||||
|
||||
|
||||
def test_delete_token_unauthenticated(client, authorized_client, tokens_file):
|
||||
token = rest_get_new_device_token(authorized_client)
|
||||
response = client.delete("/auth/new_device", json={"token": token})
|
||||
assert response.status_code == 401
|
||||
assert rest_try_authorize_new_device(client, token, "new_device").status_code == 200
|
||||
|
||||
|
||||
def rest_get_new_device_token(client):
|
||||
response = client.post("/auth/new_device")
|
||||
assert response.status_code == 200
|
||||
assert "token" in response.json()
|
||||
return response.json()["token"]
|
||||
|
||||
|
||||
def test_get_and_authorize_new_device(client, authorized_client, tokens_file):
|
||||
token = rest_get_new_device_token(authorized_client)
|
||||
response = rest_try_authorize_new_device(client, token, "new_device")
|
||||
assert response.status_code == 200
|
||||
assert_token_valid(authorized_client, response.json()["token"])
|
||||
|
||||
|
||||
def test_authorize_new_device_with_invalid_token(
|
||||
client, authorized_client, tokens_file
|
||||
):
|
||||
response = rest_try_authorize_new_device(client, "invalid_token", "new_device")
|
||||
assert response.status_code == 404
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_get_and_authorize_used_token(client, authorized_client, tokens_file):
|
||||
token_to_be_used_2_times = rest_get_new_device_token(authorized_client)
|
||||
response = rest_try_authorize_new_device(
|
||||
client, token_to_be_used_2_times, "new_device"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert_token_valid(authorized_client, response.json()["token"])
|
||||
|
||||
response = rest_try_authorize_new_device(
|
||||
client, token_to_be_used_2_times, "new_device"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_get_and_authorize_token_after_12_minutes(
|
||||
client, authorized_client, tokens_file, mocker
|
||||
):
|
||||
token = rest_get_new_device_token(authorized_client)
|
||||
|
||||
# TARDIS sounds
|
||||
mock = mocker.patch(DEVICE_KEY_VALIDATION_DATETIME, NearFuture)
|
||||
|
||||
response = rest_try_authorize_new_device(client, token, "new_device")
|
||||
assert response.status_code == 404
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_authorize_without_token(client, authorized_client, tokens_file):
|
||||
response = client.post(
|
||||
"/auth/new_device/authorize",
|
||||
json={"device": "new_device"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
# Recovery tokens
|
||||
# GET /auth/recovery_token returns token status
|
||||
# - if token is valid, returns 200 and token status
|
||||
# - token status:
|
||||
# - exists (boolean)
|
||||
# - valid (boolean)
|
||||
# - date (string)
|
||||
# - expiration (string)
|
||||
# - uses_left (int)
|
||||
# - if token is invalid, returns 400 and empty body
|
||||
# POST /auth/recovery_token generates a new token
|
||||
# has two optional parameters:
|
||||
# - expiration (string in datetime format)
|
||||
# - uses_left (int)
|
||||
# POST /auth/recovery_token/use uses the token
|
||||
# required arguments:
|
||||
# - token (string)
|
||||
# - device (string)
|
||||
# - if token is valid, returns 200 and token
|
||||
# - if token is invalid, returns 404
|
||||
# - if request is invalid, returns 400
|
||||
|
||||
|
||||
def test_get_recovery_token_status_unauthorized(client, authorized_client, tokens_file):
|
||||
response = client.get("/auth/recovery_token")
|
||||
assert response.status_code == 401
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_get_recovery_token_when_none_exists(authorized_client, tokens_file):
|
||||
response = authorized_client.get("/auth/recovery_token")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"exists": False,
|
||||
"valid": False,
|
||||
"date": None,
|
||||
"expiration": None,
|
||||
"uses_left": None,
|
||||
}
|
||||
assert_original(authorized_client)
|
||||
|
||||
|
||||
def test_generate_recovery_token(authorized_client, client, tokens_file):
|
||||
# Generate token without expiration and uses_left
|
||||
mnemonic_token = rest_make_recovery_token(authorized_client)
|
||||
|
||||
time_generated = rest_get_recovery_date(authorized_client)
|
||||
assert_recovery_recent(time_generated)
|
||||
|
||||
assert rest_get_recovery_status(authorized_client) == {
|
||||
"exists": True,
|
||||
"valid": True,
|
||||
"date": time_generated,
|
||||
"expiration": None,
|
||||
"uses_left": None,
|
||||
}
|
||||
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device")
|
||||
# And again
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timeformat", DATE_FORMATS)
|
||||
def test_generate_recovery_token_with_expiration_date(
|
||||
authorized_client, client, tokens_file, timeformat, mocker
|
||||
):
|
||||
# Generate token with expiration date
|
||||
# Generate expiration date in the future
|
||||
expiration_date = five_minutes_into_future()
|
||||
mnemonic_token = rest_make_recovery_token(
|
||||
authorized_client, expires_at=expiration_date, timeformat=timeformat
|
||||
)
|
||||
|
||||
time_generated = rest_get_recovery_date(authorized_client)
|
||||
assert_recovery_recent(time_generated)
|
||||
|
||||
assert rest_get_recovery_status(authorized_client) == {
|
||||
"exists": True,
|
||||
"valid": True,
|
||||
"date": time_generated,
|
||||
"expiration": expiration_date.replace(tzinfo=timezone.utc).isoformat(),
|
||||
"uses_left": None,
|
||||
}
|
||||
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device")
|
||||
# And again
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2")
|
||||
|
||||
# Try to use token after expiration date
|
||||
mock = mocker.patch(RECOVERY_KEY_VALIDATION_DATETIME, NearFuture)
|
||||
device_name = "recovery_device3"
|
||||
recovery_response = client.post(
|
||||
"/auth/recovery_token/use",
|
||||
json={"token": mnemonic_token, "device": device_name},
|
||||
)
|
||||
assert recovery_response.status_code == 404
|
||||
# Assert that the token was not created
|
||||
assert device_name not in [
|
||||
token["name"] for token in rest_get_tokens_info(authorized_client)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timeformat", DATE_FORMATS)
|
||||
def test_generate_recovery_token_with_expiration_in_the_past(
|
||||
authorized_client, tokens_file, timeformat
|
||||
):
|
||||
# Server must return 400 if expiration date is in the past
|
||||
expiration_date = five_minutes_into_past()
|
||||
expiration_date_str = expiration_date.strftime(timeformat)
|
||||
response = authorized_client.post(
|
||||
"/auth/recovery_token",
|
||||
json={"expiration": expiration_date_str},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert_no_recovery(authorized_client)
|
||||
|
||||
|
||||
def test_generate_recovery_token_with_invalid_time_format(
|
||||
authorized_client, tokens_file
|
||||
):
|
||||
# Server must return 400 if expiration date is in the past
|
||||
expiration_date = "invalid_time_format"
|
||||
response = authorized_client.post(
|
||||
"/auth/recovery_token",
|
||||
json={"expiration": expiration_date},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert_no_recovery(authorized_client)
|
||||
|
||||
|
||||
def test_generate_recovery_token_with_limited_uses(
|
||||
authorized_client, client, tokens_file
|
||||
):
|
||||
# Generate token with limited uses
|
||||
mnemonic_token = rest_make_recovery_token(authorized_client, uses=2)
|
||||
|
||||
time_generated = rest_get_recovery_date(authorized_client)
|
||||
assert_recovery_recent(time_generated)
|
||||
|
||||
assert rest_get_recovery_status(authorized_client) == {
|
||||
"exists": True,
|
||||
"valid": True,
|
||||
"date": time_generated,
|
||||
"expiration": None,
|
||||
"uses_left": 2,
|
||||
}
|
||||
|
||||
# Try to use the token
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device")
|
||||
|
||||
assert rest_get_recovery_status(authorized_client) == {
|
||||
"exists": True,
|
||||
"valid": True,
|
||||
"date": time_generated,
|
||||
"expiration": None,
|
||||
"uses_left": 1,
|
||||
}
|
||||
|
||||
# Try to use token again
|
||||
rest_recover_with_mnemonic(client, mnemonic_token, "recover_device2")
|
||||
|
||||
assert rest_get_recovery_status(authorized_client) == {
|
||||
"exists": True,
|
||||
"valid": False,
|
||||
"date": time_generated,
|
||||
"expiration": None,
|
||||
"uses_left": 0,
|
||||
}
|
||||
|
||||
# Try to use token after limited uses
|
||||
recovery_response = client.post(
|
||||
"/auth/recovery_token/use",
|
||||
json={"token": mnemonic_token, "device": "recovery_device3"},
|
||||
)
|
||||
assert recovery_response.status_code == 404
|
||||
|
||||
|
||||
def test_generate_recovery_token_with_negative_uses(
|
||||
authorized_client, client, tokens_file
|
||||
):
|
||||
# Generate token with limited uses
|
||||
response = authorized_client.post(
|
||||
"/auth/recovery_token",
|
||||
json={"uses": -2},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert_no_recovery(authorized_client)
|
||||
|
||||
|
||||
def test_generate_recovery_token_with_zero_uses(authorized_client, client, tokens_file):
|
||||
# Generate token with limited uses
|
||||
response = authorized_client.post(
|
||||
"/auth/recovery_token",
|
||||
json={"uses": 0},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert_no_recovery(authorized_client)
|
|
@ -1,416 +0,0 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
from selfprivacy_api.utils import get_domain
|
||||
|
||||
|
||||
def read_json(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def domain_file(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.DOMAIN_FILE", datadir / "domain")
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def turned_on(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_on.json")
|
||||
assert read_json(datadir / "turned_on.json")["autoUpgrade"]["enable"] == True
|
||||
assert read_json(datadir / "turned_on.json")["autoUpgrade"]["allowReboot"] == True
|
||||
assert read_json(datadir / "turned_on.json")["timezone"] == "Europe/Moscow"
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def turned_off(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "turned_off.json")
|
||||
assert read_json(datadir / "turned_off.json")["autoUpgrade"]["enable"] == False
|
||||
assert read_json(datadir / "turned_off.json")["autoUpgrade"]["allowReboot"] == False
|
||||
assert read_json(datadir / "turned_off.json")["timezone"] == "Europe/Moscow"
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def undefined_config(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json")
|
||||
assert "autoUpgrade" not in read_json(datadir / "undefined.json")
|
||||
assert "timezone" not in read_json(datadir / "undefined.json")
|
||||
return datadir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_values(mocker, datadir):
|
||||
mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "no_values.json")
|
||||
assert "enable" not in read_json(datadir / "no_values.json")["autoUpgrade"]
|
||||
assert "allowReboot" not in read_json(datadir / "no_values.json")["autoUpgrade"]
|
||||
return datadir
|
||||
|
||||
|
||||
class ProcessMock:
|
||||
"""Mock subprocess.Popen"""
|
||||
|
||||
def __init__(self, args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def communicate():
|
||||
return (b"", None)
|
||||
|
||||
returncode = 0
|
||||
|
||||
|
||||
class BrokenServiceMock(ProcessMock):
|
||||
"""Mock subprocess.Popen"""
|
||||
|
||||
def communicate():
|
||||
return (b"Testing error", None)
|
||||
|
||||
returncode = 3
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subprocess_popen(mocker):
|
||||
mock = mocker.patch("subprocess.Popen", autospec=True, return_value=ProcessMock)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_os_chdir(mocker):
|
||||
mock = mocker.patch("os.chdir", autospec=True)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_broken_service(mocker):
|
||||
mock = mocker.patch(
|
||||
"subprocess.Popen", autospec=True, return_value=BrokenServiceMock
|
||||
)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subprocess_check_output(mocker):
|
||||
mock = mocker.patch(
|
||||
"subprocess.check_output", autospec=True, return_value=b"Testing Linux"
|
||||
)
|
||||
return mock
|
||||
|
||||
|
||||
def test_wrong_auth(wrong_auth_client):
|
||||
response = wrong_auth_client.get("/system/pythonVersion")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_get_domain(authorized_client, domain_file):
|
||||
assert get_domain() == "test-domain.tld"
|
||||
|
||||
|
||||
## Timezones
|
||||
|
||||
|
||||
def test_get_timezone_unauthorized(client, turned_on):
|
||||
response = client.get("/system/configuration/timezone")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_get_timezone(authorized_client, turned_on):
|
||||
response = authorized_client.get("/system/configuration/timezone")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == "Europe/Moscow"
|
||||
|
||||
|
||||
def test_get_timezone_on_undefined(authorized_client, undefined_config):
|
||||
response = authorized_client.get("/system/configuration/timezone")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == "Europe/Uzhgorod"
|
||||
|
||||
|
||||
def test_put_timezone_unauthorized(client, turned_on):
|
||||
response = client.put(
|
||||
"/system/configuration/timezone", json={"timezone": "Europe/Moscow"}
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_put_timezone(authorized_client, turned_on):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/timezone", json={"timezone": "Europe/Helsinki"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Helsinki"
|
||||
|
||||
|
||||
def test_put_timezone_on_undefined(authorized_client, undefined_config):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/timezone", json={"timezone": "Europe/Helsinki"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
read_json(undefined_config / "undefined.json")["timezone"] == "Europe/Helsinki"
|
||||
)
|
||||
|
||||
|
||||
def test_put_timezone_without_timezone(authorized_client, turned_on):
|
||||
response = authorized_client.put("/system/configuration/timezone", json={})
|
||||
assert response.status_code == 422
|
||||
assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Moscow"
|
||||
|
||||
|
||||
def test_put_invalid_timezone(authorized_client, turned_on):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/timezone", json={"timezone": "Invalid/Timezone"}
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert read_json(turned_on / "turned_on.json")["timezone"] == "Europe/Moscow"
|
||||
|
||||
|
||||
## AutoUpgrade
|
||||
|
||||
|
||||
def test_get_auto_upgrade_unauthorized(client, turned_on):
|
||||
response = client.get("/system/configuration/autoUpgrade")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_get_auto_upgrade(authorized_client, turned_on):
|
||||
response = authorized_client.get("/system/configuration/autoUpgrade")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"enable": True,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_get_auto_upgrade_on_undefined(authorized_client, undefined_config):
|
||||
response = authorized_client.get("/system/configuration/autoUpgrade")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"enable": True,
|
||||
"allowReboot": False,
|
||||
}
|
||||
|
||||
|
||||
def test_get_auto_upgrade_without_values(authorized_client, no_values):
|
||||
response = authorized_client.get("/system/configuration/autoUpgrade")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"enable": True,
|
||||
"allowReboot": False,
|
||||
}
|
||||
|
||||
|
||||
def test_get_auto_upgrade_turned_off(authorized_client, turned_off):
|
||||
response = authorized_client.get("/system/configuration/autoUpgrade")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"enable": False,
|
||||
"allowReboot": False,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_unauthorized(client, turned_on):
|
||||
response = client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_put_auto_upgrade(authorized_client, turned_on):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": False, "allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_on / "turned_on.json")["autoUpgrade"] == {
|
||||
"enable": False,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_on_undefined(authorized_client, undefined_config):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": False, "allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(undefined_config / "undefined.json")["autoUpgrade"] == {
|
||||
"enable": False,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_without_values(authorized_client, no_values):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(no_values / "no_values.json")["autoUpgrade"] == {
|
||||
"enable": True,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_turned_off(authorized_client, turned_off):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": True, "allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == {
|
||||
"enable": True,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_without_enable(authorized_client, turned_off):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"allowReboot": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == {
|
||||
"enable": False,
|
||||
"allowReboot": True,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_without_allow_reboot(authorized_client, turned_off):
|
||||
response = authorized_client.put(
|
||||
"/system/configuration/autoUpgrade", json={"enable": True}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == {
|
||||
"enable": True,
|
||||
"allowReboot": False,
|
||||
}
|
||||
|
||||
|
||||
def test_put_auto_upgrade_with_empty_json(authorized_client, turned_off):
|
||||
response = authorized_client.put("/system/configuration/autoUpgrade", json={})
|
||||
assert response.status_code == 200
|
||||
assert read_json(turned_off / "turned_off.json")["autoUpgrade"] == {
|
||||
"enable": False,
|
||||
"allowReboot": False,
|
||||
}
|
||||
|
||||
|
||||
def test_system_rebuild_unauthorized(client, mock_subprocess_popen):
|
||||
response = client.get("/system/configuration/apply")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_popen.call_count == 0
|
||||
|
||||
|
||||
def test_system_rebuild(authorized_client, mock_subprocess_popen):
|
||||
response = authorized_client.get("/system/configuration/apply")
|
||||
assert response.status_code == 200
|
||||
assert mock_subprocess_popen.call_count == 1
|
||||
assert mock_subprocess_popen.call_args[0][0] == [
|
||||
"systemctl",
|
||||
"start",
|
||||
"sp-nixos-rebuild.service",
|
||||
]
|
||||
|
||||
|
||||
def test_system_upgrade_unauthorized(client, mock_subprocess_popen):
|
||||
response = client.get("/system/configuration/upgrade")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_popen.call_count == 0
|
||||
|
||||
|
||||
def test_system_upgrade(authorized_client, mock_subprocess_popen):
|
||||
response = authorized_client.get("/system/configuration/upgrade")
|
||||
assert response.status_code == 200
|
||||
assert mock_subprocess_popen.call_count == 1
|
||||
assert mock_subprocess_popen.call_args[0][0] == [
|
||||
"systemctl",
|
||||
"start",
|
||||
"sp-nixos-upgrade.service",
|
||||
]
|
||||
|
||||
|
||||
def test_system_rollback_unauthorized(client, mock_subprocess_popen):
|
||||
response = client.get("/system/configuration/rollback")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_popen.call_count == 0
|
||||
|
||||
|
||||
def test_system_rollback(authorized_client, mock_subprocess_popen):
|
||||
response = authorized_client.get("/system/configuration/rollback")
|
||||
assert response.status_code == 200
|
||||
assert mock_subprocess_popen.call_count == 1
|
||||
assert mock_subprocess_popen.call_args[0][0] == [
|
||||
"systemctl",
|
||||
"start",
|
||||
"sp-nixos-rollback.service",
|
||||
]
|
||||
|
||||
|
||||
def test_get_system_version_unauthorized(client, mock_subprocess_check_output):
|
||||
response = client.get("/system/version")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_check_output.call_count == 0
|
||||
|
||||
|
||||
def test_get_system_version(authorized_client, mock_subprocess_check_output):
|
||||
response = authorized_client.get("/system/version")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"system_version": "Testing Linux"}
|
||||
assert mock_subprocess_check_output.call_count == 1
|
||||
assert mock_subprocess_check_output.call_args[0][0] == ["uname", "-a"]
|
||||
|
||||
|
||||
def test_reboot_system_unauthorized(client, mock_subprocess_popen):
|
||||
response = client.get("/system/reboot")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_popen.call_count == 0
|
||||
|
||||
|
||||
def test_reboot_system(authorized_client, mock_subprocess_popen):
|
||||
response = authorized_client.get("/system/reboot")
|
||||
assert response.status_code == 200
|
||||
assert mock_subprocess_popen.call_count == 1
|
||||
assert mock_subprocess_popen.call_args[0][0] == ["reboot"]
|
||||
|
||||
|
||||
def test_get_python_version_unauthorized(client, mock_subprocess_check_output):
|
||||
response = client.get("/system/pythonVersion")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_check_output.call_count == 0
|
||||
|
||||
|
||||
def test_get_python_version(authorized_client, mock_subprocess_check_output):
|
||||
response = authorized_client.get("/system/pythonVersion")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == "Testing Linux"
|
||||
assert mock_subprocess_check_output.call_count == 1
|
||||
assert mock_subprocess_check_output.call_args[0][0] == ["python", "-V"]
|
||||
|
||||
|
||||
def test_pull_system_unauthorized(client, mock_subprocess_popen):
|
||||
response = client.get("/system/configuration/pull")
|
||||
assert response.status_code == 401
|
||||
assert mock_subprocess_popen.call_count == 0
|
||||
|
||||
|
||||
def test_pull_system(authorized_client, mock_subprocess_popen, mock_os_chdir):
|
||||
current_dir = os.getcwd()
|
||||
response = authorized_client.get("/system/configuration/pull")
|
||||
assert response.status_code == 200
|
||||
assert mock_subprocess_popen.call_count == 1
|
||||
assert mock_subprocess_popen.call_args[0][0] == ["git", "pull"]
|
||||
assert mock_os_chdir.call_count == 2
|
||||
assert mock_os_chdir.call_args_list[0][0][0] == "/etc/nixos"
|
||||
assert mock_os_chdir.call_args_list[1][0][0] == current_dir
|
||||
|
||||
|
||||
def test_pull_system_broken_repo(authorized_client, mock_broken_service, mock_os_chdir):
|
||||
current_dir = os.getcwd()
|
||||
response = authorized_client.get("/system/configuration/pull")
|
||||
assert response.status_code == 500
|
||||
assert mock_broken_service.call_count == 1
|
||||
assert mock_os_chdir.call_count == 2
|
||||
assert mock_os_chdir.call_args_list[0][0][0] == "/etc/nixos"
|
||||
assert mock_os_chdir.call_args_list[1][0][0] == current_dir
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Not needed, 3.0.0 will run on the system which already has a new userdata structure. I will make an additional PR to account for changes occurred during migration.