Files

158 lines
5.2 KiB
Python

"""Accounts, passwords, JWT sessions, API keys, snapshots."""
import json
import time
import pytest
from helpers import PASSWORD
from rigshare_core import jwt
import rigshare_core.store as store_module
def make_admin(store, name="admin"):
return store.setup_admin(name, PASSWORD)
# ----- setup and accounts ------------------------------------------------
def test_no_default_accounts(store):
assert store.needs_setup
assert store.users == {}
def test_setup_only_once(store):
user = make_admin(store)
assert user["perms"]["admin"] and not store.needs_setup
with pytest.raises(PermissionError):
store.setup_admin("intruder", PASSWORD)
def test_env_provisioning(tmp_path, monkeypatch):
monkeypatch.setenv("RIGSHARE_ADMIN_USER", "ops")
monkeypatch.setenv("RIGSHARE_ADMIN_PASSWORD", PASSWORD)
s = store_module.Store(str(tmp_path))
assert s.users["ops"]["perms"]["admin"]
def test_passwords_are_hashed(store, tmp_path):
make_admin(store)
raw = (tmp_path / "users.json").read_text()
assert PASSWORD not in raw
assert store.users["admin"]["password"].startswith("pbkdf2_sha256$")
def test_authenticate(store):
make_admin(store)
assert store.authenticate("admin", PASSWORD)
assert store.authenticate("admin", "wrong-password") is None
assert store.authenticate("nobody", PASSWORD) is None
@pytest.mark.parametrize("username,password", [("x", PASSWORD), ("bad name", PASSWORD), ("ok_name", "short")])
def test_create_user_validation(store, username, password):
make_admin(store)
with pytest.raises(ValueError):
store.create_user(username, password)
def test_last_admin_is_protected(store):
make_admin(store)
with pytest.raises(ValueError):
store.delete_user("admin")
with pytest.raises(ValueError):
store.update_user("admin", perms={"admin": False})
def test_password_change_revokes_sessions(store):
make_admin(store)
token = store.create_session("admin")
store.update_user("admin", password="another-password")
assert store.user_for_token(token) is None
# ----- JWT sessions --------------------------------------------------------
def claims_of(token):
return json.loads(jwt._b64d(token.split(".")[1]))
def test_session_is_jwt(store):
make_admin(store)
token = store.create_session("admin")
assert token.count(".") == 2
assert store.user_for_token(token)["username"] == "admin"
def test_tampered_token_rejected(store):
make_admin(store)
header, body, sig = store.create_session("admin").split(".")
forged = jwt._b64e(json.dumps({**claims_of(f"{header}.{body}.{sig}"), "sub": "someone"}).encode())
assert store.user_for_token(f"{header}.{forged}.{sig}") is None
assert store.user_for_token(f"{header}.{body}.{sig[:-2]}AA") is None
def test_alg_none_rejected(store):
make_admin(store)
_, body, _ = store.create_session("admin").split(".")
none_header = jwt._b64e(b'{"alg":"none","typ":"JWT"}')
assert store.user_for_token(f"{none_header}.{body}.") is None
def test_expired_token_rejected(store):
make_admin(store)
sid = claims_of(store.create_session("admin"))["sid"]
old = jwt.encode({"iss": "rigshare", "sub": "admin", "sid": sid, "iat": 0, "nbf": 0, "exp": 1}, store.secret)
assert store.user_for_token(old) is None
def test_logout_revokes_immediately(store):
make_admin(store)
token = store.create_session("admin")
store.revoke_session(token)
assert store.user_for_token(token) is None
def test_refresh_only_when_old(store, monkeypatch):
make_admin(store)
token = store.create_session("admin")
assert store.refresh_token(token) is None
monkeypatch.setattr(store_module, "TOKEN_REFRESH", -1)
fresh = store.refresh_token(token)
assert fresh and store.user_for_token(fresh)["username"] == "admin"
assert claims_of(fresh)["sid"] == claims_of(token)["sid"]
def test_rename_keeps_sessions_and_keys(store):
make_admin(store)
token = store.create_session("admin")
key, _ = store.create_api_key("admin", "k")
store.rename_user("admin", "root")
assert store.user_for_token(token)["username"] == "root"
assert store.user_for_token(key)["username"] == "root"
# ----- API keys ---------------------------------------------------------------
def test_api_keys(store, tmp_path):
make_admin(store)
key, meta = store.create_api_key("admin", "script")
assert key.startswith("rs_") and key not in (tmp_path / "users.json").read_text()
assert store.user_for_token(key)["username"] == "admin"
assert "hash" not in store.list_api_keys("admin")[0]
store.revoke_api_key("admin", meta["id"])
assert store.user_for_token(key) is None
# ----- snapshots ----------------------------------------------------------------
def test_snapshot_rotation_keeps_labelled(store):
store.update_config({"snapshot_keep": 2})
for i in range(4):
store.add_snapshot({"nodes": []}, "me", "room", "Room")
time.sleep(0.01)
store.add_snapshot({"nodes": []}, "me", "room", "Room", label="keep me")
snaps = store.list_snapshots("room")
assert len([s for s in snaps if not s["label"]]) == 2
assert any(s["label"] == "keep me" for s in snaps)