37 lines
2.3 KiB
Python
37 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Concurrent account changes and one-time password reset tokens."""
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
import json
|
|
import subprocess
|
|
import tempfile
|
|
from support import copy_core, write_json
|
|
|
|
with tempfile.TemporaryDirectory(prefix="raiju-users-smoke-") as folder:
|
|
root = copy_core(folder)
|
|
users_path = root / "content/users.json"
|
|
write_json(users_path, {"users": [
|
|
{"id": "first", "username": "first", "email": "", "role": "admin", "passwordHash": "old", "resetToken": None},
|
|
{"id": "second", "username": "second", "email": "", "role": "admin", "passwordHash": "old", "resetToken": None},
|
|
]})
|
|
|
|
def php(code):
|
|
return subprocess.check_output(["php", "-r", "require 'admin/users.php'; " + code], cwd=root, text=True).strip()
|
|
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
results = list(workers.map(lambda n: php("echo (int)" + ("updateUserPassword('first','new-hash');" if n == 0 else "updateUserLastLogin('first');")), range(24)))
|
|
assert all(result == "1" for result in results)
|
|
assert json.loads(users_path.read_text())["users"][0]["passwordHash"] == "new-hash", "Login overwrote a new password"
|
|
token = php("echo generateResetToken('first');")
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
results = list(workers.map(lambda _: php(f"echo (int)completePasswordReset('first','{token}','reset-hash');"), range(24)))
|
|
assert results.count("1") == 1, "Reset token accepted multiple times"
|
|
with ThreadPoolExecutor(max_workers=2) as workers:
|
|
results = list(workers.map(lambda name: php(f"echo (int)updateUser('{name}', ['role'=>'editor']);"), ["first", "second"]))
|
|
assert results.count("1") == 1, "Concurrent demotions removed the last admin"
|
|
remaining_admin = next(user["id"] for user in json.loads(users_path.read_text())["users"] if user["role"] == "admin")
|
|
assert php(f"echo (int)deleteUser('{remaining_admin}');") == "0", "Deleted the last administrator"
|
|
users_path.write_text("{broken-users")
|
|
assert php("echo (int)updateUserPassword('first','must-not-save');") == "0"
|
|
assert users_path.read_text() == "{broken-users", "Overwrote corrupt user store"
|
|
print("PASS concurrent account updates, single-use password reset, last-admin protection and corrupt-store preservation")
|