← All 10 studies
Field guide · Data accessViral Nation2026

One Secure Data Policy

How a four-switch Sigma policy replaced hard-coded workbook access across hundreds of clients with composable organization, agency, division, and brand permissions.

Methods and toolsSigmaRow-level securityTeamsUser attributesBoolean logicImpersonation

Every new dashboard user created an access puzzle. Hundreds of clients sat inside a shared reporting environment. Some humans worked for one brand. Some worked for a parent company and needed every brand below it. Some were third-party agency partners. Internal agency teams needed a broad view. Before and during the move from Looker to Sigma, access was tied to individual emails, workbooks, and dashboards. Each request meant another manual choice about which copy to share.

That process was slow and hard to reason about. A new workbook could miss an existing permission. A person who changed roles could keep an old share. A parent-brand user might need many separate dashboards. The worst mistake was not an inconvenience. It was showing one client another client's campaign data. As the client list grew, a permission model based on individual files and humans became a map no one could hold in their head.

I designed a row-level security system for Sigma around four access switches: organization, agency, division, and brand. A user can receive one value, several values, or an intentionally open setting at each level. Teams package common combinations. One formula evaluates the final attributes against every row. The result is a policy that can express a narrow brand view, a whole parent company, a partner agency portfolio, or approved internal access without making another dashboard copy.

This article explains the mechanism, why the formula is powerful, where it can fail, and how to test it. The central lesson is simple: sharing a dashboard and authorizing its data are two different jobs.

A dashboard has two doors.

The first door controls whether a person can open the product. Sigma account types, workbook permissions, and homepage assignments handle that job. The second door controls which rows appear after the workbook opens. Row-level security, usually shortened to RLS, handles that job. It tests the current user's approved scope against the fields on each row.

Workbook sharing alone is too broad for a shared reporting product. If a workbook contains many organizations, permission to open it can reveal all of them unless another rule filters the data. Copying one workbook per client avoids that specific problem, but creates many products to maintain. Every metric fix, layout change, and new feature must be copied again. The access problem becomes a software maintenance problem.

User signs inCan open workbook?Which rows pass RLS?Visible dashboard
Opening the workbook and seeing a row are separate decisions. Both must pass before a user receives data.

One shared product needs a shared policy.

A reusable dashboard works only when access rules move out of workbook copies and into a stable policy. The dashboard can then evolve once. Each user receives a different data view because the query applies their attributes at run time. This separates product maintenance from access scope: one team owns the dashboard, while access admins manage approved combinations of values.

Model access as four independent switches.

The access hierarchy had more than one useful level. Organization represented the client or parent company. Agency represented an agency boundary. Division represented a business unit. Brand represented the narrowest common client or campaign group. A person might need any combination of them.

Hard-coded roles such as “client viewer” or “agency viewer” cannot express every real case. A parent-company analyst may need all brands in one organization. A partner may need two brands across one agency. An internal lead may need everything. The four-switch model does not guess a job title. It describes the data boundary directly.

Each switch answers one access question
SwitchRow fieldQuestion
OrganizationOrganizationWhich client or parent company is allowed?
AgencyAgencyWhich agency boundary is allowed?
DivisionDivisionWhich business unit is allowed?
BrandBrandWhich brand or campaign group is allowed?

Combinations create the useful power.

The switches are independent, so the policy does not need a custom branch for every kind of user. Organization set and Brand blank means every brand inside one organization. Organization and Brand both set means only the listed brands inside that organization. Agency and Division can narrow the same row further. Several allowed values can live inside one switch as a comma-separated list.

This is composition: small rules combine into a larger rule. Four switches can express many access shapes without a long list of named roles. The price is that the meaning of an open switch must be exact and every field must use the same hierarchy values.

The formula is a compact policy engine.

Each switch uses the same Boolean rule. Boolean logic is logic built from true and false values. For one dimension, the row passes when the user's attribute is blank or the row value appears in the user's allowed list. All four dimensions are joined with and, so every active restriction must pass.

One dimension
Attribute is blankOR row is in allowed list
then
Complete rule
Organization passesAND agency passesAND division passesAND brand passes
One switch is open when its attribute is blank. When it is set, the row must match an allowed value. All four switch results must pass.
(
  Len(CurrentUserAttributeText("rls_organization")) = 0
  OR ArrayContains(SplitToArray(CurrentUserAttributeText("rls_organization"), ", "), [Organization])
)
AND (
  Len(CurrentUserAttributeText("rls_agency")) = 0
  OR ArrayContains(SplitToArray(CurrentUserAttributeText("rls_agency"), ", "), [Agency])
)
AND (
  Len(CurrentUserAttributeText("rls_division")) = 0
  OR ArrayContains(SplitToArray(CurrentUserAttributeText("rls_division"), ", "), [Division])
)
AND (
  Len(CurrentUserAttributeText("rls_brand")) = 0
  OR ArrayContains(SplitToArray(CurrentUserAttributeText("rls_brand"), ", "), [Brand])
)

In Sigma, CurrentUserAttributeText reads the current user's value. SplitToArray turns text such as “Brand A, Brand B” into a list. ArrayContains checks whether the row's field appears in that list. The same pattern repeats four times, which makes the policy easy to inspect and extend.

Blank is a wildcard, not a denial.

This is the most important security fact in the design. A blank attribute opens that dimension. It does not block the user. If Organization is set but Brand is blank, the user sees all brands that pass the Organization rule. If every attribute is blank, the formula applies no row limit at all. That is useful for approved internal leaders, but dangerous for an external user whose team was configured incorrectly.

The formula is therefore compact, not self-sufficient. The provisioning workflow must guarantee that every external user receives at least the required boundary. Blanks must be reviewed as explicit broad-access choices. A safer future version could add a separate user-class attribute and deny external users when no organization is set. The current implementation protects broad access through admin process and verification.

Teams turn policy into reusable access packages.

Assigning four attributes by hand to every user would still be hard to maintain. Sigma teams provide the reusable layer. A client team can assign an organization. A smaller campaign or brand team can add a brand value. A homepage team can send external users to the right landing page. Users inherit the final attributes from their assigned teams.

This changes the unit of work. The old unit was “share these workbooks with this email.” The new unit is “add this person to the approved policy groups.” When a team's scope changes, the access owner updates the team rather than finding every workbook and every person that copied the old choice.

Approved requestAssigned teamsFinal user attributesFour-switch formulaVisible rows
Teams hold repeatable policy choices. User attributes carry those choices into the row filter every time the dashboard runs.

Inheritance must be inspected, not assumed.

A person can belong to several teams. More than one team may assign a value to the same attribute. The safe rule is to inspect the final value on the user after every change. Do not assume which team wins or how values combine. The data filter sees the resolved attribute, not the administrator's intention.

Exact text also matters. The formula compares the attribute value with the data value. Different spelling, spaces, punctuation, or old client names can produce no rows or the wrong boundary. The campaign hierarchy must be the shared source for both the warehouse fields and the access-team values.

Access scope should follow the business hierarchy.

Good row security starts with a clear data model. Organization, agency, division, and brand must mean the same thing in every protected table. If one source calls a parent company an organization while another stores it as a brand, the same policy cannot protect both without a mapping layer.

The four-switch design matches how the business grants access. A narrow client user receives Organization plus Brand. A parent-brand user receives Organization while Brand stays intentionally open. A partner agency can receive Agency plus selected Organizations or Brands. An approved internal user can receive a broad setting. These are not separate formulas. They are different inputs to the same one.

Common access shapes produced by the same formula
User needExample active switchesResult
One brandOrganization + BrandOnly that brand inside that client
Parent companyOrganizationAll agencies, divisions, and brands inside it
Partner agencyAgency + selected valuesOnly the approved partner portfolio
Approved internal leaderIntentionally broad attributesFull reporting view

Multiple values need a stable protocol.

The implementation splits lists on comma plus space. “Brand A, Brand B” works as two values. “Brand A,Brand B” may not. That tiny formatting rule is part of the security protocol. A better long-term system would prefer structured arrays or a policy table over encoded text, but Sigma user attributes expose text cleanly and made this pattern practical. The runbook records the exact separator so administrators can use it consistently.

Row security fails at the seams.

The formula can be correct and the dashboard can still be unsafe. A new data source may omit the filter. A field may be renamed. A team may assign the wrong spelling. An external user may receive a blank Organization. A dashboard tab may use an unprotected element. Security must cover the whole data path, not only the place where the formula was first written.

Common failure modes and their controls
FailureVisible symptomControl
Blank external attributeUser sees a broader scope than intendedReview blanks as explicit grants
Value mismatchUser sees no data or misses approved rowsUse one hierarchy and exact text
Unprotected second sourceOne section leaks wider dataInventory and test every data element
Stale team membershipOld access survives a role changeTrack grants and review active users
Only positive testingApproved rows appear, but extra rows go unnoticedTest a known blocked case

RLS is not the whole security system.

Account type still controls what a person can do. Workbook permission still controls what they can open. Homepage assignment still controls where they land. Warehouse permissions still protect the source. Row-level security narrows the data inside the reporting product. Each layer has a different job, and removing one layer makes the others carry risk they were not designed to hold.

Test what appears and what stays hidden.

A successful page load proves almost nothing about access. The user may see their own client and five others. Every grant needs a positive test and a negative test. The positive test proves useful access. The negative test proves the boundary.

Sigma's impersonation tool makes this practical. An admin can enter the user's view without asking for their password. The runbook checks the homepage, campaign selector, dashboard data, approved brands, a known unapproved brand, and a known organization outside the user's client. It repeats the check across important tabs and data sources, then stops impersonation and records the result.

  1. Confirm the approved requestName the person, business need, scope, approver, and whether access is broad or limited.
  2. Resolve exact hierarchy valuesUse the same organization, agency, division, and brand text stored in the data.
  3. Assign reusable teamsChoose the client, brand, partner, and homepage groups that produce the intended attributes.
  4. Inspect final attributesCheck the resolved user values. Treat every blank as an access choice.
  5. Impersonate the userConfirm the right homepage and at least one approved row set.
  6. Try a known blocked caseSearch for a brand or organization the person must not see.
  7. Check every protected sourceDo not assume one passing tab proves the rest of the dashboard.
  8. Record the grantSave the account type, teams, access level, attribute values, invite state, and last test date.

Provisioning is part of the security design.

The tracker is not busywork. It records what was approved, what was configured, whether an invite is still pending, and when the view was last tested. Access is not complete when an email is sent. It is complete when the active user has the right attributes, the allowed view works, the blocked view fails, and the live settings match the record.

What the evidence supports

The repository contains the Sigma formula, a 1,008-line access runbook, a documented production location, the four active attributes, worked broad and limited examples, and a test protocol that checks both allowed and blocked data. The policy runs on one shared client dashboard and supports client, parent-brand, agency, division, brand, and broad internal access shapes.

The history across hundreds of clients is a direct operating observation. It is not an audited count of active Sigma users. The evidence does not measure hours saved, disclosures prevented, or incident reduction. It also does not replace an independent security review. The strongest claim is architectural: one reusable formula and provisioning model replaced one-off workbook-level access logic with composable row scopes and a repeatable verification process.

4

row-level access dimensions

Repository backed
1 dashboard

shared across approved user scopes

Repository backed
Allow + block

both access paths tested

Repository backed
Tracked

access grants and checks recorded

Repository backed

The result was one reporting product that could serve very different audiences without making access a new dashboard project every time. A brand user could see one approved slice. A parent company could see its portfolio. A partner agency could receive its own combination. Internal teams could work across the full system. The four-switch formula made those permutations possible; teams made them reusable; impersonation and blocked-row tests made each grant reviewable. That is the difference between sharing files and designing access.