72 lines
2.8 KiB
Python
72 lines
2.8 KiB
Python
|
|
import base64
|
||
|
|
import hashlib
|
||
|
|
import hmac
|
||
|
|
import json
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from app.auth import _username_from_cookie, authenticate
|
||
|
|
from app.config import settings
|
||
|
|
|
||
|
|
|
||
|
|
def _b64url(raw: bytes) -> str:
|
||
|
|
return base64.urlsafe_b64encode(raw).decode().rstrip("=")
|
||
|
|
|
||
|
|
|
||
|
|
def _make_jwt(payload: dict, secret: str | None = None) -> str:
|
||
|
|
header = _b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
|
||
|
|
body = _b64url(json.dumps(payload).encode())
|
||
|
|
signing = f"{header}.{body}".encode()
|
||
|
|
sig = hmac.new((secret or "x").encode(), signing, hashlib.sha256).digest()
|
||
|
|
return f"{header}.{body}.{_b64url(sig)}"
|
||
|
|
|
||
|
|
|
||
|
|
class _Headers(dict):
|
||
|
|
"""Case-insensitives .get wie Starlette-Headers (nur was wir brauchen)."""
|
||
|
|
def get(self, key, default=None):
|
||
|
|
return super().get(key.lower(), default)
|
||
|
|
|
||
|
|
|
||
|
|
def _cfg(monkeypatch, **over):
|
||
|
|
monkeypatch.setattr(settings, "trusted_auth_cookie", "yunohost.portal")
|
||
|
|
monkeypatch.setattr(settings, "trusted_auth_cookie_claim", "user")
|
||
|
|
monkeypatch.setattr(settings, "trusted_auth_jwt_secret", over.get("secret", ""))
|
||
|
|
return settings
|
||
|
|
|
||
|
|
|
||
|
|
def test_username_from_unsigned_cookie(monkeypatch):
|
||
|
|
_cfg(monkeypatch)
|
||
|
|
jwt = _make_jwt({"user": "dieterschlueter", "host": "linix.de"})
|
||
|
|
headers = _Headers({"cookie": f"foo=bar; yunohost.portal={jwt}; baz=qux"})
|
||
|
|
assert _username_from_cookie(headers, settings) == "dieterschlueter"
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_cookie_returns_none(monkeypatch):
|
||
|
|
_cfg(monkeypatch)
|
||
|
|
assert _username_from_cookie(_Headers({"cookie": "foo=bar"}), settings) is None
|
||
|
|
assert _username_from_cookie(_Headers({}), settings) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_signature_checked_when_secret_set(monkeypatch):
|
||
|
|
_cfg(monkeypatch, secret="geheim")
|
||
|
|
good = _make_jwt({"user": "atoor"}, secret="geheim")
|
||
|
|
bad = _make_jwt({"user": "atoor"}, secret="falsch")
|
||
|
|
assert _username_from_cookie(_Headers({"cookie": f"yunohost.portal={good}"}), settings) == "atoor"
|
||
|
|
assert _username_from_cookie(_Headers({"cookie": f"yunohost.portal={bad}"}), settings) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_authenticate_via_cookie_from_trusted_proxy(monkeypatch):
|
||
|
|
import app.dependencies as deps
|
||
|
|
_cfg(monkeypatch)
|
||
|
|
monkeypatch.setattr(settings, "trusted_auth_header", "")
|
||
|
|
monkeypatch.setattr(settings, "trusted_proxy_ips", "192.168.179.10")
|
||
|
|
monkeypatch.setattr(settings, "admin_users", "atoor,dieterschlueter,dschlueter")
|
||
|
|
jwt = _make_jwt({"user": "dieterschlueter"})
|
||
|
|
headers = _Headers({"cookie": f"yunohost.portal={jwt}"})
|
||
|
|
|
||
|
|
user = authenticate(headers, "192.168.179.10", None)
|
||
|
|
assert user is not None
|
||
|
|
assert user.external_id == "dieterschlueter" and user.is_admin is True
|
||
|
|
# Von fremder IP wird das Cookie ignoriert.
|
||
|
|
assert authenticate(headers, "10.0.0.1", None) is None or \
|
||
|
|
authenticate(headers, "10.0.0.1", None).external_id != "dieterschlueter"
|