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 | Tag1–Tag4 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:
- Sign in as that user with
DoLogin. - Fetch the report with
GetReportMetadataById. - Call
GetSQLand confirm the WHERE clause contains the tenant's value. - Run the report with
GetReportDataand 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.