permissions.md 5.76 KB

How to set permissions

xly's permission model has two complementary tables. They answer different questions and live at different layers; reading the wiki correctly here matters because the names look superficially similar.

The two tables

Table Granularity What it stores Loaded when
gdsjurisdiction per module the catalog of actions/buttons that exist on each module (BtnAdd, BtnUpd, BtnDel, BtnPrint, …) every getModelBysId call (skipped for ADMIN — see Slice 1)
sysjurisdiction per role (and optionally per user) the grants — which role (or user) can perform which action on which module resolved on getModelBysId for the user's role; BusinessGdsconfigformsServiceImpl.getJurisdictionData()

The mental model: gdsjurisdiction is the menu of permission items the framework knows about per module. sysjurisdiction is the seating chart — who's allowed to use which item.

What gdsjurisdiction contains

A tree of permission items keyed by module:

Column Meaning
sParentId the gdsmodule.sId this permission item belongs to
sName the action key — e.g., BtnAdd, BtnUpd, BtnDel, BtnPrint
sChinese / sEnglish / sBig5 display label (e.g., 新增)
iOrder sort order in the permission UI
sBrandsId / sSubsidiaryId tenant scope

gdsjurisdiction does not contain a role column or a user column — it is purely a catalog of which buttons/actions exist for each module. Every module that wants any permission control has rows here.

What sysjurisdiction contains

The actual grants:

Column Meaning
sParentId the module sId the grant is for
sParent2Id the parent module (group/category) — used by the permission UI for tree rendering
sJurisdictionClassifyId role / permission-class id (FK to sisjurisdictionclassify)
sUserId user id, for per-user overrides — empty means "role-level grant"
sAction the granted action — e.g., BtnBsOperation.BtnDesignFunction
sKey composite path key, encoding the module hierarchy that owns the action

sysjurisdiction is the volume table of the permission system: one grant per (role-or-user × module × action). Roles live in sisjurisdictionclassify. A deployment may use only role-level grants (every sUserId empty), only user-level grants, or a mix.

The runtime in BusinessGdsconfigformsServiceImpl.getJurisdictionData (line 212) chooses between user-level and role-level grants:

Boolean hasCheckUser = checkUserJurisdiction(map);
if (hasCheckUser) {
    return this.getGroupJurisdictionUserNew(map);  // per-user overrides
} else {
    return this.getGroupJurisdictionNew(map);      // role-only fallback
}

If the calling user has any row in sysjurisdiction with their sUserId set, the user path wins; otherwise the role-only path is used.

How permissions are loaded on a request

Pseudocode for the relevant slice of BusinessBaseServiceImpl.getModelBysId (xlyBusinessService/.../BusinessBaseServiceImpl.java:196-198):

List<Map<String, Object>> jList = new ArrayList<>();
if (!UserType.ADMIN.getTypeName().equals(map.get("sUserType"))) {
    jList = businessGdsconfigformsService.getJurisdictionData(qMap);
}
returnMap.put("gdsjurisdiction", jList);

ADMIN bypasses permissions entirely and gets an empty grant list, which the SPA interprets as "show every button". Non-admin users get the filtered list of actions resolvable for their role + module + tenant. The SPA enables/disables buttons and toggles columns based on what came back.

How to grant a permission

For a new module, the typical path is:

  1. Define the module + form (recipe).
  2. Insert gdsjurisdiction rows for each button/action the module exposes (BtnAdd, BtnUpd, BtnPrint, …). One row per action; sParentId = your module's sId.
  3. Decide which roles get which actions, and insert sysjurisdiction rows: sParentId = module sId, sJurisdictionClassifyId = role sId, sAction = action key. Leave sUserId empty for role-level grants.
  4. (Or override at the user level by inserting rows with sUserId set; per-user overrides shadow the role-level grants for that user.)
  5. Save through BACK so cache invalidation propagates (the runtime caches getJurisdictionData per (sUserId, sModelsId, sBrandsId, sSubsidiaryId) — see the @Cacheable annotation at line 209).

ADMIN as a special case

The hardcoded ADMIN bypass is a real escape hatch. Every deployment has at least one ADMIN account by convention. From a security audit perspective, ADMIN access should be tightly controlled and audited externally — the framework offers no within-app constraint on what an ADMIN can do.

Cross-tenant boundary is not permissions

Worth saying explicitly: gdsjurisdiction + sysjurisdiction enforce within-tenant permissions. The cross-tenant boundary (one company can't read another company's rows) is enforced by the multi-tenant scoping — automatic sBrandsId/sSubsidiaryId injection — not by jurisdiction rules.

Note on plat_base_authority_*

Three tables under plat_base_authority (plat_base_authority, plat_base_authority_button_type, plat_base_authority_data_type) exist in the schema and look like they ought to be lookups for button-type and data-permission-type identifiers. They belong to the out-of-scope xlyPlat* B2B platform layer, not to the framework's own permission flow. Don't write referencing them when documenting framework permissions — the jurisdiction surface above is self-contained.