Skip to content
78 changes: 45 additions & 33 deletions guides/security/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

<script setup>
Expand All @@ -20,8 +19,6 @@ impl-variants: true

# CAP-level Authorization { #authorization }

<ImplVariantsHint />

This guide explains how to restrict access to data by adding respective declarations to CDS models, that are then enforced by CAP's generic service providers.

[[toc]]
Expand Down Expand Up @@ -257,14 +254,21 @@ 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 | <Na/> | <Y/> | <Na/> | = `@requires` |
| entity | <Y/> | <Y/> | <Y/><sup>1</sup> | |
| action/function | <Na/> | <Y/> | <Na/><sup>2</sup> | = `@requires` |
| CDS Resource | `grant` | `to` | `where` | Remark |
|-----------------------|:-------:|:----:|:-------------------:|---------------|
| service | <Na/> | <Y/> | <Na/> | = `@requires` |
| entity | <Y/> | <Y/> | <Y/> | |
| action/function | <Na/> | <Y/> | <Na/><sup>1,2</sup> | = `@requires` |

> <sup>1</sup> For [bound actions and functions](../../cds/cdl#bound-actions) that are <u>not</u> *bound to a collection of instances*, Node.js supports instance-based authorization.
> Example:
> ```cds
> entity Orders @(restrict: [
> { grant: 'cancel', where: (CreatedBy = $user) },
> ]) {/*...*/}
> ```

> <sup>1</sup>For 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`.
> <sup>2</sup> 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`.
> <sup>2</sup> 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:

Expand Down Expand Up @@ -314,7 +318,7 @@ The resulting authorizations are illustrated in the following access matrix:
| `CustomerService.Orders` (*) | <X/> | <Y/><sup>1</sup> | <X/> | <X/> |
| `CustomerService.monthlyBalance` | <Y/> | <X/> | <X/> | <X/> |

> <sup>1</sup> A `Vendor` user can only access the instances that they created. <br>
> <sup>1</sup> A `Customer` user can only access the instances that they created. <br>

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.

Expand Down Expand Up @@ -440,26 +444,23 @@ This means that, the condition applies to following standard CDS events only:
- `UPDATE` (as reject condition)
- `DELETE` (as reject condition)

<div class="impl java">

In addition, the runtime [checks the filter condition of the input data](#input-data-auth) for following standard CDS events:
::: tip Java
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)
:::

</div>
::: tip Node.js
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`.
:::

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

* Predicates with arithmetic operators.
* Combining predicates to expressions with `and` and `or` logical operators.
* Value references to constants, [user attributes](#user-attrs), and entity data (elements including [association paths](#association-paths))
* [Exists predicate](#exists-predicate) based on subselects.

<div class="impl java">

* [Exists with a subquery](#exists-subquery) for access to ACL like entities.

</div>
* [Exists with a subquery](#exists-subquery) for access to Access Control list like entities. _(Java only)_


At runtime you'll find filter predicates attached to the appropriate CQN queries matching the instance-based condition.
Expand Down Expand Up @@ -607,14 +608,9 @@ service SalesOrderService @(requires: 'authenticated-user') {
Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the condition selects at most one associated instance_ (`exists` semantic).


<div class="impl java">

<div id="exists-subquery" />

</div>

<div id="exists-subquery"></div>

### Checking Input Data { #input-data-auth .java}
### Checking Input Data in Java { #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`.
Expand All @@ -633,25 +629,41 @@ Starting with CAP Java `4.0`, deep authorization is active by default.
It can be disabled by setting <Config java>cds.security.authorization.instanceBased.checkInputData: false</Config>.


### Rejected Entity Selection { #reject-403 .java}
### Simple Static Checks in Node.js { #simple-static-checks }

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' } ]);
Comment on lines +639 to +640

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This snippet seems not correct (see build error).

```

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

::: tip Java
Starting with CAP Java `4.0`, the reject behaviour is active by default.
It can be disabled by setting <Config java>cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: false</Config>.
:::



## Limitations {.node}
## Limitations in Node.js

Currently, the security annotations **are only evaluated on the target entity of the request**.
Restrictions on associated entities touched by the operation are not regarded.
Expand All @@ -662,7 +674,7 @@ This has the following implications:
See [solution sketches](#limitation-deep-authorization) for information about how to deal with that.


## Deep Authorizations { #deep-auth .java}
## Deep Authorizations in Java { #deep-auth }

### Associations

Expand Down
Loading