Contact us
Guides

Row-level data security

Use data tags and AppShield policies so one report returns different rows to different users — the mechanism behind multi-tenant separation.

Row-level security in Yurbi has two parts. A data tag holds a value that varies by group or by user. An AppShield policy applies a constraint that compares a column to that value. Together they let a single report serve everyone while returning only the rows each viewer is entitled to.

Nothing changes in the report itself. Constraints are applied when the report runs, so they cover the API, the interface, embeds, exports and scheduled delivery alike.

When to use which

Approach Value lives on Good for
Data tag, group-scoped A security group Tenants, regions, departments — anywhere a set of people shares one value
Data tag, user-scoped A single user Values that differ per person
Profile tag Tag1Tag4 on the user record Values you already hold in your own system and can set when creating the user

Profile tags are often simplest for a multi-tenant deployment, because the value can be set in the same call that creates the user.

1. Create a data tag

Start from NewDataTag, then save it with a value for each group:

{
  "sessionToken": "YOUR_SESSION_TOKEN",
  "datatag": {
    "ID": "", "Label": "tenantid", "TagGroup": "Tenancy",
    "DataTypeEnum": 1, "isGroup": true, "isUser": false,
    "isActive": true, "DefaultValue": "NONE", "Contacts": [], "Index": 0,
    "SecurityGroups": [
      { "LeftID": "", "RightID": "12",
        "RelationshipType": "tag_grp", "RelationType": "tag_grp",
        "Option1": "TENANT-A", "PermissionTypeEnum": 0 },
      { "LeftID": "", "RightID": "13",
        "RelationshipType": "tag_grp", "RelationType": "tag_grp",
        "Option1": "TENANT-B", "PermissionTypeEnum": 0 }
    ]
  }
}

Each SecurityGroups entry maps one group to one value: RightID is the group ID, Option1 is the value. Use Contacts the same way for per-user values.

DefaultValue applies to anyone with no assigned value. Set it to something that matches no rows, so a user who has been missed sees nothing rather than everything.

Label is the name the constraint refers to, written as /#tenantid#/.

Adding a tenant later means saving the tag again with an extra SecurityGroups entry. Read the current tag with GetAllDataTags first so you send back the entries that already exist.

2. Create the policy

Build it from NewPolicy so its collections are initialised, then save:

{
  "sessionToken": "YOUR_SESSION_TOKEN",
  "policy": {
    "id": null,
    "Name": "Tenant row-level security",
    "Description": "Every query is constrained to the caller's tenant",
    "Constraints": [], "Groups": [], "Users": [], "isActive": false
  },
  "isDeepSave": true,
  "groups": [], "users": []
}

A constraint targets one application and report type, and compares a column to the tag:

customer_id  =  /#tenantid#/

Use /#tag1#/ instead when the value is a profile tag on the user record.

3. Assign and activate

Assign the policy to All Users so every account is constrained and the tag decides what each one sees. Assigning to individual tenant groups also works, but means attaching each new group as you create it.

AddGroup and RemoveGroup adjust assignments on an existing policy without rewriting it.

Set isActive true when you are ready to enforce the policy. Leaving it false while you verify the constraint lets you save and review it first.

Verifying a policy

The most direct check is to run a report as a user in the constrained group and inspect the SQL:

  1. Sign in as that user with DoLogin.
  2. Fetch the report with GetReportMetadataById.
  3. Call GetSQL and confirm the WHERE clause contains the tenant's value.
  4. Run the report with GetReportData and confirm the rows.

Test with two tenants and a user in neither. The third case is the one that catches a DefaultValue set too permissively.

Notes

Policies apply everywhere. A constrained report returns the same restricted rows through the API, the interface, an embed and a scheduled email. There is no separate configuration per channel.

Keep tenants in separate groups. A user in two tenant groups may match more than one tag value. One tenant group per user keeps the constraint unambiguous — see Multi-tenant provisioning.

The super-admin account is not constrained. The built-in admin bypasses these checks, so verify policies with an ordinary user account.

Endpoints used