Spaces:
Running
Running
Download editor/scope.py from acecalisto3/SignalMesh: direct link, hf CLI and curl.
- Browser
- Download file 3.81 kB
-
https://huggingface.co/spaces/acecalisto3/SignalMesh/resolve/main/editor/scope.py
- Command line
-
hf download hf://spaces/acecalisto3/SignalMesh/editor/scope.py
-
curl -L -o scope.py https://huggingface.co/spaces/acecalisto3/SignalMesh/resolve/main/editor/scope.py
3.81 kB
| """An optional narrowing of what a signed-in account may touch. | |
| Joomla is the authority on permissions and stays that way — `joomla.permits` | |
| walks the real asset line and nothing here can widen its answer. This only | |
| subtracts, and exists because of a practical gap: most Joomla sites leave every | |
| article inheriting the root rules, so "this person may edit exactly one page" | |
| is not expressible in Joomla without hand-configuring per-article permissions. | |
| An account with no rows here is unrestricted, which keeps the overlay opt-in — | |
| adding the table changes nothing until someone deliberately narrows an account. | |
| Owner and Super User scope bypasses this by design: it is a delegation tool, | |
| not a way to fence in the person who owns the site. | |
| Rows live in the site's own database (see `store`), so a fence survives a | |
| restart. A fence that quietly disappeared would silently re-widen an account, | |
| which is the worst direction for this particular mistake to fail in. | |
| """ | |
| import time | |
| import store | |
| COLUMNS = """ | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| ts DOUBLE NOT NULL, | |
| user_id INT NOT NULL, | |
| kind VARCHAR(16) NOT NULL, | |
| ref INT NOT NULL, | |
| UNIQUE KEY kyklos_scope_once (user_id, kind, ref) | |
| """ | |
| INDEXES = ("kyklos_scope_by_user (user_id)",) | |
| KINDS = ("article", "category") | |
| def _t(site=None): | |
| return store.ensure("scope", COLUMNS, INDEXES, site) | |
| def limits(site, user_id): | |
| """What this account is fenced to, or None when it is not fenced at all. | |
| None and an empty fence are deliberately different: None means nobody has | |
| narrowed this account, empty would mean somebody narrowed it to nothing. | |
| """ | |
| rows = store.query(f"SELECT kind, ref FROM {_t(site)} WHERE user_id = %s", | |
| (int(user_id),), site) | |
| if not rows: | |
| return None | |
| return {"articles": {r["ref"] for r in rows if r["kind"] == "article"}, | |
| "categories": {r["ref"] for r in rows if r["kind"] == "category"}} | |
| def allows(site, user_id, article_id, category_id=None): | |
| """Is this article inside the fence? Unfenced accounts are always allowed.""" | |
| fence = limits(site, user_id) | |
| if fence is None: | |
| return True | |
| if int(article_id) in fence["articles"]: | |
| return True | |
| return category_id is not None and int(category_id) in fence["categories"] | |
| def grant(site, user_id, kind, ref): | |
| """Add one page or one category to an account's fence. | |
| The first grant is what creates the fence, so this both narrows and | |
| restricts: an unfenced account becomes fenced to exactly this. | |
| """ | |
| if kind not in KINDS: | |
| raise ValueError(f"scope kind must be article or category, not {kind!r}") | |
| store.write( | |
| f"INSERT IGNORE INTO {_t(site)} (ts,user_id,kind,ref) VALUES (%s,%s,%s,%s)", | |
| (time.time(), int(user_id), kind, int(ref)), site) | |
| def revoke(site, user_id, kind=None, ref=None): | |
| """Drop one entry, or the whole fence when called with neither. | |
| Dropping the last entry returns the account to unfenced — Joomla's answer | |
| alone — rather than to a fence containing nothing, which would silently | |
| lock the person out. | |
| """ | |
| q, args = f"DELETE FROM {_t(site)} WHERE user_id = %s", [int(user_id)] | |
| if kind is not None: | |
| q += " AND kind = %s"; args.append(kind) | |
| if ref is not None: | |
| q += " AND ref = %s"; args.append(int(ref)) | |
| _, dropped = store.write(q, tuple(args), site) | |
| return dropped | |
| def fenced(site): | |
| """Every narrowed account on one site, for the owner's view of who is.""" | |
| rows = store.query( | |
| f"SELECT user_id, kind, ref FROM {_t(site)} ORDER BY user_id", (), site) | |
| out = {} | |
| for r in rows: | |
| out.setdefault(r["user_id"], []).append({"kind": r["kind"], "ref": r["ref"]}) | |
| return out | |