diff --git a/customer_management/init_perm.py b/customer_management/init_perm.py new file mode 100644 index 0000000..796e3d9 --- /dev/null +++ b/customer_management/init_perm.py @@ -0,0 +1,104 @@ +"""Permission initialization utility for business modules. + +Scans wwwroot directory for .ui and .dspy files, registers them in +the permission table, and grants access to convention roles. + +Convention roles: + - any: anyone (no auth required) + - logined: any logged-in user + - customer.*: customer org arbitrary role + - owner.*: owner org arbitrary role + - owner.superuser: superuser for initialization +""" +import os +from appPublic.uniqueID import getID +from appPublic.log import debug +from sqlor.dbpools import DBPools + + +def collect_ui_dspy_paths(wwwroot_dir): + """Scan wwwroot directory and return list of URL paths for .ui/.dspy files.""" + paths = [] + for dirpath, _, filenames in os.walk(wwwroot_dir): + for fn in sorted(filenames): + if fn.endswith(('.ui', '.dspy')): + full = os.path.join(dirpath, fn) + rel = os.path.relpath(full, wwwroot_dir) + # Convert to URL path: module_name/relative/path + url = '/' + rel.replace(os.sep, '/') + # Skip base.ui (layout template, not a standalone page) + if fn == 'base.ui': + continue + paths.append(url) + return paths + + +async def ensure_permission(sor, path, permtype='page'): + """Ensure permission exists in database, return permid.""" + recs = await sor.R('permission', {'path': path}) + if recs: + return recs[0].id + permid = getID() + await sor.C('permission', { + 'id': permid, + 'name': path.split('/')[-1], + 'path': path, + 'permtype': permtype, + }) + return permid + + +async def ensure_role(sor, orgtypeid, name): + """Ensure role exists in database, return roleid.""" + recs = await sor.R('role', {'orgtypeid': orgtypeid, 'name': name}) + if recs: + return recs[0].id + roleid = getID() + await sor.C('role', { + 'id': roleid, + 'orgtypeid': orgtypeid, + 'name': name, + }) + return roleid + + +async def grant_permission(sor, roleid, permid): + """Grant permission to role if not already granted.""" + recs = await sor.R('rolepermission', {'roleid': roleid, 'permid': permid}) + if not recs: + await sor.C('rolepermission', { + 'id': getID(), + 'roleid': roleid, + 'permid': permid, + }) + + +async def init_module_permissions(dbname, module_name, wwwroot_dir): + """Initialize permissions for a business module. + + Scans wwwroot for .ui/.dspy files, registers paths in permission table, + and grants access to convention roles (logined, customer.*, owner.superuser). + """ + paths = collect_ui_dspy_paths(wwwroot_dir) + if not paths: + debug(f'{module_name}: no UI/DSPY paths found, skipping permission init') + return + + db = DBPools() + async with db.sqlorContext(dbname) as sor: + # Ensure convention roles exist + role_logined = await ensure_role(sor, '*', 'logined') + role_customer = await ensure_role(sor, 'customer', '*') + role_superuser = await ensure_role(sor, 'owner', 'superuser') + + # Register paths and grant permissions + for path in paths: + permid = await ensure_permission(sor, path) + # Grant to logined users + await grant_permission(sor, role_logined, permid) + # Grant to customer org users + await grant_permission(sor, role_customer, permid) + # Grant to superuser + await grant_permission(sor, role_superuser, permid) + + debug(f'{module_name}: registered {len(paths)} permissions')