Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ jobs:
matrix:
node-version: [ 22.x ]
python-version: [ 3.11 ]
use_tesseract_sql_planner: [ true, false ]
fail-fast: false

steps:
Expand Down Expand Up @@ -605,6 +606,8 @@ jobs:
chmod +x ./rust/cubestore/downloaded/latest/bin/cubestored
- name: Run Integration smoke tests
timeout-minutes: 30
env:
CUBEJS_TESSERACT_SQL_PLANNER: ${{ matrix.use_tesseract_sql_planner }}
run: ./.github/actions/smoke.sh

docker-image-latest-set-tag:
Expand Down
194 changes: 190 additions & 4 deletions docs/content/product/auth/data-access-policies.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Access policies

Access policies provide a holistic mechanism to manage [member-level](#member-level-access)
and [row-level](#row-level-access) security for different user groups.
You can define access control rules in data model files, allowing for an organized
and maintainable approach to security.
Access policies provide a holistic mechanism to manage [member-level](#member-level-access),
[row-level](#row-level-access) security, and [data masking](#data-masking) for
different user groups. You can define access control rules in data model files,
allowing for an organized and maintainable approach to security.

## Policies

Expand Down Expand Up @@ -116,6 +116,136 @@ filtered by the row-level security rules of both views.

</InfoBox>

### Data masking

With data masking, you can return masked values for restricted members instead
of denying access entirely. Users who don't have full access to a member will
see a transformed value (e.g., `***`, `-1`, `NULL`) rather than receiving an error.

To use data masking, define a [`mask` parameter][ref-ref-mask-dim] on dimensions
or measures, and add `member_masking` to your access policy alongside `member_level`.
Members in `member_level` get real values; members not in `member_level` but in
`member_masking` get masked values; members in neither are denied.

<CodeTabs>

```yaml
cubes:
- name: orders
# ...

dimensions:
- name: status
sql: status
type: string

- name: secret_code
sql: secret_code
type: string
mask:
sql: "CONCAT('***', RIGHT({CUBE}.secret_code, 3))"

- name: revenue
sql: revenue
type: number
mask: -1

measures:
- name: count
type: count
mask: 0

access_policy:
- group: manager
member_level:
includes:
- status
- count
member_masking:
includes: "*"
```

```javascript
cube(`orders`, {
// ...

dimensions: {
status: {
sql: `status`,
type: `string`
},

secret_code: {
sql: `secret_code`,
type: `string`,
mask: {
sql: `CONCAT('***', RIGHT(${CUBE}.secret_code, 3))`
}
},

revenue: {
sql: `revenue`,
type: `number`,
mask: -1
}
},

measures: {
count: {
type: `count`,
mask: 0
}
},

access_policy: [
{
group: `manager`,
member_level: {
includes: [`status`, `count`]
},
member_masking: {
includes: `*`
}
}
]
})
```

</CodeTabs>

With this policy, users in the `manager` group will see:

| Member | Value |
| --- | --- |
| `status` | Real value (full access via `member_level`) |
| `count` | Real value (full access via `member_level`) |
| `secret_code` | Masked via SQL: `***xyz` |
| `revenue` | Masked: `-1` |

If no `mask` is defined on a member, the default mask value is `NULL`. You can
customize defaults with the `CUBEJS_ACCESS_POLICY_MASK_STRING`,
`CUBEJS_ACCESS_POLICY_MASK_NUMBER`, `CUBEJS_ACCESS_POLICY_MASK_BOOLEAN`, and
`CUBEJS_ACCESS_POLICY_MASK_TIME` environment variables.

<WarningBox>

SQL masks (`mask: { sql: "..." }`) on measures are not applied in ungrouped
queries (e.g., `SELECT *` via the SQL API), because SQL mask expressions
typically reference columns that are not meaningful in a per-row context.
Static masks (`mask: -1`, `mask: 0`) are applied in all cases.

If you need to mask a measure in ungrouped queries with a dynamic expression,
define it as a dimension with an SQL mask instead, and reference that masked
dimension in your query.

</WarningBox>

_When querying a view,_ data masking follows the same pattern as row-level
security: masking rules from both the view and relevant cubes are applied.

For more details on available parameters, check out the
[`member_masking` reference][ref-ref-dap-masking].

## Common patterns

### Restrict access to specific groups
Expand Down Expand Up @@ -252,6 +382,60 @@ view(`deals_view`, {

</CodeTabs>

### Mask sensitive members

You can mask sensitive members for most users while granting full access to
privileged groups:

<CodeTabs>

```yaml
views:
- name: orders_view
# ...

access_policy:
# Default: all members masked
- group: "*"
member_level:
includes: []
member_masking:
includes: "*"

# Admins: full access
- group: admin
member_level:
includes: "*"
```

```javascript
view(`orders_view`, {
// ...

access_policy: [
{
// Default: all members masked
group: `*`,
member_level: {
includes: []
},
member_masking: {
includes: `*`
}
},
{
// Admins: full access
group: `admin`,
member_level: {
includes: `*`
}
}
]
})
```

</CodeTabs>

### Mandatory filters

You can apply mandatory row-level filters to specific groups to ensure they only see data matching certain criteria:
Expand Down Expand Up @@ -379,4 +563,6 @@ cube(`orders`, {
[ref-sec-ctx]: /product/auth/context
[ref-ref-dap]: /product/data-modeling/reference/data-access-policies
[ref-ref-dap-role]: /product/data-modeling/reference/data-access-policies#role
[ref-ref-dap-masking]: /product/data-modeling/reference/data-access-policies#member-masking
[ref-ref-mask-dim]: /product/data-modeling/reference/dimensions#mask
[ref-core-data-apis]: /product/apis-integrations/core-data-apis
10 changes: 9 additions & 1 deletion docs/content/product/auth/member-level-security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ Access policies also respect member-level security restrictions configured via
`public` parameters. For more details, see the [access policies
reference][ref-dap-ref].

<InfoBox>

If you want to return masked values for restricted members instead of hiding
them entirely, see [data masking][ref-data-masking] in access policies.

</InfoBox>


[ref-data-modeling-concepts]: /product/data-modeling/concepts
[ref-apis]: /product/apis-integrations
Expand All @@ -150,4 +157,5 @@ reference][ref-dap-ref].
[ref-hierarchies-public]: /product/data-modeling/reference/hierarchies#public
[ref-segments-public]: /product/data-modeling/reference/segments#public
[ref-dynamic-data-modeling]: /product/data-modeling/dynamic
[ref-security-context]: /product/auth/context
[ref-security-context]: /product/auth/context
[ref-data-masking]: /product/auth/data-access-policies#data-masking
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ can be configured using the following parameters:
takes effect.
- [`member_level`](#member-level) and [`row_level`](#row-level) parameters are used
to configure [member-level][ref-dap-mls] and [row-level][ref-dap-rls] access.
- [`member_masking`](#member-masking) can be optionally used to configure
[data masking][ref-dap-masking] for members not included in `member_level`.

<InfoBox>

Expand Down Expand Up @@ -295,6 +297,60 @@ Note that access policies also respect [member-level security][ref-mls] restrict
configured via `public` parameters. See [member-level access][ref-dap-mls] to
learn more about policy evaluation.

### `member_masking`

The optional `member_masking` parameter, when present, configures [data
masking][ref-dap-masking] for a policy. It requires `member_level` to be
defined in the same policy.

Members included in `member_level` get full access. Members not in
`member_level` but included in `member_masking` return masked values instead
of being denied. The mask value is defined by the [`mask` parameter][ref-mask-dim]
on each dimension or measure.

You can provide a list of maskable members with `includes`, or a list of
non-maskable members with `excludes`. Use `"*"` as a shorthand for all members.

<CodeTabs>

```yaml
cubes:
- name: orders
# ...

access_policy:
- group: manager
member_level:
includes:
- status
- count
member_masking:
includes: "*"
```

```javascript
cube(`orders`, {
// ...

access_policy: [
{
group: `manager`,
member_level: {
includes: [
`status`,
`count`
]
},
member_masking: {
includes: `*`
}
}
]
})
```

</CodeTabs>

### `row_level`

The optional `row_level` parameter, when present, configures [row-level
Expand Down Expand Up @@ -406,6 +462,8 @@ cube(`orders`, {
[ref-rls]: /product/auth/row-level-security
[ref-sec-ctx]: /product/auth/context
[ref-core-data-apis]: /product/apis-integrations/core-data-apis
[ref-dap-masking]: /product/auth/data-access-policies#data-masking
[ref-mask-dim]: /product/data-modeling/reference/dimensions#mask
[ref-rest-query-filters]: /product/apis-integrations/rest-api/query-format#filters-format
[ref-rest-query-ops]: /product/apis-integrations/rest-api/query-format#filters-operators
[ref-rest-boolean-ops]: /product/apis-integrations/rest-api/query-format#boolean-logical-operators
Loading
Loading