feat(auth): define Default/Local owner contract (#5795)

* feat(auth): define default local owner contract

* test(auth): harden default local owner matrix

---------

Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
RaresKeY
2026-08-15 20:27:26 +01:00
committed by GitHub
co-authored by Alexandre Teixeira
parent 9c71948376
commit 0dd70a7556
13 changed files with 250 additions and 45 deletions
@@ -16,8 +16,11 @@ from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from src.owner_identity import DEFAULT_LOCAL_OWNER
from tests.helpers.import_state import clear_module
_RESERVED_NAMES = ["internal-tool", "api", "demo", "system", DEFAULT_LOCAL_OWNER]
def _fresh_auth_manager(tmp_path):
# Same import dance as test_security_regressions: drop any cached stub so
@@ -30,7 +33,7 @@ def _fresh_auth_manager(tmp_path):
@pytest.mark.parametrize(
"name",
["internal-tool", "api", "demo", "system", "INTERNAL-TOOL", " Internal-Tool ", "Api", "SYSTEM"],
_RESERVED_NAMES + ["INTERNAL-TOOL", " Internal-Tool ", "Api", "SYSTEM"],
)
def test_create_user_rejects_reserved_usernames(tmp_path, name):
mgr = _fresh_auth_manager(tmp_path)
@@ -45,34 +48,37 @@ def test_create_user_rejects_empty_username(tmp_path):
assert "" not in mgr.users
def test_setup_rejects_reserved_admin_username(tmp_path):
@pytest.mark.parametrize("name", _RESERVED_NAMES)
def test_setup_rejects_reserved_admin_username(tmp_path, name):
mgr = _fresh_auth_manager(tmp_path)
# First-run admin setup funnels through create_user, so it's covered too.
assert mgr.setup("internal-tool", "pw-123456") is False
assert mgr.setup(name, "pw-123456") is False
assert mgr.is_configured is False
def test_rename_into_reserved_username_is_blocked(tmp_path):
@pytest.mark.parametrize("name", _RESERVED_NAMES)
def test_rename_into_reserved_username_is_blocked(tmp_path, name):
mgr = _fresh_auth_manager(tmp_path)
assert mgr.create_user("admin", "pw-123456", is_admin=True) is True
assert mgr.create_user("bob", "pw-123456") is True
assert mgr.rename_user("bob", "internal-tool", "admin") is False
assert "internal-tool" not in mgr.users
assert mgr.rename_user("bob", name, "admin") is False
assert name not in mgr.users
assert "bob" in mgr.users
def test_legacy_reserved_username_is_removed_on_load(tmp_path):
@pytest.mark.parametrize("name", _RESERVED_NAMES)
def test_legacy_reserved_username_is_removed_on_load(tmp_path, name):
auth_path = tmp_path / "auth.json"
auth_path.write_text(
'{"users": {"internal-tool": {"password_hash": "unused", "is_admin": false}, '
'"admin": {"password_hash": "unused", "is_admin": true}}}',
'{"users": {"%s": {"password_hash": "unused", "is_admin": false}, '
'"admin": {"password_hash": "unused", "is_admin": true}}}' % name,
encoding="utf-8",
)
mgr = _fresh_auth_manager(tmp_path)
assert "internal-tool" not in mgr.users
assert name not in mgr.users
assert "admin" in mgr.users
assert "internal-tool" not in auth_path.read_text(encoding="utf-8")
assert name not in auth_path.read_text(encoding="utf-8")
def test_legacy_reserved_username_session_cannot_authenticate(tmp_path):
@@ -121,15 +127,16 @@ def test_legacy_reserved_username_session_cannot_pass_admin_gate(tmp_path, monke
assert exc.value.status_code == 403
def test_legacy_reserved_single_user_migrates_to_admin(tmp_path):
@pytest.mark.parametrize("name", _RESERVED_NAMES)
def test_legacy_reserved_single_user_migrates_to_admin(tmp_path, name):
auth_path = tmp_path / "auth.json"
auth_path.write_text(
'{"username": "internal-tool", "password_hash": "unused"}',
'{"username": "%s", "password_hash": "unused"}' % name,
encoding="utf-8",
)
mgr = _fresh_auth_manager(tmp_path)
assert "internal-tool" not in mgr.users
assert name not in mgr.users
assert "admin" in mgr.users
assert mgr.is_admin("admin") is True
@@ -141,8 +148,8 @@ def test_token_cache_owner_normalization_requires_current_user():
users = {"alice": {}, "admin": {}}
assert normalize_known_username(users, " Alice ") == "alice"
assert normalize_known_username(users, "internal-tool") is None
assert normalize_known_username(users, "api") is None
for name in _RESERVED_NAMES:
assert normalize_known_username(users, name) is None
assert normalize_known_username(users, "") is None