mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
Open WebUI loads /static/custom.css on every page, so tasks/00_core.yml
renders templates/custom.css.j2 when design is on and meta/volumes.yml
mounts it read-only there; it maps the sign-in page, the chat shell,
Markdown, tables and code onto the design tokens and leaves the OLED
dark theme to the user. templates/design.js.j2 mirrors the native theme
setting into data-design-theme. meta/services.yml keeps logo and title
replacement off, so Open WebUI keeps its own branding.
With sso and ldap both off, tasks/utils/admin.yml runs files/python/
administrator.py, which creates the native administrator or resets its
password and promotes it, so the native sign-in has an account; the
identity is lookup('users', 'platform_administrator') as on
feature/store-i18n, declared in meta/users.yml next to the user the
role still reads elsewhere. tests/unit/python/roles/web-app-openwebui/
files/test_administrator.py covers the script with fake values.
files/playwright/test-design.js checks tokens, sign-in page, chat shell,
a stored answer, the native theme setting, the provisioned
administrator, the in-house custom.css, logo and title, and captures
the gallery; files/playwright/_shared.js waits for editable password
fields. README.md links the gallery; TODO.md records the open
self-registration with sso and ldap off and the bot password and
bearer tokens in container exec argv.
The design was verified green on the compose stack (10 passed); the
switch to the platform administrator is checked statically only, the
first live run is the verification on the merged state.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import importlib.util
|
|
import types
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
SCRIPT_PATH = PROJECT_ROOT / "roles/web-app-openwebui/files/python/administrator.py"
|
|
EMAIL = "Administrator@Example.org"
|
|
PASSWORD = "p$ssw0rd-from-the-users-spot"
|
|
|
|
|
|
def load_script() -> object:
|
|
spec = importlib.util.spec_from_file_location("administrator", SCRIPT_PATH)
|
|
module = importlib.util.module_from_spec(spec)
|
|
with patch.dict(
|
|
"os.environ",
|
|
{
|
|
"OPENWEBUI_ADMIN_EMAIL": EMAIL,
|
|
"OPENWEBUI_ADMIN_NAME": "administrator",
|
|
"OPENWEBUI_ADMIN_PASSWORD": PASSWORD,
|
|
},
|
|
):
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class FakeBackend:
|
|
"""Stands in for the open_webui models the script imports in-process."""
|
|
|
|
def __init__(self, user=None, hashed=None):
|
|
self.user = user
|
|
self.hashed = hashed
|
|
self.created = []
|
|
self.passwords = []
|
|
self.roles = []
|
|
|
|
def modules(self) -> dict:
|
|
users = types.SimpleNamespace(
|
|
get_user_by_email=self._get_user_by_email,
|
|
update_user_role_by_id=self._update_role,
|
|
)
|
|
auths = types.SimpleNamespace(
|
|
insert_new_auth=self._insert_auth,
|
|
authenticate_user=self._authenticate,
|
|
update_user_password_by_id=self._update_password,
|
|
)
|
|
out = {}
|
|
for name, attrs in (
|
|
("open_webui.models.users", {"Users": users}),
|
|
("open_webui.models.auths", {"Auths": auths}),
|
|
(
|
|
"open_webui.utils.auth",
|
|
{"get_password_hash": self._hash, "verify_password": self._verify},
|
|
),
|
|
):
|
|
module = types.ModuleType(name)
|
|
for attr, value in attrs.items():
|
|
setattr(module, attr, value)
|
|
out[name] = module
|
|
return out
|
|
|
|
async def _get_user_by_email(self, email):
|
|
return self.user if self.user and self.user.email == email else None
|
|
|
|
async def _insert_auth(self, email, password, name, role):
|
|
self.created.append((email, password, name, role))
|
|
return types.SimpleNamespace(id="created", email=email, role=role)
|
|
|
|
async def _authenticate(self, email, verify):
|
|
return self.user if await verify(self.hashed) else None
|
|
|
|
async def _update_password(self, user_id, hashed):
|
|
self.passwords.append((user_id, hashed))
|
|
return True
|
|
|
|
async def _update_role(self, user_id, role):
|
|
self.roles.append((user_id, role))
|
|
|
|
async def _hash(self, password):
|
|
return f"hashed-{password}"
|
|
|
|
async def _verify(self, plain, hashed):
|
|
return hashed == f"hashed-{plain}"
|
|
|
|
|
|
class TestConvergeAdministrator(unittest.TestCase):
|
|
def run_script(self, backend: FakeBackend) -> str:
|
|
module = load_script()
|
|
with patch.dict("sys.modules", backend.modules()):
|
|
return asyncio.run(module.converge())
|
|
|
|
def test_a_missing_administrator_is_created_with_the_spot_password(self) -> None:
|
|
backend = FakeBackend()
|
|
self.assertEqual(self.run_script(backend), "CHANGED created")
|
|
self.assertEqual(
|
|
backend.created,
|
|
[(EMAIL.lower(), f"hashed-{PASSWORD}", "administrator", "admin")],
|
|
)
|
|
|
|
def test_an_account_in_the_state_found_on_the_stack_is_promoted_and_reset(
|
|
self,
|
|
) -> None:
|
|
user = types.SimpleNamespace(id="u1", email=EMAIL.lower(), role="user")
|
|
backend = FakeBackend(user, hashed="hashed-an-older-password")
|
|
self.assertEqual(self.run_script(backend), "CHANGED password role")
|
|
self.assertEqual(backend.passwords, [("u1", f"hashed-{PASSWORD}")])
|
|
self.assertEqual(backend.roles, [("u1", "admin")])
|
|
|
|
def test_a_rotated_password_reaches_an_existing_administrator(self) -> None:
|
|
user = types.SimpleNamespace(id="u1", email=EMAIL.lower(), role="admin")
|
|
backend = FakeBackend(user, hashed="hashed-an-older-password")
|
|
self.assertEqual(self.run_script(backend), "CHANGED password")
|
|
self.assertEqual(backend.roles, [])
|
|
|
|
def test_a_converged_administrator_is_left_alone(self) -> None:
|
|
user = types.SimpleNamespace(id="u1", email=EMAIL.lower(), role="admin")
|
|
backend = FakeBackend(user, hashed=f"hashed-{PASSWORD}")
|
|
self.assertEqual(self.run_script(backend), "OK")
|
|
self.assertEqual(
|
|
(backend.created, backend.passwords, backend.roles), ([], [], [])
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|