feat(ip): protect persona 'formula' — secret fields only super_admin sees/edits
IP protection so casual copying yields inferior results: - SECRET_PERSONA_FIELDS (pains/objections/negotiation_levers/opener/tolerance + pain rootCause/resolutionConditions): only super_admin can view/edit them. - list_personas/get_persona/update_persona/get_group strip these for role=admin (and hide sales_kit + pain-fit report from admins too). - update_persona rejects admin attempts to set secret fields (403). - PersonaForm hides the 'การขาย' recipe section for non-super-admin (shows locked note); auth.isSuperAdmin getter added. Rebuilt dist. Added test_ip_protection.
This commit is contained in:
61
backend/scripts/test_ip_protection.py
Normal file
61
backend/scripts/test_ip_protection.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Test: IP protection — admin cannot see/edit secret persona fields; super_admin can."""
|
||||
import os, sys, tempfile, warnings
|
||||
from pathlib import Path
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
BACKEND = str(Path(__file__).resolve().parents[1])
|
||||
sys.path.insert(0, BACKEND)
|
||||
|
||||
from app.factory import create_app
|
||||
from app.config import Config
|
||||
|
||||
td = tempfile.mkdtemp()
|
||||
Config.DATA_DIR = Path(td)
|
||||
sys.path.insert(0, BACKEND + "/scripts")
|
||||
from mock_llm import MockLLM
|
||||
|
||||
app = create_app()
|
||||
app.extensions["llm"] = MockLLM()
|
||||
C = app.test_client()
|
||||
|
||||
def tok(u, p): return C.post("/api/auth/login", json={"username": u, "password": p}).get_json()["token"]
|
||||
|
||||
AT = tok("admin", "1234"); AH = {"Authorization": f"Bearer {AT}"}
|
||||
C.post("/api/auth/setup", headers=AH, json={"username": "admin", "email": "a@b.co", "password": "newpass"})
|
||||
AT = tok("admin", "newpass"); AH = {"Authorization": f"Bearer {AT}"}
|
||||
|
||||
gid = C.post("/api/groups", headers=AH, json={"product": "CRM", "segment": "SME", "channel": "line", "language": "th"}).get_json()["group"]["id"]
|
||||
C.post(f"/api/groups/{gid}/analyze", headers=AH)
|
||||
personas = C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"] # super_admin sees all
|
||||
assert personas and "pains" in personas[0], "super_admin should see secret fields"
|
||||
print("[ok] super_admin sees secret fields")
|
||||
|
||||
# create an admin (not super) user
|
||||
C.post("/api/admin/users", headers=AH, json={"username": "adm", "name": "Adm", "password": "pppp", "role": "admin"})
|
||||
AT2 = tok("adm", "pppp"); AH2 = {"Authorization": f"Bearer {AT2}"}
|
||||
pid = personas[0]["id"]
|
||||
|
||||
# admin list_personas: secret fields stripped
|
||||
admin_list = C.get(f"/api/groups/{gid}/personas", headers=AH2).get_json()["personas"]
|
||||
assert "pains" not in admin_list[0] and "tolerance" not in admin_list[0], "admin list should strip secrets"
|
||||
print("[ok] admin list strips secret fields")
|
||||
|
||||
# admin update with a secret field -> 403
|
||||
r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH2, json={"name": "X", "tolerance": 1})
|
||||
assert r.status_code == 403, r.get_json()
|
||||
print("[ok] admin cannot set secret field (403)")
|
||||
|
||||
# admin update of non-secret field -> ok, but response still strips secrets
|
||||
r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH2, json={"name": "Edited Name"})
|
||||
assert r.status_code == 200, r.get_json()
|
||||
body = r.get_json()["persona"]
|
||||
assert body["name"] == "Edited Name"
|
||||
assert "pains" not in body, "admin update response should strip secrets"
|
||||
print("[ok] admin can edit non-secret field; response strips secrets")
|
||||
|
||||
# super_admin can update secret field
|
||||
r = C.put(f"/api/groups/{gid}/personas/{pid}", headers=AH, json={"tolerance": 5})
|
||||
assert r.status_code == 200, r.get_json()
|
||||
print("[ok] super_admin can edit secret field")
|
||||
|
||||
print("ALL IP-PROTECTION TESTS PASSED")
|
||||
Reference in New Issue
Block a user