Skip to Content

Access policies

Access policies provide a holistic mechanism to manage member-level, row-level security, and 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

You can define policies that target specific groups and contain member-level and (or) row-level security rules:

cubes: - name: orders # ... access_policy: # For the `manager` group, # allow access to all members # but filter rows by the user's country - group: manager member_level: includes: "*" row_level: filters: - member: country operator: equals values: [ "{ userAttributes.country }" ]
cube(`orders`, { // ... access_policy: [ { // For all groups, restrict access entirely group: `*`, member_level: { includes: [] } }, { // For the `manager` group, // allow access to all members // but filter rows by the user's country group: `manager`, member_level: { includes: `*` }, row_level: { filters: [ { member: `country`, operator: `equals`, values: [ userAttributes.country ] } ] } } ] })

While you can define access policies on both cubes and views, it is more common to define them on views.

For more details on available parameters, check out the access policies reference.

Policy evaluation

When processing a request, Cube will evaluate the access policies and combine them with relevant custom security rules, e.g., public parameters for member-level security and query_rewrite filters for row-level security.

If multiple access policies apply to a request, they are combined together using the OR semantics. For example, if a user has two groups with different policies, the user will get the union of the permissions in these policies.

Member-level access

Member-level security rules in access policies are combined together with public parameters of cube and view members using the AND semantics. Both will apply to the request.

When querying a view, member-level security rules defined in the view are not combined together with member-level security rules defined in relevant cubes. Only the ones from the view will apply to the request.

This is consistent with how column-level security works in SQL databases. If you have a view that exposes a subset of columns from a table, it doesnt matter if the columns in the table are public or not, the view will expose them anyway.

Row-level access

Row-level filters in access policies are combined together with filters defined using the query_rewrite configuration option. Both will apply to the request.

When querying a view, row-level filters defined in the view are combined together with row-level filters defined in relevant cubes. Both will apply to the request.

This is consistent with how row-level security works in SQL databases. If you have a view that exposes a subset of rows from another view, the result set will be filtered by the row-level security rules of both views.

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 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.

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: "*"
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: `*` } } ] })

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

MemberValue
statusReal value (full access via member_level)
countReal value (full access via member_level)
secret_codeMasked via SQL: ***xyz
revenueMasked: -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.

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.

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.

Common patterns

Restrict access to specific groups

To restrict access to a view to only specific groups, define access policies for those groups. Access is automatically denied to all other groups:

views: - name: sensitive_data_view # ... access_policy: # Allow access only to the `analysts` group - group: analysts member_level: includes: "*"
view(`sensitive_data_view`, { // ... access_policy: [ { // Allow access only to the `analysts` group group: `analysts`, member_level: { includes: `*` } } ] })

You can also use the groups parameter (plural) to apply the same policy to multiple groups at once:

views: - name: sensitive_data_view # ... access_policy: # Allow access to multiple groups using groups array - groups: [analysts, managers] member_level: includes: "*"
view(`sensitive_data_view`, { // ... access_policy: [ { // Allow access to multiple groups using groups array groups: [`analysts`, `managers`], member_level: { includes: `*` } } ] })

Filter by user attribute

You can filter data based on user attributes to ensure users only see data they’re authorized to access. For example, sales people can see only their own deals, while sales managers can see all deals:

views: - name: deals_view # ... access_policy: # Sales people can only see their own deals - group: sales member_level: includes: "*" row_level: filters: - member: sales_person_id operator: equals values: [ "{ userAttributes.userId }" ] # Sales managers can see all deals - group: sales_manager member_level: includes: "*" # No row-level filters - full access to all rows
view(`deals_view`, { // ... access_policy: [ { // Sales people can only see their own deals group: `sales`, member_level: { includes: `*` }, row_level: { filters: [ { member: `sales_person_id`, operator: `equals`, values: [ userAttributes.userId ] } ] } }, { // Sales managers can see all deals group: `sales_manager`, member_level: { includes: `*` } // No row-level filters - full access to all rows } ] })

Mask sensitive members

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

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: "*"
view(`orders_view`, { // ... access_policy: [ { // Default: all members masked group: `*`, member_level: { includes: [] }, member_masking: { includes: `*` } }, { // Admins: full access group: `admin`, member_level: { includes: `*` } } ] })

Mandatory filters

You can apply mandatory row-level filters to specific groups to ensure they only see data matching certain criteria:

views: - name: country_data_view # ... access_policy: # Allow access only to the `sales` and `marketing` groups with country filtering - groups: [sales, marketing] member_level: includes: "*" row_level: filters: - member: users_country operator: equals values: ["Brasil"]
view(`country_data_view`, { // ... access_policy: [ { // Allow access only to the `sales` and `marketing` groups with country filtering groups: [`sales`, `marketing`], member_level: { includes: `*` }, row_level: { filters: [ { member: `users_country`, operator: `equals`, values: [`Brasil`] } ] } } ] })

Custom mapping

Cube cloud platform automatically maps authenticated users to groups for access policies. If you are using Cube Core or authenticating against Core Data APIs directly, you might need to map the security context to groups manually.

# cube.py from cube import config @config('context_to_groups') def context_to_groups(ctx: dict) -> list[str]: return ctx['securityContext'].get('groups', ['default'])
// cube.js module.exports = { contextToGroups: ({ securityContext }) => { return securityContext.groups || ['default'] } }

A user can have more than one group.

Using securityContext

The userAttributes object is only available in Cube Cloud platform. If you are using Cube Core or authenticating against Core Data APIs directly, you won’t have access to userAttributes. Instead, you need to use securityContext directly when referencing user attributes in access policies (e.g., in row_level filters or conditions). For example, use securityContext.userId instead of userAttributes.userId.

cubes: - name: orders # ... access_policy: - group: manager row_level: filters: - member: country operator: equals values: [ "{ securityContext.country }" ]
cube(`orders`, { // ... access_policy: [ { group: `manager`, row_level: { filters: [ { member: `country`, operator: `equals`, values: [ securityContext.country ] } ] } } ] })

Was this page useful?