Compare commits

..

No commits in common. "main" and "1.0.0" have entirely different histories.
main ... 1.0.0

7 changed files with 6 additions and 86 deletions

View file

@ -1,7 +1,6 @@
# Pssecret server
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/pssecret-server?label=PyPI%20downloads)](https://pypi.org/project/pssecret-server/)
Pssecret is self-hosted service to share secrets (like passwords) with somebody
over the network, but don't want them to appear in chats, unencrypted e-mails, etc.
@ -51,7 +50,6 @@ Available configuration options:
--uds TEXT Bind to a UNIX domain socket.
--workers INTEGER Number of worker processes. Defaults to the
$WEB_CONCURRENCY environment variable if available, or 1.
--version Show the version and exit.
--help Show this message and exit.
```

View file

@ -1,5 +1,3 @@
from importlib.metadata import version
import click
import uvicorn
@ -23,6 +21,5 @@ import uvicorn
),
type=int,
)
@click.version_option(version("pssecret_server"))
def cli(**kwargs) -> None:
uvicorn.run("pssecret_server.main:app", **kwargs)

View file

@ -8,7 +8,7 @@ from redis.asyncio import Redis
from pssecret_server.fernet import get_fernet
from pssecret_server.models import Secret, SecretSaveResult
from pssecret_server.redis_db import get_redis
from pssecret_server.utils import decrypt_secret, encrypt_secret, getdel, save_secret
from pssecret_server.utils import decrypt_secret, encrypt_secret, save_secret
app = FastAPI()
@ -49,7 +49,7 @@ async def set_secret(
async def get_secret(
secret_key: str, redis: RedisDep, fernet: FernetDep
) -> dict[str, bytes]:
data: bytes | None = await getdel(redis, secret_key)
data: bytes | None = await redis.getdel(secret_key)
if data is None:
raise HTTPException(404)

View file

@ -1,10 +1,7 @@
from functools import lru_cache
from uuid import uuid4
from cryptography.fernet import Fernet
from redis.asyncio import Redis
from redis.exceptions import ResponseError
from redis.typing import ResponseT
from pssecret_server.models import Secret
@ -33,35 +30,3 @@ async def save_secret(data: Secret, redis: Redis) -> str:
await redis.setex(new_key, 60 * 60 * 24, data.data)
return new_key
@lru_cache
async def _is_getdel_available(redis: Redis) -> bool:
"""Checks the availability of GETDEL command on the Redis server instance
GETDEL is not available in Redis prior to version 6.2
"""
try:
await redis.getdel("test:getdel:availability")
except ResponseError:
return False
return True
async def getdel(redis: Redis, key: str) -> ResponseT:
"""Gets the value of key and deletes the key
Depending on the capabilities of Redis server this function
will either call GETDEL command, either first call GETSET with empty string
and DEL right after that.
"""
result: ResponseT
if await _is_getdel_available(redis):
result = await redis.getdel(key)
else:
result = await redis.getset(key, "")
await redis.delete(key)
return result

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "pssecret-server"
version = "1.1.2"
version = "1.0.0"
description = "API service for secrets sharing over network"
authors = ["Ivan Golikov <root@ivnglkv.me>"]
license = "BSD-3-Clause"
@ -53,6 +53,3 @@ reportUnusedCallResult = "none"
[tool.pytest.ini_options]
asyncio_mode = "auto"
[tool.isort]
profile = "black"

View file

@ -1,9 +1,8 @@
from unittest.mock import AsyncMock, patch
from unittest.mock import patch
import pytest
from redis.asyncio import Redis
from pssecret_server.utils import get_new_key, getdel, save_secret
from pssecret_server.utils import get_new_key, save_secret
from ..factories import SecretFactory
@ -34,22 +33,3 @@ async def test_save_secret_data(redis_server: Redis) -> None:
assert redis_data is not None
assert redis_data.decode() == secret.data
@pytest.mark.parametrize("getdel_available", [True, False])
@patch("pssecret_server.utils._is_getdel_available", new_callable=AsyncMock)
async def test_getdel(
mock_is_getdel_available: AsyncMock,
getdel_available: bool,
redis_server: Redis,
) -> None:
mock_is_getdel_available.return_value = getdel_available
test_value = "test_data"
test_key = "test_key"
await redis_server.set(test_key, test_value)
result = await getdel(redis_server, test_key)
assert result.decode() == test_value # pyright: ignore[reportAttributeAccessIssue]
assert not await redis_server.exists(test_key)

View file

@ -1,10 +1,7 @@
from unittest.mock import AsyncMock
import pytest
from cryptography.fernet import Fernet, InvalidToken
from redis.exceptions import ResponseError
from pssecret_server.utils import _is_getdel_available, decrypt_secret, encrypt_secret
from pssecret_server.utils import decrypt_secret, encrypt_secret
from ..factories import SecretFactory
@ -32,17 +29,3 @@ def test_secret_is_not_decryptable_by_random_key(fernet: Fernet):
with pytest.raises(InvalidToken):
decrypt_secret(encrypted_secret.data.encode(), random_fernet)
@pytest.mark.parametrize(
("getdel_effect", "expected_result"), [(None, True), (ResponseError, False)]
)
async def test_is_getdel_available(
getdel_effect: ResponseError | None, expected_result: bool
):
redis = AsyncMock()
redis.getdel.side_effect = getdel_effect # pyright: ignore[reportAny]
result = await _is_getdel_available(redis)
assert result is expected_result