# Access policies

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

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

<CodeTabs>

```yaml
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 }" ]
```

```javascript
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 ]
          }
        ]
      }
    }
  ]
})
```

</CodeTabs>

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][ref-ref-dap].

## Policy evaluation

When processing a request, Cube will evaluate the access policies and combine them
with relevant custom security rules, e.g., [`public` parameters][ref-mls-public] 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.**

<InfoBox>

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.

</InfoBox>

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

<InfoBox>

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.

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

<WarningBox>

Unlike [member-level access policies][ref-dap-mls], which are **not** inherited
from cubes when querying through views, data masking rules **are** applied from
both the view and the relevant cubes. If you define masking on both a cube and a
view that uses that cube, the masking will be applied twice. To avoid unexpected
results, define masking rules on either the cube or the view, but not both.

</WarningBox>

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

## 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:

<CodeTabs>

```yaml
views:
  - name: sensitive_data_view
    # ...
    
    access_policy:
      # Allow access only to the `analysts` group
      - group: analysts
        member_level:
          includes: "*"
```

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

</CodeTabs>

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

<CodeTabs>

```yaml
views:
  - name: sensitive_data_view
    # ...
    
    access_policy:
      # Allow access to multiple groups using groups array
      - groups: [analysts, managers]
        member_level:
          includes: "*"
```

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

</CodeTabs>

### 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:

<CodeTabs>

```yaml
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
```

```javascript
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
    }
  ]
})
```

</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:

<CodeTabs>

```yaml
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"]
```

```javascript
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`]
          }
        ]
      }
    }
  ]
})
```

</CodeTabs>

## 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][ref-core-data-apis] directly, you might need to map the security context to groups manually.

<CodeTabs>

```python
# cube.py
from cube import config

@config('context_to_groups')
def context_to_groups(ctx: dict) -> list[str]:
  return ctx['securityContext'].get('groups', ['default'])
```

```javascript
// cube.js
module.exports = {
  contextToGroups: ({ securityContext }) => {
    return securityContext.groups || ['default']
  }
}
```

</CodeTabs>

A user can have more than one group.

## Using securityContext

The [`userAttributes`][ref-sec-ctx] object is only available in Cube Cloud platform. If you are using Cube Core or authenticating against [Core Data APIs][ref-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`.

<CodeTabs>

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

    access_policy:
      - group: manager
        row_level:
          filters:
            - member: country
              operator: equals
              values: [ "{ securityContext.country }" ]
```

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

  access_policy: [
    {
      group: `manager`,
      row_level: {
        filters: [
          {
            member: `country`,
            operator: `equals`,
            values: [ securityContext.country ]
          }
        ]
      }
    }
  ]
})
```

</CodeTabs>


[ref-mls-public]: /product/auth/member-level-security#managing-member-level-access
[ref-sec-ctx]: /product/auth/context
[ref-ref-dap]: /product/data-modeling/reference/data-access-policies
[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