Fine-grained access control is essential for delivering the correct data to the right people. Data teams must ensure that users only see what they're supposed to see without slowing down development cycles or duplicating logic across dashboards, APIs, and applications. That's where Cube Cloud's data access controls come in.

As the universal semantic layer platform, Cube Cloud delivers a unified, governed, and scalable way to enforce data security across every data experience, including AI, BI, spreadsheets, and embedded analytics. With query rewrite, Data Access Policies, LDAP integration, and Cube Cloud authentication, organizations can now centrally manage access at the row, column, and member level using flexible configuration in Cube's data model. In this blog, we'll explore different techniques to help you implement access control strategies that are secure, maintainable, and enterprise-ready.

Implementing Data Access Controls

Imagine your company has a lot of information, some very private, like customer details or employee salaries. Data access policies are rules that decide who gets to see what information. Just like you wouldn't give everyone a key to your house, you don't want everyone in your company to see all the data. These policies help keep sensitive information safe and ensure it's only used by the right people for the right reasons. For example, a customer support agent might need to see a customer's order history to help them, but they shouldn't be able to see that customer's payment information. Data access policies allow you to set up these permissions, so everyone gets the data they need for their job, but nothing more.

Cube offers robust solutions for implementing granular security, including row-level, column-level, and member-level access control. Developers can dynamically restrict data visibility based on user attributes and roles by leveraging features like queryRewrite, COMPILE_CONTEXT, and Data Access Policies. This ensures that sensitive information is only accessible to authorized users, enhancing data security and compliance within applications powered by Cube.

  1. Row-Level Security: Implemented using queryRewrite, COMPILE_CONTEXT in data models, or access_policy with row_level parameters. These methods filter data based on user attributes, ensuring users only see authorized data.
  2. Column-Level Security: Implemented by invalidating queries in queryRewrite, conditionally including fields using COMPILE_CONTEXT, or using member_level parameters in access_policy. These methods restrict access to specific fields based on user roles or attributes.
  3. Member-Level Security: Controls the visibility of data model entities (cubes, views, members) using queryRewrite, COMPILE_CONTEXT to dynamically control entity public property, or member_level parameters in access_policy to define allowed or disallowed members. By default, all entities are public.

Row-Level Security in Cube

In simple terms, row-level security is like having a special filter on your data. Imagine you have a big spreadsheet with lots of rows, each representing a customer's order. Row-level security ensures that when someone looks at that spreadsheet, they only see the rows they're allowed to see.

For example, a customer service agent might only be able to see the rows (orders) that belong to the customers for whom they are responsible. They wouldn't be able to see orders from other customers. It's like having personalized views of the same data, where each person only sees the information relevant to them. So, instead of everyone seeing all the data, row-level security ensures everyone just gets to see "their" rows.

Row Level Security (RLS) is a powerful feature in Cube that lets you restrict data access based on user attributes. The security context is the foundation of RLS in Cube. It contains user-specific information (like user ID, role, or organization) that Cube uses to determine what data a user can access.

There are three main approaches to implementing RLS in Cube:

1. Using queryRewrite for Row-Level Security

The most common approach is using the queryRewrite function in your Cube configuration file:

cube.py

from cube import config
@config('query_rewrite')
def query_rewrite(query: dict, ctx: dict) -> dict:
"""
Dynamically add filters based on security context and queried cubes.
"""
# Extract security context
security_context = ctx.get('securityContext', {})
# Example: Filter for orders cube
if 'user_id' in security_context:
query['filters'].append({
'member': 'orders.user_id',
'operator': 'equals',
'values': [security_context['user_id']]
})
return query

This function intercepts every query, adds the necessary security filters, and ensures users can only see their data. A more realistic scenario would be injecting role-level security into views only. Assuming all views have a join path to a dimension that can be filtered by a user ID (e.g., email). You need to install cube_utils to parse your query payload. You can install it by adding cube_utils to your requirements.txt file.

from cube_utils.query_parser import extract_cubes
from cube import config
@config('query_rewrite')
def query_rewrite(query: dict, ctx: dict) -> dict:
"""
Dynamically add filters based on security context and queried cubes.
"""
# Extract user_id
user_id = ctx.get('securityContext', {}).get('email', False)
cubes = extract_cubes(query)
views = set(['orders_view', 'line_items_view'])
# Example: Filter for views if there's a view in the payload
if user_id and views & set(cubes):
query['filters'].append({
'member': f'{cubes[0]}.user_email',
'operator': 'equals',
'values': [user_id]
})
return query

2. Using COMPILE_CONTEXT in Data Model

For more complex scenarios, you can embed security context directly in your data model:

cubes:
- name: orders
sql: “SELECT * FROM orders o WHERE o.user_created_by = {{ COMPILE_CONTEXT.securityContext.userId | safe }}
joins:
- name: users
relationship: many_to_one
sql: "{CUBE}.user_id = {users}.id"

3. Using row_level Data Access Policies

You can use the access_policy parameter within cubes and views to configure data access policies for them.

cubes:
- name: orders
# ...
access_policy:
- role: "*"
row_level:
allow_all: false
- role: manager
row_level:
filters:
- member: state
operator: equals
values: [ "{ securityContext.state }" ]

Column-Level Security in Cube

Think of your data as a big table with rows and columns. We already discussed row-level security, which lets you see only specific rows. Now, column-level security is about controlling which columns of that table you're allowed to see.

Imagine that same spreadsheet with customer orders. You might have columns like "Customer Name," "Order Date," "Items Ordered," and also "Credit Card Number." Column-level security lets you say, "Customer service reps can see the 'Customer Name', 'Order Date', and 'Items Ordered' columns, but they cannot see the 'Credit Card Number' column."

So, while row-level security decides which customers you can see data for, column-level security decides what details about those customers you're allowed to see. It's all about ensuring you only get access to the specific information you need to do your job, and nothing more. In Cube, this can be implemented in several ways:

1. Using queryRewrite for Column-Level Security

You can invalidate queries in query_rewrite if a user doesn’t have the necessary permissions.

cube.py

from cube_utils.query_parser import extract_members
from cube import config
@config('query_rewrite')
def query_rewrite(query: dict, ctx: dict) -> dict:
"""
Dynamically add filters based on security context and queried cubes.
"""
# Extract security context
roles = ctx.get('securityContext', {}).get('roles', [])
# Example: Invalidate query if email is not allowed
if 'admin' not in roles and 'users.email' in extract_members(query):
raise Exception(f'users.email not allowed for roles: {roles}')
return query
  1. Using COMPILE_CONTEXT to include fields conditionally

You can dynamically control field visibility based on the security context:

cubes:
- name: users
sql_table: users
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: email
sql: email
type: string
public: "{{ COMPILE_CONTEXT.securityContext.role == 'admin' }}"
- name: phone
sql: phone
type: string
public: "{{ COMPILE_CONTEXT.securityContext.role in ['admin', 'support'] }}"
- name: salary
sql: salary
type: number
public: "{{ COMPILE_CONTEXT.securityContext.department == 'finance' }}"

3. Using member_level Data Access Policies

The optional member_level parameter, when present, configures member-level access for a policy by specifying allowed or disallowed members.

You can either provide a list of allowed members with the includes parameter, or a list of disallowed members with the excludes parameter.

cubes:
- name: orders
# ...
access_policy:
- role: "*"
member_level:
# Includes nothing, i.e., excludes all members
includes: []
- role: manager
member_level:
# Includes all members except for `count`
excludes:
- count
- role: observer
member_level:
# Includes all members except for `count` and `count_7d`
excludes:
- count
- count_7d
- role: guest
# Includes only `count_30d`, excludes all other members
member_level:
includes:
- count_30d

Implementing Member-Level security in Cube

Let's talk about member-level security. Imagine you have different reports or dashboards in your company. Each report shows specific data, like "Sales Figures," "Customer Lists," or "Employee Salaries."

Member-level security is about controlling who can see all of those reports or specific parts within those reports.

The data model serves as a facade of your data. With member-level security, you can define whether data model entities (cubes, views, and their members) are exposed to end users and can be queried via APIs & integrations.

By default, all cubes, views, and their members are public, meaning that any user can access them, and they are also visible during data model introspection.

1. Using queryRewrite for Member-Level Security

You can invalidate queries in query_rewrite if a user doesn’t have the necessary permissions. You need to install cube_utils to parse your query payload.

cube.py

from cube_utils.query_parser import extract_cubes
from cube import config
@config('query_rewrite')
def query_rewrite(query: dict, ctx: dict) -> dict:
"""
Dynamically add filters based on security context and queried cubes.
"""
# Extract security context
roles = ctx.get('securityContext', {}).get('roles', [])
# Example: Invalidate query if users cube is not allowed
if 'admin' not in roles and 'users' in extract_cubes(query):
raise Exception(f'users not allowed for roles: {roles}')
return query

2. Using COMPILE_CONTEXT to include make entities public conditionally

You can also control whether a data model entity should be public or private dynamically.

In the example below, the customers view would only be visible to a subset of tenants that have the team property set to marketing in the security context:

model/views/customers.yml
views:
- name: customers
public: "{{ is_accessible_by_team('marketing', COMPILE_CONTEXT) }}"
model/globals.py
from cube import TemplateContext
template = TemplateContext()
@template.function('is_accessible_by_team')
def is_accessible_by_team(team: str, ctx: dict) -> bool:
return team == ctx['securityContext'].setdefault('team', 'default')

3. Using member_level Data Access Policies

The optional member_level parameter, when present, configures member-level access for a policy by specifying allowed or disallowed members.

You can either provide a shorthand for both parameters: includes: "*", excludes: "*".

cubes:
- name: orders
# ...
access_policy:
- role: manager
member_level:
# Includes all members
includes:
- "*"
- role: observer
member_level:
# Excludes all members
excludes:
- "*"

Example Scenario:

Imagine an e-commerce platform using Cube Cloud for analytics. This platform collects PII, including customer names, email addresses, phone numbers, and order history.

Row-Level Security for PII:

  • Using queryRewrite or COMPILE_CONTEXT, access can be restricted so that a customer service representative can only see the order history and PII of customers they are assigned to.
  • For example, a query might only return orders where orders.customer_email matches the email within the securityContext of the logged-in representative. This prevents them from seeing other customers' data.
  • Data Access Policies with row_level parameters can further enforce this, ensuring that even if a query tries to access all orders, the policy will filter the results based on the user's role or attributes.

Column-Level Security for PII:

  • Specific sensitive PII columns, like users.phone or users.salary, can be restricted to specific roles.
  • Using COMPILE_CONTEXT, the public property of a dimension like users.email can be dynamically set based on the user's role. Only users with the "admin" role might see the email column, while others would not.
  • queryRewrite can also invalidate queries that attempt to access restricted columns if the user's role does not have permission.
  • Data Access Policies with member_level parameters can exclude sensitive members (columns) for specific roles.

Member-Level Security for PII:

  • Entire views or cubes related to highly sensitive PII could be restricted. For example, a "Full Customer Details" view might only be accessible to executives or compliance officers.
  • queryRewrite can invalidate queries that try to access the "Full Customer Details" view if the user's role lacks the necessary permissions.
  • COMPILE_CONTEXT can control the public property of views, making them visible only to specific teams or roles.
  • Data Access Policies can exclude access to specific data model entities (cubes, views) for specific roles.

In all these cases, the securityContext plays a vital role. It carries information about the logged-in user, such as their user_id, email, role, or department. Cube Cloud uses this context to dynamically enforce access controls, ensuring that PII data is protected and only accessible to authorized individuals.

Therefore, these data access controls in Cube Cloud are highly relevant and essential for protecting PII data by limiting who can see what data, based on their role and permissions.

Conclusion

Whether securing internal dashboards or powering analytics for thousands of external users, Cube Cloud gives you the tools to confidently control who sees what, without compromising on performance or developer velocity. With support for query rewriting, COMPILE_CONTEXT, and declarative Data Access Policies, Cube lets you enforce access rules close to the data model, so governance is baked into the analytics layer.

Now, with Cube Cloud authentication and LDAP integration, enterprises can bring their existing identity and permissions frameworks directly into Cube, enabling consistent and compliant access control at scale. As your organization grows in data complexity, Cube's approach ensures your access controls scale securely and seamlessly. Contact sales to learn more about how Cube can help you deliver trusted and governed data across AI, BI, spreadsheets, and embedded analytics.