Skip to content

Permissions matrix

This page is the mechanical truth about authorization in the eBiz platform API: what is checked, where, and what each token grants. Verified against API module v3_7_3 source code.

The platform does not use dotted permission keys (there is no deal.create / rebate.edit style catalogue anywhere in the code). Authorization is enforced in three layers:

  1. Authentication firewall — a valid JWT is required for all API events except an explicit public whitelist.
  2. Role tokens — programmatic checks against the roles carried on the authenticated request (request.sIsUserInRole(...)).
  3. Object-level ACL — per-object view / edit / delete / admin privileges stored in the security database table and evaluated by securityService.hasPermission(...).

Layer 1: Authentication firewall

Configured in the app security config (firewall rules, lines 858–947). Two rules cover event patterns v3_* and api.*; any matching event requires a valid JWT (HS512, 600-minute expiration, Authorization: Bearer or x-auth-token header). Failing requests are redirected to /login.

Public (no authentication) events are those on the two whitelists (app config :871 and :879), including: login.*, api.auth.*, signup.* / api.signup.*, healthcheck.*, admin.*, avatar.* / api.avatar.*, campaign.view / campaign.rd* / campaign.webhook / api.campaign.rd / api.campaign.track, blog.viewDigest, documents.thumbnail / api.documents.thumbnail, pim.image / api.pim.image, quickbooks.*, indexer.* / api.indexer.*, site.getSite, whatif.linearReport, audit.userReport, cbswagger.*, api.test.*, api.email.*, api.import.*, api.msgraph.*, api.tickets.checkMail, socialite.*, error.*, api.index.*.

Both rules also declare roles: "view" and permissions: "view".

Handler annotation security is enabled (handlerAnnotationSecurity: true, app config :914) but no handler in any API version uses a secured= annotation, so there is no per-action declarative permission gating.

Layer 2: Role tokens

On every authenticated request, request.roles is built by interceptors/eunify (the JWT-authentication interceptor, lines 250–320) as the union of:

  • the user's company known-as name and company name
  • member if the user's company is COMPANYTYPE 3, supplier if COMPANYTYPE 2, both if COMPANYTYPE 4 (v3_7_3/models/userService:288–297)
  • the name of each database role assigned to the user
  • the user's email address (lowercased)
  • each of the user's contact tags

Handlers and models test membership with request.sIsUserInRole("token") (exact match) or request.sIsUserInAnyRole("a,b,c") (any of a list). Static tokens checked in v3_7_3 handlers and models:

Role token Guards Representative check sites
member Member-vs-supplier branching: search scope, document visibility, figures visibility, blog, sign-off, rebate views handlers/search:32, handlers/documents:695, handlers/signoff:141, handlers/rebate:363, handlers/audit:55
admin Admin-only create paths, category-security bypass, extended search handlers/documents:48, handlers/search:179, handlers/psa:550, handlers/spend:552
superusers Superuser dashboards, aggregate figures, security admin, and the ACL fallback for edit/delete/admin (see layer 3) handlers/dashboard:48, handlers/rebate:783, handlers/security:15, models/securityService:386
superuser (singular) Campaign edit/admin/delete capability flags handlers/campaign:349–351
supplier Supplier-specific rebate logic handlers/rebate:363,422,461
figures Exposure of PAYABLE vs MEMBERPAYABLE rebate figures handlers/rebate:1016,1031,1074
figuresEntry Turnover/figures data entry handlers/spend:309,424
edit Spend edit rights (checked as edit or any-of edit,admin) handlers/spend:424
turnover, ownfigures Spend/turnover access (any-of check with figuresEntry) handlers/spend:309
rebates Rebate access handlers/rebate:486
categoryRestrictions Category restriction behavior checked in v3_7_3 handlers/models (2 sites)
staff, ebiz Internal/staff branches (ebiz gates a debug dump only) handlers/documents:1056

Some report/form definitions also check dynamic tokens read from XML element attributes (sIsUserInRole("#elem.permissions[...]#")), so the static list above is not exhaustive for XML-driven surfaces.

Because role tokens include company names, emails, and tags, the full set of possible tokens is per-site data, not a static enum. The table above lists only the tokens hard-coded in application logic.

Layer 3: Object-level ACL (view / edit / delete / admin)

securityService.hasPermission(priviledge, objectID, objectType) (v3_7_3/models/securityService:375–421) evaluates rules from the security DB table (columns: priviledge, role, pType of any|must|not, relatedID, securityAgainst, siteID), falling back to request.siteConfig.defaultSecurity[objectType] when an object has no rows for a privilege.

Evaluation semantics (as implemented):

  • If no rules exist for the requested privilege: view is granted to any authenticated user (default-open), and edit/delete/admin are granted only to the superusers role (securityService:382–388).
  • Otherwise the privilege is granted when the user matches at least one any role, all must roles, and none of the not roles. A grant can only originate from an any match — rules with only must/not entries evaluate to denied.
  • Role entries beginning with $ are skipped during evaluation.

The same rules are enforced inside list queries via a SQL hasPermission(...) function in HAVING clauses, so users only see rows they can view.

Object types covered, and where each is checked:

Object type Blocking server-side check Capability flags in response List filtering (SQL)
agreement (deals/PSAs) handlers/psa:55 aborts if no view handlers/psa:93–99 psaService, export, figuresService
document handlers/documents:368–371 documentService
category (DMS) handlers/documents:57–60,238–241 documentService
calendar handlers/calendar:240–243 calendarService
blog (news) handlers/blog:229 guards delete handlers/blog:212–217
tender handlers/tender:21–24
survey handlers/survey:469–472 surveyService

Important: most handler-level hasPermission calls populate a permissions struct in the JSON response — capability hints the front end uses to show or hide actions. They do not block the request. The only confirmed hard server-side gates are the agreement view abort (psa:55) and the blog delete guard (blog:229); everything else relies on list filtering plus front-end enforcement.

ACL rules are administered through handlers/security: getBasicPermissions (groups available to the current user, gated by superusers membership), getPermissions, and savePermissions.

sitePermissions (proof of concept)

A per-site deal-security field (sitePermissions) is being added to Elasticsearch documents and query filters (securityService:164–294, projected in psaService:1246, companyService:884, contactService:947). During the POC it is additive and fail-open, and is not yet a load-bearing authorization layer.

What this page is not

  • It is not a catalogue of user-manageable permission toggles — role tokens come from database group/role names per site; group-specific role conventions belong in tenant-notes/ pages.
  • It does not document authentication setup (login flows, JWT issuance) beyond the firewall summary above.
  • userService.hasPermission() (v3_7_3/models/userService:53–63) exists but has no callers — it is interface-satisfying code, not part of the authorization model described here.