diff --git a/DEPRECATION.md b/DEPRECATION.md index 9e933a771209a..e1ea79ecd51a4 100644 --- a/DEPRECATION.md +++ b/DEPRECATION.md @@ -68,6 +68,7 @@ features: | Deprecated | [Node.js 20](#nodejs-20) | v1.3.0 | | | Deprecated | [`renewQuery` parameter of the `/v1/load` endpoint](#renewquery-parameter-of-the-v1load-endpoint) | v1.3.73 | | | Deprecated | [Elasticsearch driver](#elasticsearch-driver) | v1.6.0 | | +| Deprecated | [`context_to_roles`](#context-to-roles) | v1.6.4 | | ### Node.js 8 @@ -426,4 +427,11 @@ options and use the new `cache` parameter of the `/v1/load` endpoint instead. **Deprecated in Release: v1.6.0** -The Elasticsearch driver is deprecated and will be removed in a future release. \ No newline at end of file +The Elasticsearch driver is deprecated and will be removed in a future release. + +### `context_to_roles` + +**Deprecated in Release: v1.6.4** + +The `context_to_roles` configuration option is deprecated and will be removed in a future +release. Please use `context_to_groups` instead. \ No newline at end of file diff --git a/docs/pages/product/administration/users-and-permissions/user-attributes.mdx b/docs/pages/product/administration/users-and-permissions/user-attributes.mdx index dd742bda7db2f..af38ad4a6b3e0 100644 --- a/docs/pages/product/administration/users-and-permissions/user-attributes.mdx +++ b/docs/pages/product/administration/users-and-permissions/user-attributes.mdx @@ -30,7 +30,7 @@ To filter data based on user attributes, implement an access policy in your view views: - name: orders_view access_policy: - - role: "*" # Applies to all roles + - group: "*" # Applies to all groups row_level: filters: - member: customers_city diff --git a/docs/pages/product/auth.mdx b/docs/pages/product/auth.mdx index d9acfb3843138..8927afa3d22ae 100644 --- a/docs/pages/product/auth.mdx +++ b/docs/pages/product/auth.mdx @@ -1,15 +1,38 @@ # Access control +Access control in Cube involves _authentication_ and _authorization_. -Cube has a data access control layer built into the data model. Access control in Cube involves _authentication_ and _authorization_. +## Authentication +Authentication determines if a user can access Cube. -**Cube** cloud platform provides built-in authentication abstractions alongside [user attributes][ref-user-attributes] and groups for managing data access. **Cube Core** users can control the [security context][ref-sec-ctx] directly to implement authentication. +* **Cube** cloud platform provides built-in authentication mechanisms. Users are assigned +[roles and permissions][ref-roles-perms] that determine available features of the Cube +platform. +* **Cube Core** provides several [authentication methods][ref-auth-methods] for its API +endpoints. +## Authorization -Authorization is based on [access policies][ref-dap] and dynamic controls for advanced use cases. +Authorization determines what data a user can access though Cube. +Authorization is managed declaratively via [access policies][ref-dap], a built-in +capability of Cube's data modeling layer. There are also programmatic controls for +advanced use cases, such as the [`query_rewrite`][ref-query-rewrite] configuration +parameter. +* **Cube** cloud platform applies access policies to users based on their +[groups][ref-user-groups] and [attributes][ref-user-attributes]. +* **Cube Core** applies access policies to users based on their groups derived from the +[security context][ref-sec-ctx]. See the [`context_to_groups`][ref-ctx-to-groups] +configuration parameter for details. + + +[ref-roles-perms]: /product/administration/users-and-permissions/roles-and-permissions +[ref-auth-methods]: /product/auth/methods +[ref-user-groups]: /product/administration/users-and-permissions/user-groups [ref-user-attributes]: /product/administration/users-and-permissions/user-attributes [ref-sec-ctx]: /product/auth/context -[ref-dap]: /product/auth/data-access-policies \ No newline at end of file +[ref-dap]: /product/auth/data-access-policies +[ref-query-rewrite]: /product/configuration/reference/config#query_rewrite +[ref-ctx-to-groups]: /product/configuration/reference/config#context_to_groups \ No newline at end of file diff --git a/docs/pages/product/auth/data-access-policies.mdx b/docs/pages/product/auth/data-access-policies.mdx index 83636387c0da0..e18b36c39f8a5 100644 --- a/docs/pages/product/auth/data-access-policies.mdx +++ b/docs/pages/product/auth/data-access-policies.mdx @@ -302,38 +302,6 @@ view(`country_data_view`, { -## Troubleshooting - -Be careful with the [_any group_ shorthand][ref-ref-dap-role] (`group: "*"`) in access -policies that don't restrict access on member __and__ row level. Given that policies for -multiple groups are [combined together](#policy-evaluation) using the _OR_ semantics, -a policy that allows access for _any group_ without restrictions on a certain level will -effectively grant unrestricted access on that level to all users: - -```yaml -cubes: - - name: orders - # ... - - access_policy: - # No access on member level, __full access__ on row level for any group - - group: "*" - member_level: - includes: [] - - # Some restrictions on row level for the `restricted` group - - group: restricted - row_level: - filters: - - member: country - operator: equals - values: [ "USA" ] -``` - -In the example above, all users will have full access to all members and all rows of the -`orders` cube, regardless of their groups, because the first policy applies to _any -group_ and is combined with the second one using the _OR_ semantics. - ## Custom mapping Cube cloud platform automatically maps authenticated users to groups for access policies. @@ -406,17 +374,9 @@ cube(`orders`, { -## Using roles - -In prior versions of Cube, we were using `roles` instead of `groups`. We have changed that to `groups` to avoid conflict with [Cube roles][ref-cube-roles]. - -In Cube Core, you can still use roles. You would need to define `context_to_roles` instead of `context_to_groups`. - [ref-mls-public]: /product/auth/member-level-security#managing-member-level-access [ref-sec-ctx]: /product/auth/context -[ref-ctx-to-roles]: /product/configuration/reference/config#context_to_roles [ref-ref-dap]: /product/data-modeling/reference/data-access-policies [ref-ref-dap-role]: /product/data-modeling/reference/data-access-policies#role -[ref-core-data-apis]: /product/apis-integrations/core-data-apis -[ref-cube-roles]: /product/administration/users-and-permissions/roles-and-permissions \ No newline at end of file +[ref-core-data-apis]: /product/apis-integrations/core-data-apis \ No newline at end of file diff --git a/docs/pages/product/configuration/reference/config.mdx b/docs/pages/product/configuration/reference/config.mdx index 2647ac445cf54..e7e9a5cbbdbc4 100644 --- a/docs/pages/product/configuration/reference/config.mdx +++ b/docs/pages/product/configuration/reference/config.mdx @@ -1363,26 +1363,27 @@ module.exports = { -### `context_to_roles` +### `context_to_groups` -Used by [access policies][ref-dap]. This option is used to derive a list of -[data access roles][ref-dap-roles] from the [security context][ref-sec-ctx]. +Used by [access policies][ref-dap]. This option is used to derive a list of groups that +the user belongs to from the [security context][ref-sec-ctx]. These groups can then be +used to match [access policies][ref-dap-roles]. ```python from cube import config -@config('context_to_roles') -def context_to_roles(ctx: dict) -> list[str]: - return ctx['securityContext'].get('roles', ['default']) +@config('context_to_groups') +def context_to_groups(ctx: dict) -> list[str]: + return ctx['securityContext'].get('groups', ['default']) ``` ```javascript module.exports = { - contextToRoles: ({ securityContext }) => { - return securityContext.roles || ['default'] + contextToGroups: ({ securityContext }) => { + return securityContext.groups || ['default'] } } ``` @@ -1391,24 +1392,24 @@ module.exports = { If the [user roles mapping][ref-ldap-roles-mapping] in the [LDAP integration][ref-ldap-integration] is configured and the [authentication integration][ref-auth-integration] is enabled, -the `context_to_roles` option might be defined as follows: +the `context_to_groups` option might be defined as follows: ```python from cube import config -@config('context_to_roles') -def context_to_roles(ctx: dict) -> list[str]: - cloud_ctx = ctx['securityContext'].get('cubeCloud', {'roles': []}) +@config('context_to_groups') +def context_to_groups(ctx: dict) -> list[str]: + cloud_ctx = ctx['securityContext'].get('cubeCloud', {'groups': []}) return cloud_ctx.get('roles', []) ``` ```javascript module.exports = { - contextToRoles: ({ securityContext }) => { - const cloud_ctx = securityContext.cubeCloud || { roles: [] } + contextToGroups: ({ securityContext }) => { + const cloud_ctx = securityContext.cubeCloud || { groups: [] } return cloud_ctx.roles || [] } } @@ -1504,6 +1505,66 @@ Usually used for [multitenancy][ref-multitenancy]. If not defined, Cube will lookup for environment variable `CUBEJS_DB_TYPE` to resolve the data source type. +### `context_to_roles` + + + +`context_to_roles` is deprecated and will be removed in a future release. +Use [`context_to_groups`](#context_to_groups) instead. + + + +Used by [access policies][ref-dap]. This option is used to derive a list of +[data access roles][ref-dap-roles] from the [security context][ref-sec-ctx]. + + + +```python +from cube import config + +@config('context_to_roles') +def context_to_roles(ctx: dict) -> list[str]: + return ctx['securityContext'].get('roles', ['default']) +``` + +```javascript + +module.exports = { + contextToRoles: ({ securityContext }) => { + return securityContext.roles || ['default'] + } +} +``` + + + +If the [user roles mapping][ref-ldap-roles-mapping] in the [LDAP integration][ref-ldap-integration] +is configured and the [authentication integration][ref-auth-integration] is enabled, +the `context_to_roles` option might be defined as follows: + + + +```python +from cube import config + +@config('context_to_roles') +def context_to_roles(ctx: dict) -> list[str]: + cloud_ctx = ctx['securityContext'].get('cubeCloud', {'roles': []}) + return cloud_ctx.get('roles', []) +``` + +```javascript + +module.exports = { + contextToRoles: ({ securityContext }) => { + const cloud_ctx = securityContext.cubeCloud || { roles: [] } + return cloud_ctx.roles || [] + } +} +``` + + + [gh-jsonwebtoken-algs]: https://github.com/auth0/node-jsonwebtoken#algorithms-supported diff --git a/rust/cubestore/cubestore-sql-tests/src/tests.rs b/rust/cubestore/cubestore-sql-tests/src/tests.rs index 24a2c15c125c1..ddabfccc16859 100644 --- a/rust/cubestore/cubestore-sql-tests/src/tests.rs +++ b/rust/cubestore/cubestore-sql-tests/src/tests.rs @@ -3348,10 +3348,9 @@ async fn planning_hints(service: Box) { assert_eq!( pp_phys_plan_ext(p.worker.as_ref(), &show_hints), "Worker, sort_order: [0, 1]\ - \n CoalescePartitions, sort_order: [0, 1]\ - \n Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\ - \n Sort, sort_order: [0, 1]\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\ + \n Sort, sort_order: [0, 1]\ + \n Empty" ); let p = service @@ -3362,10 +3361,9 @@ async fn planning_hints(service: Box) { pp_phys_plan_ext(p.worker.as_ref(), &show_hints), "Worker, sort_order: [1, 0]\ \n Projection, [id2, id1], sort_order: [1, 0]\ - \n CoalescePartitions, sort_order: [0, 1]\ - \n Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\ - \n Sort, sort_order: [0, 1]\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id1, id2], sort_order: [0, 1]\ + \n Sort, sort_order: [0, 1]\ + \n Empty" ); // Unsorted when skips columns from sort prefix. @@ -3390,10 +3388,9 @@ async fn planning_hints(service: Box) { assert_eq!( pp_phys_plan_ext(p.worker.as_ref(), &show_hints), "Worker, sort_order: [0]\ - \n CoalescePartitions, sort_order: [0]\ - \n Scan, index: default:1:[1], fields: [id1, id3], sort_order: [0]\ - \n Sort, sort_order: [0]\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id1, id3], sort_order: [0]\ + \n Sort, sort_order: [0]\ + \n Empty" ); // Single value hints. @@ -3433,10 +3430,9 @@ async fn planning_hints(service: Box) { pp_phys_plan_ext(p.worker.as_ref(), &show_hints), "Worker, sort_order: [0, 1]\ \n Filter, sort_order: [0, 1]\ - \n CoalescePartitions, sort_order: [0, 1, 2]\ - \n Scan, index: default:1:[1], fields: *, sort_order: [0, 1, 2]\ - \n Sort, sort_order: [0, 1, 2]\ - \n Empty" + \n Scan, index: default:1:[1], fields: *, sort_order: [0, 1, 2]\ + \n Sort, sort_order: [0, 1, 2]\ + \n Empty" ); } @@ -3733,10 +3729,9 @@ async fn planning_simple(service: Box) { assert_eq!( pp_phys_plan(p.worker.as_ref()), "Worker\ - \n CoalescePartitions\ - \n Scan, index: default:1:[1], fields: [id, amount]\ - \n Sort\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id, amount]\ + \n Sort\ + \n Empty" ); let p = service @@ -3751,10 +3746,9 @@ async fn planning_simple(service: Box) { pp_phys_plan(p.worker.as_ref()), "Worker\ \n Filter\ - \n CoalescePartitions\ - \n Scan, index: default:1:[1], fields: [id, amount]\ - \n Sort\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id, amount]\ + \n Sort\ + \n Empty" ); let p = service @@ -3776,10 +3770,9 @@ async fn planning_simple(service: Box) { "Sort\ \n Worker\ \n Filter\ - \n CoalescePartitions\ - \n Scan, index: default:1:[1], fields: [id, amount]\ - \n Sort\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id, amount]\ + \n Sort\ + \n Empty" ); let p = service @@ -3801,10 +3794,9 @@ async fn planning_simple(service: Box) { "GlobalLimit, n: 10\ \n Worker\ \n Filter\ - \n CoalescePartitions\ - \n Scan, index: default:1:[1], fields: [id, amount]\ - \n Sort\ - \n Empty" + \n Scan, index: default:1:[1], fields: [id, amount]\ + \n Sort\ + \n Empty" ); let p = service diff --git a/rust/cubestore/cubestore/src/queryplanner/query_executor.rs b/rust/cubestore/cubestore/src/queryplanner/query_executor.rs index 748223668ed2a..bd3094c919fd3 100644 --- a/rust/cubestore/cubestore/src/queryplanner/query_executor.rs +++ b/rust/cubestore/cubestore/src/queryplanner/query_executor.rs @@ -883,6 +883,8 @@ impl CubeTable { let schema = table_projected_schema; let partition_num = partition_execs.len(); + let table_ordering = lex_ordering_for_index(self.index_snapshot.index.get_row(), &schema)?; + let read_data: Arc = Arc::new(CubeTableExec { schema: schema.clone(), partition_execs, @@ -891,10 +893,7 @@ impl CubeTable { properties: PlanProperties::new( EquivalenceProperties::new_with_orderings( schema.clone(), - &[LexOrdering::new(lex_ordering_for_index( - self.index_snapshot.index.get_row(), - &schema, - )?)], + &[LexOrdering::new(table_ordering.clone())], ), Partitioning::UnknownPartitioning(partition_num), EmissionType::Incremental, @@ -989,6 +988,11 @@ impl CubeTable { LexOrdering::new(join_columns), read_data, )) + } else if !table_ordering.is_empty() { + Arc::new(SortPreservingMergeExec::new( + LexOrdering::new(table_ordering), + read_data, + )) } else { Arc::new(CoalescePartitionsExec::new(read_data)) };