From b9662e894d4a403a3d317318fbf7e74c7e42aa5b Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Tue, 21 Jul 2026 12:32:58 +0200 Subject: [PATCH 01/12] Node.js: clarify instance-based auth --- guides/security/authorization.md | 42 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 7d779d86fc..e5ee673cad 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -257,14 +257,22 @@ Here, users can read and write orders they've created, and `Auditor` users can r Restrictions can be defined on different types of CDS resources, but there are some limitations with regards to supported privileges: -| CDS Resource | `grant` | `to` | `where` | Remark | -|-----------------|:-------:|:----:|:-----------------:|---------------| -| service | | | | = `@requires` | -| entity | | | 1 | | -| action/function | | | 2 | = `@requires` | - -> 1For bound actions and functions that are not bound against a collection, Node.js supports instance-based authorization at the entity level. For example, you can use `where` clauses that *contain references to the model*, such as `where: CreatedBy = $user`. For all bound actions and functions, Node.js supports simple static expressions at the entity level that *don't have any reference to the model*, such as `where: $user.level = 2`. -> 2 For unbound actions and functions, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. +| CDS Resource | `grant` | `to` | `where` | Remark | +|-----------------------|:-------:|:----:|:-----------------:|---------------| +| service | | | | = `@requires` | +| entity | | | | | +| bound action/function | | | 1 | = `@requires` | +| action/function | | | 2 | = `@requires` | + +> 1 For [bound actions and functions](../../cds/cdl#bound-actions) that *are not bound to a collection of instances*, Node.js supports instance-based authorization. +> Example: +> ```cds +> entity Orders @(restrict: [ +> { grant: 'cancel', where: (CreatedBy = $user) }, +> ]) {/*...*/} +> ``` + +> 2 For (unbound) actions and functions, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. Unsupported privilege properties are ignored by the runtime. Especially, for bound or unbound actions, the `grant` property is implicitly removed (assuming `grant: '*'` instead). The same also holds for functions: @@ -314,7 +322,7 @@ The resulting authorizations are illustrated in the following access matrix: | `CustomerService.Orders` (*) | | 1 | | | | `CustomerService.monthlyBalance` | | | | | -> 1 A `Vendor` user can only access the instances that they created.
+> 1 A `Customer` user can only access the instances that they created.
The example models access rules for different roles in the same service. In general, this is _not recommended_ due to the high complexity. See [best practices](#dedicated-services) for information about how to avoid this. @@ -440,13 +448,11 @@ This means that, the condition applies to following standard CDS events only: - `UPDATE` (as reject condition) - `DELETE` (as reject condition) -
- -In addition, the runtime [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: +The Java runtime additionally [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: - `CREATE` (input filter) - `UPDATE` (input filer) -
+The Node.js runtime, on the other hand, supports simple static expressions that *don't have any reference to the model* (e.g., `where: $user.level = 2`) for `CREATE` as well as unbound actions and functions. You can define filter conditions in the `where`-clause of restrictions based on [CQL](/cds/cql)-predicates, declared as [compiler expressions](../../cds/cdl#expressions-as-annotation-values): @@ -609,12 +615,13 @@ Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the co
+// TODO: Node.js does not support his?
-### Checking Input Data { #input-data-auth .java} +### Checking Input Data (Java only) { #input-data-auth } Input data of `CREATE` and `UPDATE` events is also validated with regards to instance-based authorization conditions. Invalid input that does not meet the condition is rejected with response code `400`. @@ -633,7 +640,12 @@ Starting with CAP Java `4.0`, deep authorization is active by default. It can be disabled by setting cds.security.authorization.instanceBased.checkInputData: false. -### Rejected Entity Selection { #reject-403 .java} +### Simple Static Checks (Node.js only) { #simple-static-checks } + +TODO + + +### Rejected Entity Selection { #reject-403 } //> TODO: Node.js? Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. From fe1cd2b4935db6e2bf89834c0bf36fcf7c40df12 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Tue, 21 Jul 2026 13:03:43 +0200 Subject: [PATCH 02/12] restore toggle --- guides/security/authorization.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index e5ee673cad..5f3aa14b9b 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -448,11 +448,19 @@ This means that, the condition applies to following standard CDS events only: - `UPDATE` (as reject condition) - `DELETE` (as reject condition) -The Java runtime additionally [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: +
+ +In addition, the Java runtime [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: - `CREATE` (input filter) - `UPDATE` (input filer) -The Node.js runtime, on the other hand, supports simple static expressions that *don't have any reference to the model* (e.g., `where: $user.level = 2`) for `CREATE` as well as unbound actions and functions. +
+ +
+ +In addition, for `CREATE` as well as unbound actions and functions, the Node.js runtime supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. + +
You can define filter conditions in the `where`-clause of restrictions based on [CQL](/cds/cql)-predicates, declared as [compiler expressions](../../cds/cdl#expressions-as-annotation-values): From a5261217fa876d871bc3cefdd5462a172df8999d Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Tue, 21 Jul 2026 13:05:48 +0200 Subject: [PATCH 03/12] more toggles --- guides/security/authorization.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 5f3aa14b9b..f910e13954 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -629,7 +629,7 @@ Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the co
-### Checking Input Data (Java only) { #input-data-auth } +### Checking Input Data { #input-data-auth .java} Input data of `CREATE` and `UPDATE` events is also validated with regards to instance-based authorization conditions. Invalid input that does not meet the condition is rejected with response code `400`. @@ -648,12 +648,13 @@ Starting with CAP Java `4.0`, deep authorization is active by default. It can be disabled by setting cds.security.authorization.instanceBased.checkInputData: false. -### Simple Static Checks (Node.js only) { #simple-static-checks } +### Simple Static Checks { #simple-static-checks .node} TODO -### Rejected Entity Selection { #reject-403 } //> TODO: Node.js? +//> TODO: Node.js? +### Rejected Entity Selection { #reject-403 .java} Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. From 93f9d7f6052f81486ea18b4bb0e26af22b9a1b23 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Tue, 21 Jul 2026 13:23:39 +0200 Subject: [PATCH 04/12] Simple Static Checks --- guides/security/authorization.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index f910e13954..d920e1883b 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -650,7 +650,16 @@ It can be disabled by setting cds.security.authorization.instanceBa ### Simple Static Checks { #simple-static-checks .node} -TODO +Most instance-based [`@restrict.where`](#restrict-annotation) conditions reference business data (for example, `where: 'createdBy = $user'`) and can only be enforced against persisted data — pushed into the query for `READ`, or verified with a `COUNT` for `UPDATE`/`DELETE`. + +Some conditions, though, reduce to a plain comparison of literals once [user attributes](#user-attrs) are resolved: + +```cds +entity Reviews @(restrict: [ + { grant: 'CREATE', where: '$user.level >= 2' } ]); +``` + +For a user with `level = 3`, this becomes `3 >= 2`, which the runtime evaluates in memory — granting or rejecting with `403` without any database access. Such _simple static checks_ apply to `CREATE` (and its draft variant `NEW`) and to unbound actions and functions, where there's no persisted instance to query. They're only recognized for a single binary comparison (`=`, `!=`, `<`, `<=`, `>`, `>=`) with no reference to entity elements. //> TODO: Node.js? From 86c719a53362b01dd0b36db0a1b2656699e6d389 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:26:10 +0200 Subject: [PATCH 05/12] Apply suggestion from @sjvans --- guides/security/authorization.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index d920e1883b..68151a25e5 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -662,8 +662,7 @@ entity Reviews @(restrict: [ For a user with `level = 3`, this becomes `3 >= 2`, which the runtime evaluates in memory — granting or rejecting with `403` without any database access. Such _simple static checks_ apply to `CREATE` (and its draft variant `NEW`) and to unbound actions and functions, where there's no persisted instance to query. They're only recognized for a single binary comparison (`=`, `!=`, `<`, `<=`, `>`, `>=`) with no reference to entity elements. -//> TODO: Node.js? -### Rejected Entity Selection { #reject-403 .java} +### Rejected Entity Selection { #reject-403 } Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. From 85637dd9e32b309841ce8ef631b89ddef52b841e Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:26:35 +0200 Subject: [PATCH 06/12] Apply suggestion from @sjvans --- guides/security/authorization.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 68151a25e5..6296cdb0d4 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -623,7 +623,6 @@ Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the co
-// TODO: Node.js does not support his?
From 98e81b52a82d7de5f7e567b2bd5c439dfad1f212 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Fri, 14 Aug 2026 02:25:29 +0200 Subject: [PATCH 07/12] docs(auth): static @restrict.where on collection-bound actions + Node reject-403 - Simple static checks now also apply to collection-bound actions in Node.js (cap/cds#6570), alongside CREATE/NEW and unbound actions/functions. - Rejected Entity Selection (single-entity READ -> 404, UPDATE/DELETE -> 403) is no longer Java-only; Node.js aligns (verified in cds-compliance#46). The CAP Java version/config note is scoped to an impl-java block. --- guides/security/authorization.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 6296cdb0d4..4bb6e43823 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -272,7 +272,7 @@ Restrictions can be defined on different types of CDS resources, but there are s > ]) {/*...*/} > ``` -> 2 For (unbound) actions and functions, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. +> 2 For (unbound) actions and functions — as well as actions and functions bound to a *collection* of instances — Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. Unsupported privilege properties are ignored by the runtime. Especially, for bound or unbound actions, the `grant` property is implicitly removed (assuming `grant: '*'` instead). The same also holds for functions: @@ -458,7 +458,7 @@ In addition, the Java runtime [checks the filter condition of the input data](#i
-In addition, for `CREATE` as well as unbound actions and functions, the Node.js runtime supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. +In addition, for `CREATE` as well as unbound actions and functions and actions and functions bound to a *collection* of instances, the Node.js runtime supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`.
@@ -658,25 +658,29 @@ entity Reviews @(restrict: [ { grant: 'CREATE', where: '$user.level >= 2' } ]); ``` -For a user with `level = 3`, this becomes `3 >= 2`, which the runtime evaluates in memory — granting or rejecting with `403` without any database access. Such _simple static checks_ apply to `CREATE` (and its draft variant `NEW`) and to unbound actions and functions, where there's no persisted instance to query. They're only recognized for a single binary comparison (`=`, `!=`, `<`, `<=`, `>`, `>=`) with no reference to entity elements. +For a user with `level = 3`, this becomes `3 >= 2`, which the runtime evaluates in memory — granting or rejecting with `403` without any database access. Such _simple static checks_ apply to `CREATE` (and its draft variant `NEW`), to unbound actions and functions, and to actions and functions bound to a *collection* of instances — everywhere there's no single persisted instance to query. They're only recognized for a single binary comparison (`=`, `!=`, `<`, `<=`, `>`, `>=`) with no reference to entity elements. ### Rejected Entity Selection { #reject-403 } Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), -are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. +are guarded by the runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. Hence, if the user isn't authorized to query an entity, requests targeting a *single* entity return *404 - Not Found* response and not *403 - Forbidden*. -To allow the UI to distinguish between *not found* and *forbidden*, CAP Java can detect this situation and rejects `UPDATE` and `DELETE` requests to single entities with forbidden accordingly. +To allow the UI to distinguish between *not found* and *forbidden*, the runtime detects this situation and rejects `UPDATE` and `DELETE` requests to single entities with forbidden accordingly. The additional authorization check might affect performance. ::: warning Avoid enumerable keys To avoid disclosure of the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable or add custom code to overrule the default behavior otherwise. ::: +
+ Starting with CAP Java `4.0`, the reject behaviour is active by default. It can be disabled by setting cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: false. +
+ ## Limitations {.node} From 9018e5c2ff9fd160aa26b2969804905e849c94d3 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Fri, 14 Aug 2026 02:41:42 +0200 Subject: [PATCH 08/12] Apply suggestion from @sjvans --- guides/security/authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 4bb6e43823..1b01381c7d 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -272,7 +272,7 @@ Restrictions can be defined on different types of CDS resources, but there are s > ]) {/*...*/} > ``` -> 2 For (unbound) actions and functions — as well as actions and functions bound to a *collection* of instances — Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. +> 2 For actions and functions that are either unbound or bound to a *collection* of instances, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. Unsupported privilege properties are ignored by the runtime. Especially, for bound or unbound actions, the `grant` property is implicitly removed (assuming `grant: '*'` instead). The same also holds for functions: From 2807d81b722014fd3efcbbfc1222db86a60c3371 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 17 Aug 2026 10:51:33 +0200 Subject: [PATCH 09/12] 1,2 --- guides/security/authorization.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 1b01381c7d..21f41a5a0c 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -257,14 +257,13 @@ Here, users can read and write orders they've created, and `Auditor` users can r Restrictions can be defined on different types of CDS resources, but there are some limitations with regards to supported privileges: -| CDS Resource | `grant` | `to` | `where` | Remark | -|-----------------------|:-------:|:----:|:-----------------:|---------------| -| service | | | | = `@requires` | -| entity | | | | | -| bound action/function | | | 1 | = `@requires` | -| action/function | | | 2 | = `@requires` | - -> 1 For [bound actions and functions](../../cds/cdl#bound-actions) that *are not bound to a collection of instances*, Node.js supports instance-based authorization. +| CDS Resource | `grant` | `to` | `where` | Remark | +|-----------------------|:-------:|:----:|:-------------------:|---------------| +| service | | | | = `@requires` | +| entity | | | | | +| action/function | | | 1,2 | = `@requires` | + +> 1 For [bound actions and functions](../../cds/cdl#bound-actions) that are not *bound to a collection of instances*, Node.js supports instance-based authorization. > Example: > ```cds > entity Orders @(restrict: [ @@ -272,7 +271,7 @@ Restrictions can be defined on different types of CDS resources, but there are s > ]) {/*...*/} > ``` -> 2 For actions and functions that are either unbound or bound to a *collection* of instances, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. +> 2 For actions and functions that are either *unbound* or *bound to a collection of instances*, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. Unsupported privilege properties are ignored by the runtime. Especially, for bound or unbound actions, the `grant` property is implicitly removed (assuming `grant: '*'` instead). The same also holds for functions: From 000bc07f19a5605b6f2b6e9eb3f5ba72ab6b5099 Mon Sep 17 00:00:00 2001 From: Sebastian Van Syckel Date: Mon, 17 Aug 2026 11:00:10 +0200 Subject: [PATCH 10/12] untoggle authorization guide --- guides/security/authorization.md | 43 +++++++++----------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/guides/security/authorization.md b/guides/security/authorization.md index 21f41a5a0c..cc061338e4 100644 --- a/guides/security/authorization.md +++ b/guides/security/authorization.md @@ -2,7 +2,6 @@ description: > This guide explains how to restrict access to data by adding respective declarations to CDS models, which are then enforced by CAP's generic service providers. uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/e4a7559baf9f4e4394302442745edcd9.html -impl-variants: true ---